使用列表的内容作为单个多参数函数的位置参数

时间:2014-11-17 13:19:38

标签: haskell arguments

是否有标准的Haskell函数(或模式)来提取列表的内容并将它们作为函数的有序位置参数提供它们?

例如,考虑函数(,),当给定两个位置参数时,它将从它们产生两元组:

(,) 3 4 --> (3,4)

假设我通过一些我无法改变的外部函数调用给我这些参数,表示为列表[3, 4]

是否存在“操作内容”,以便这样做:

(,) $ contents_of [3, 4]

以便contents_of的行为就像项目放在源代码中一样,它们之间有空格作为函数应用程序?

例如,(,) $ contents_of [1]应该是curry函数((,) 1),然后再用一个参数来完成创建元组。

我有一个想法是尝试将功能折叠在列表上,折叠函数表示currying:

foldr (\x y -> y x) (,) [3, 4]

但是查看foldr的类型签名:

foldr :: (a -> b -> b) -> b -> [a] -> b

让这看起来很难。 b这里需要是函数类型本身,但是当它被应用到参数时,它将不再是具有与b相同类型签名的函数,导致在折叠中键入问题。

这与Python *args构造的精神相似。

我并不关心这可能意味着的严格属性 - 只是在标准的Haskell中是否可以这样做。

5 个答案:

答案 0 :(得分:4)

可以像这样表示N元函数:

data FunN r a = FunN Int (a -> FunN r a) | FNil r

然后将普通函数转换为FunN

f2FunN :: (FunN (a->b) a) -> FunN b a
f2FunN (FNil g)   = FunN 1 (FNil . g)
f2FunN (FunN n g) = FunN (n+1) (f2FunN . g)

然后应用参数列表:

a :: FunN b a -> [a] -> b
a (FNil r)   []    = r
a (FunN _ f) (x:t) = a (f x) t
a _          _     = error "wrong arity"

例如:

Prelude> a (f2FunN $ f2FunN $ FNil (+)) [1,2]
3
Prelude> a (f2FunN $ FNil (+)) [1] 2
3
Prelude> a (f2FunN $ f2FunN $ FNil (+)) [1,2,3]
*** Exception: wrong arity
Prelude> a (f2FunN $ f2FunN $ FNil (+)) [1]
*** Exception: wrong arity

但是当然你需要在编译时知道函数的arity - 这样你才能知道用f2FunN包装函数的次数。

答案 1 :(得分:3)

正如我在上面的评论中所解释的那样,我不认为标准列表可以实现这一点。因此,让我们引入一个新列表,其中列表中的每个元素可以是不同的类型,并且这是以列表的类型编码的:

