将列表元素作为参数传递给curried函数

时间:2010-09-23 04:26:11

标签: haskell currying

这里还是一个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)

这可能吗?我是多么误导?

6 个答案:

答案 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中往往非常尴尬,因为多个结果都有不同的类型,并且对具有可变数量的不同类型参数的集合的支持非常少。我已经看到了三种常见的解决方案类别:

  1. 为每个案例明确编码 分开(快速变成很多 工作)。

  2. 模板Haskell。

  3. 输入系统hackery。

  4. 我的回答主要是试图减少痛苦。 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函数来增强简洁性;这消除了一些样板,但从某种意义上说,它会带来两种实现的缺点。