是否有标准的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中是否可以这样做。
答案 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尝试将c
与a -> 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 Z
,applyAll
等对列表的长度进行注释 - 在这种情况下,我认为您可以获得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"