{-# LANGUAGE GADTs, MultiParamTypeClasses, FlexibleInstances #-}

data Nil
data TList a b where
    TEmpty :: TList Nil Nil
    (:.) :: c -> TList d e -> TList c (TList d e)
infixr 4 :.

TEmpty此处类似于[]:.:。因此,我们可以列出Int和几个Bool s 31 :. True :. False :. TEmpty的列表。此列表的类型为TList Int (TList Bool (TList Bool (TList Nil Nil)))

我们现在可以引入一个类型类,它提供了一个函数,可以用你的方式将任意函数应用到列表中,只要类型匹配。

class TApplies f h t r where
    tApply :: f -> TList h t -> r

instance TApplies a Nil Nil a where
    tApply a _ = a

instance TApplies f h t r => TApplies (a -> f) a (TList h t) r where
    tApply f (e :. l) = tApply (f e) l

我们现在可以使用tApply来做你想要的事情。请注意,以下内容将无法编译:

tApply (+) $ 1 :. (2 :: Int) :. TEmpty

我们必须明确表示注释所有内容:

tApply ((+) :: Int -> Int -> Int) $ (1 :: Int) :. (2 :: Int) :. TEmpty :: Int

这给出了我们可能期望的3。我不确定如何解决这个必要性;我希望巧妙地使用FunctionalDependencies可以解决问题。

答案 2 :(得分:2)

我经常使用像

这样的匹配模式
let (a:b:c:_) = args
in  func a b c

或内联

(\(a:b:c:_) -> func a b c) args

如果你真的想这样做,你可以创建一个运算符来“inyect”元素列表到任何函数

showAdd2 <<< args

但我认为不是很有用......

{-# LANGUAGE FlexibleInstances, UndecidableInstances, ConstraintKinds, InstanceSigs, MultiParamTypeClasses #-}
class App a b c where
  (<<<) :: a -> [b] -> c

instance App (b -> b -> c) b c where -- 2-arity
  (<<<) f (a:b:_) = f a b

instance App (b -> b -> b -> c) b c where -- 3-arity
  (<<<) f (a:b:c:_) = f a b c

instance App (b -> b -> b -> b -> c) b c where -- 4-arity
  (<<<) f (a:b:c:d:_) = f a b c d

showAdd2 :: Int -> Int -> String
showAdd2 a b = show (a + b)

showAdd3 :: Int -> Int -> Int -> String
showAdd3 a b c = show (a + b + c)

main = do

    let args = [5..8] :: [Int]

        r2 = showAdd2 <<< args
        r3 = showAdd3 <<< args

    putStrLn r2
    putStrLn r3

更有用的版本可能是

i2 :: (b -> b -> c) -> [b] -> c
i2 f (a:b:_) = f a b

但你必须为每个案件选择合适的“inyector”

showAdd2 `i2` [4..5]

(在Haskell中,使用代数数据类型,列表和类正确地替换了多变量函数的需要。正如@ user5402所说,你应该提供一些明确的问题)

答案 3 :(得分:2)

为什么列表本身不起作用

在最狭隘的问题上,

  

是否有标准的Haskell函数(或模式)来提取列表的内容并将它们作为函数的有序位置参数提供它们?

这有一个非常简单的答案“不”。这种功能不存在的原因是因为类型签名不容易理解。您要求的类型签名是:

applyToList :: (a -> c) -> [a] -> d

其中c的格式为a -> c'(在这种情况下我们会递归定义),或者其类型为d。 Prelude函数都没有古怪的类型签名。

如果您主动忽略此操作并尝试,则会收到以下错误:

Prelude> let applyToList f list = case list of [] -> f; x : xs -> applyToList (f x) xs

<interactive>:9:71:
    Occurs check: cannot construct the infinite type: t1 ~ t -> t1
    Relevant bindings include
    xs :: [t] (bound at <interactive>:9:52)
    x :: t (bound at <interactive>:9:48)
    list :: [t] (bound at <interactive>:9:19)
    f :: t -> t1 (bound at <interactive>:9:17)
    applyToList :: (t -> t1) -> [t] -> t -> t1
        (bound at <interactive>:9:5)
    In the first argument of ‘applyToList’, namely ‘(f x)’
    In the expression: applyToList (f x) xs

问题在于,当typechecker尝试将ca -> c统一起来时,它会构造一个无限类型;它有一个循环检测算法可以阻止它,所以它过早出错了。

这里有一个更基本的问题,那就是applyTo (+) [3]应该产生什么的问题。对于某些(+)n -> n -> n n类型为(3+) :: n -> n。道德上正确的答案是undefined :: n;但如果您真的想要使用列表中的所有参数,则可能希望它返回applyTo f = f . head。您无法使用道德上正确答案的原因是您拒绝定义[3] 可输入并执行上述操作)。摘要中的问题是,{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FlexibleContexts, FunctionalDependencies, UndecidableInstances #-} -- no constructors; these exist purely in the type domain: data Z data S n class Reducible f a x | f -> a x where applyAll :: f -> [a] -> x newtype Wrap x n = Wrap x -- n is a so-called "phantom type" here rewrap :: Wrap x n -> Wrap x (S n) rewrap (Wrap x) = Wrap x wrap0 :: x -> Wrap x Z wrap0 = Wrap wrap1 = rewrap . wrap0 wrap2 = rewrap . wrap1 wrap3 = rewrap . wrap2 wrap4 = rewrap . wrap3 apply :: Wrap (a -> x) (S n) -> a -> Wrap x n apply (Wrap f) a = Wrap (f a) instance Reducible (Wrap x Z) a x where applyAll (Wrap x) [] = x applyAll (Wrap x) _ = error "function called on too many arguments" instance (Reducible (Wrap y n) a x) => Reducible (Wrap (a -> y) (S n)) a x where applyAll (Wrap f) [] = error "function called on too few arguments" applyAll w (x : xs) = applyAll (apply w x) xs 的长度直到运行时才知道。你可以在那里插入任意表达式。你可以尝试在一个长度为1的数组上运行它,如果Goldbach的猜想为真,或者如果不是则长度为2;是什么类型系统应该为你解决哥德巴赫猜想?

如何使它们起作用

最后一点实际上包含了解决方案。我们需要用它们的arities注释函数:

wrap3 (\x y z -> (x + y) * z) `applyAll` [9, 11, 2]

然后您可以编写如下内容:

40

它将正确构建applyAll (wrap3 ___) ___

正如你所知道的,这涉及很多包袱,但是所有必须告诉编译器“嘿,这个函数将有三个参数,所以长度3的列表对它来说是完美的”以完全通用的方式

当然,写Z是单调乏味的。但是,如果您正在尝试使用任意arities构建函数库,则可以使用一些额外的函数来管理这些arities。

您可能还希望使用S ZapplyAll等对列表的长度进行注释 - 在这种情况下,我认为您可以获得Wrap进行干扰。另外,正如另一个答案所指出的那样,你可以通过为{{1}}提供多个构造函数来获得良好的距离,这些构造函数将递归移动到数据类型本身 - 可能能够删除一些讨厌的语言扩展。

答案 4 :(得分:0)

在没有依赖类型或大量扩展的情况下编写真正的arity-generic函数是不可能的,但你可以作弊:

z :: b -> [a] -> b 
z x _ = x

s :: (b -> [a] -> c) -> (a -> b) -> [a] -> c
s h f (x:xs) = h (f x) xs
s _ _    _   = error "function called on too few arguments"

sum3 a b c   = a + b + c
tup4 a b c d = (a, b, c, d)

main = do
    print $ z               0    [1..4] -- 0
    print $ s (s (s z))     sum3 [1..4] -- 6
    print $ s (s (s (s z))) tup4 [1..4] -- (1, 2, 3, 4)
    -- print $ s (s (s (s z))) tup4 [1..3] -- runtime error

或者如果要抛出错误,如果列表中有多个元素,则将z的定义更改为

z :: b -> [a] -> b 
z x [] = x
z _ _  = error "function called on too many arguments"