这里还是一个Haskell新手。我知道这足以让我自己陷入错误假设的困境。如果我有以下功能......
quadsum w x y z = w+x+y+z
我想要一个可以获取列表的函数,将每个元素用作指定函数(如quadsum
)中的参数,并返回一个curried函数供以后使用。
我一直在努力尝试......
的效果magicalFunctionMaker f [] = (f)
magicalFunctionMaker f (x:xs) = magicalFunctionMaker (f x) xs
希望能够做到这一点......
magicalFunctionMaker (quadsum) [4,3,2]
获得像......这样的咖喱函数:
(((quadsum 4) 3) 2)
或者,或者,致电:
magicalFunctionMaker (quadsum) [4,3,2,1]
导致......
((((quadsum 4) 3) 2) 1)
这可能吗?我是多么误导?
答案 0 :(得分:7)
我认为你误解了Haskell类型系统。
首先,你的“quadsum”功能已经过去了。您可以编写“quadsum 4 3”并返回一个函数,该函数期望2和1作为额外参数。当你写“quadsum 4 3 2 1”相当于“((((quadsum 4)3)2)1)”。
在Haskell中,整数列表与整数或元组的类型不同,例如“(4,3,2,1)”。鉴于此,很难理解你想要做什么。如果你写这个应该发生什么?
magicalFunctionMaker quadsum [5,4,3,2,1]
你的“magicalFunctionMaker”看起来很像“foldl”,除了你给foldl的函数只有两个参数。所以你可以写:
mySum = foldl (+) 0
这将返回一个获取列表并对元素求和的函数。
(顺便说一句,一旦你理解了这一点,就要了解foldl和foldr之间的区别。
编辑:
重新阅读你的问题后,我想你正试图获得:
magicalFunctionMaker quadSum [4,3,2,1] :: Integer
magicalFunctionMaker quadSum [4,3,2] :: Integer -> Integer
这是不可能的,因为magicalFunctionMaker的类型将取决于list参数的长度,这意味着动态类型。正如有人所说,多变量函数可以做一些接近这个的事情(尽管没有列表参数),但这需要相当多的类型hackery毫克。
答案 1 :(得分:3)
quadsum 4 3 2
,结果将是您想要的功能,类型为Integer -> Integer
。
但有时这还不够好。有时你得到数字列表,你不知道列表有多长,你需要将这些元素应用到你的函数中。这有点难。你做不到:
magicalFunction2 f [] = f
magicalFunction2 f (x1:x2:xs) = f x1 x2
因为结果有不同的类型。在第一种情况下,结果需要两个参数,而在第二种情况下,它是一个完全应用的函数,因此不再允许参数。在这种情况下,最好的做法是保持列表和原始函数,直到有足够的参数可用:
type PAPFunc f a result = Either (f, [a]) result
magicfunc f xs = Left (f,xs)
apply (Left (f,xs)) ys = Left (f,xs++ys)
apply p _ = p
simp2 :: PAPFunc (a->a->b) a b -> PAPFunc (a->a->b) a b
simp2 (Left (f,(x1:x2:xs))) = Right (f x1 x2)
simp2 p = p
现在你可以这样做:
Main> let j = magicfunc (+) []
Main> let m = apply j [1]
Main> let n = apply m [2,3]
Main> either (const "unfinished") show $ simp2 m
"unfinished"
Main> either (const "unfinished") show $ simp2 n
"3"
每个arity都需要一个单独的简化函数,这个问题最容易被Template Haskell修复。
使用参数列表(而不是列表的参数)在Haskell中往往非常尴尬,因为多个结果都有不同的类型,并且对具有可变数量的不同类型参数的集合的支持非常少。我已经看到了三种常见的解决方案类别:
为每个案例明确编码 分开(快速变成很多 工作)。
模板Haskell。
输入系统hackery。
我的回答主要是试图减少痛苦。 2和3不适合胆小的人。
编辑:事实证明,Hackage上有some packages与此问题相关。使用“iteratee”:
import qualified Data.Iteratee as It
import Control.Applicative
magic4 f = f <$> It.head <*> It.head <*> It.head <*> It.head
liftedQuadsum = magic4 quadsum
-- liftedQuadsum is an iteratee, which is essentially an accumulating function
-- for a list of data
Main> p <- It.enumChunk (It.Chunk [1]) liftedQuadsum
Main> It.run p
*** Exception: EofException
Main> q <- It.enumChunk (It.Chunk [2,3,4]) p
Main> It.run q
10
但是“iteratee”和“enumerator”可能有点过分。
答案 2 :(得分:3)
我遇到了同样的问题:我有一个像
这样的功能someFunc :: Int -> Int -> Int -> Int
我喜欢做的是制作一个神奇的功能,例如
listApply :: [Int] -> (Int -> Int -> Int -> Int) -> Int
这样我可以说
listApply [1,2,3] someFunc
看起来似乎并且约翰的答案同意,为了做到这一点,应该可以做一些类型系统魔术。有类似问题的解决方案涉及使用一堆显式滚动和展开来显式地使用iso-recursive数据类型(参见,例如,类型和编程语言的第20章,或this thread中的第4篇文章)。
我在类型解决方案上攻击了一段时间;这感觉很可能,但在决定试用模板Haskell之前,我并没有完全开始工作,而且事情变得更加友好。
{-# LANGUAGE TemplateHaskell #-}
import Language.Haskell.TH
import Language.Haskell.TH.Syntax
lApply :: [Int] -> String -> ExpQ
lApply [] fn = return $ VarE (mkName fn)
lApply (l:ls) fn = [| $(lApply ls fn) l |]
(请记住使用LANGUAGE编译指示或-XTemplateHaskell命令行开关。)
要使用此功能,请将lApply调用到拼接内部,如下所示:
> $(lApply [1,2] "+")
3
请注意,我必须使用包含我要调用的函数名称的字符串:我不能将函数直接提升到ExpQ中,但我可以引用它的全局绑定。我可以看到这可能会让人烦恼。此外,由于我们遍历列表的方式,参数必须在列表中以相反的顺序显示。
还有一些其他问题:为了将此概括为其他数据类型,这些类型必须在Lift类中具有相应的实例。例如,Double没有实例,但您可以轻松制作一个:
instance Lift Double where
lift x = return $ LitE (RationalL (toRational x))
Lit数据类型没有DoubleL构造函数,但可以使用RationalL,因为它将拼接到Fractional类的一般成员。
如果要将此类函数用作混合类型作为参数,则无法传递列表,因为列表不能是混合类型。你可以使用元组来做这件事,老实说使用Template Haskell并不困难。在这种情况下,您将创建一个函数,该函数生成一个函数的AST,该函数在内部使用适当类型的元组并将其映射到您想要的函数调用。或者,您可以将参数类型包装在适当制作的ADT中,顺便提一下,您也可以使用Template Haskell创建。这留给读者一个练习:)
最后,所有标准的模板Haskell限制都适用。例如,由于GHC阶段限制,您无法从定义它的模块中调用此函数。
模板Haskell是有趣而有趣的东西,但是说实话,iso-recursive数据类型解决方案可能性能稍高,显然不需要额外使用TH。如果/当我开始工作时,我会回来并发布一个后续行动:)
答案 3 :(得分:2)
您甚至无法“手动”列出不同长度的案例:
mf f [] = f
mf f [x] = f x
mf f [x,y] = f x y
--Occurs check: cannot construct the infinite type: t = t1 -> t
--Probable cause: `f' is applied to too many arguments
--In the expression: f x
--In the definition of `mf': mf f [x] = f x
这意味着mf不能采取任意“arity”的功能,你必须决定一个。出于同样的原因,您无法将任意列表转换为元组:您不能说元组将包含多少元素,但类型系统需要知道。
通过将f限制为递归类型a = a - &gt;可能存在解决方案。 a使用“forall”(见http://www2.tcs.ifi.lmu.de/~abel/fomega/hm.html)。但是,我无法让它工作(似乎我必须告诉Leksah在某处使用标志-XRankNTypes),对f的限制会使你的函数变得毫无用处。
[编辑]
思考,最接近你想要的东西可能是某种减少功能。正如保罗指出的那样,这与foldl,foldr ...类似(但下面的版本没有“额外的参数”,并且与foldl相比具有同质类型。请注意空列表缺少基本情况)
mf :: (a → a → a) -> [a] -> a
mf f [x] = x
mf f (x:y:xs) = mf f ((f x y) : xs)
答案 4 :(得分:1)
我认为不会工作。 (((quadsum 4) 3) 2)
的类型Intger -> Integer
与((((quadsum 4) 3) 2) 1)
的类型不同,其类型为Integer
。
答案 5 :(得分:1)
我打算编辑我的其他帖子,但这对自己来说足够大了。
这是使用“类型魔术”进行此操作的一种方法,但我觉得它有点不理想,因为它需要一个特定于特定数量参数的函数的提升函数(更多信息如下)。
让我们从定义递归数据类型
开始data RecT a = RecR a
| RecC (a -> RecT a)
因此,RecT类型的变量可以只是一个包装结果(RecR),也可以是一个连续的递归(RecC)。
现在,我们如何处理并将其转换为RecT a?
值很简单:
valRecT x = RecR x
RecR x显然属于RecT a。
一个带有一个参数的函数怎么样,比如id?
idRecT x = RecC $ \x -> RecR x
RecC包装一个带变量的函数并返回RecT a类型。表达式
\x -> RecR x
就是这样一个函数,因为我们在RecR x之前观察到的类型是RecT a。
更一般地说,任何单参数函数都可以解除:
lift1RecT :: (a -> a) -> RecT a
lift1RecT fn = RecC $ \a -> RecR $ fn a
我们可以通过在RecC中重复包装更深层嵌套的函数调用来概括这一点:
lift2RecT :: (a -> a -> a) -> RecT a
lift2RecT fn = RecC $ \b -> RecC $ \a -> RecR $ fn b a
lift3RecT :: (a -> a -> a -> a) -> RecT a
lift3RecT fn = RecC $ \c -> RecC $ \b -> RecC $ \a -> RecR $ fn c b a
好的,所以我们已经完成了所有这些工作,将任意数量的参数的函数转换为单一类型,RecT a。我们如何使用它?
我们可以轻松写下一个级别的功能应用程序:
reduceRecT :: RecT a -> a -> RecT a
reduceRecT (RecC fn) = fn
reduceRecT _ = undefined
换句话说,reduceRecT采用RecT a类型的参数和类型a的另一个参数,并返回一个减少了一个级别的新RecT。
我们还可以将RecT内的已完成计算展开到结果中:
unrollRecT :: RecT a -> a
unrollRecT (RecR fn) = fn
unrollRecT _ = undefined
现在我们准备将一个参数列表应用于一个函数!
lApply :: [a] -> RecT a -> a
lApply [] fn = unrollRecT fn
lApply (l:ls) fn = lApply ls $ (reduceRecT fn) l
让我们首先考虑基本情况:如果我们完成了计算,我们只需打开结果并返回它。在递归的情况下,我们将参数列表减1,然后通过将列表的头部应用于缩减的fn来变换fn,从而产生新的RecT a。
让我们试一试:
lApply [2,5] $ lift2RecT (**)
> 32.0
那么,这种方法的优缺点是什么?那么,Template Haskell版本可以做部分列表应用;这里提出的isorecursive类型解决方案并不正确(尽管我们原则上可以通过一些丑陋来解决这个问题)。类型解决方案还有一个缺点,就是它有更多的样板代码:我们需要一个listNRecT来处理我们想要使用的所有N.最后,如果我们想要将lApp应用于混合变量类型的函数,那么将它推广到类似的元组解决方案要容易得多。
当然,另一个有趣的可能性是通过使用Template Haskell生成listNRecT函数来增强简洁性;这消除了一些样板,但从某种意义上说,它会带来两种实现的缺点。