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

问题描述:

这里仍然是Haskell的新手。我只知道自己因错误的假设而陷入困境。如果我有以下功能...将列表元素作为参数传递给curried函数

quadsum w x y z = w+x+y+z 

我想,可以采取一个列表,在指定的函数中使用的每个元素作为参数一样quadsum,并返回一个咖喱​​功能供以后使用的功能。

我一直想的东西的效果...

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

快速浏览polyvariadic函数,但它可能会打击你的思想:[http://*.com/questions/3467279/how-to-create-a-polyvariadic-haskell-function] – fuz 2010-09-23 11:51:56

Paul Johnson的回答几乎涵盖了它。只要做

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" 

你需要一个单独的简化函数用于每个元素,这个问题最容易由Template Haskell修复。

在Haskell中使用参数列表(相对于列表参数)往往非常尴尬,因为多个结果都有不同的类型,并且对可变数量的不同类型参数的集合的支持很少。我见过的解决方案的三大类:

  1. 每个案例 明确代码分开(迅速成为了很多 工作)。

  2. 模板Haskell。

  3. 类型系统hackery。

我的回答大多是试图让1痛苦少交易。 2和3不适合心脏病。

编辑:事实证明,有关于这个问题的Hackage上有somepackages。使用“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”和“枚举器”可能矫枉过正。

+0

谢谢。这有助于清理很多。 – Ishpeck 2010-09-23 18:46:47

+0

'在第一种情况下,结果需要两个参数' - 如果这不是'需要一个参数' - '4 3 2'需要'1'? – 2017-12-29 16:36:02

不工作我想。 (((quadsum 4) 3) 2)具有不同的类型Intger -> Integer,例如具有Integer类型的((((quadsum 4) 3) 2) 1)

我认为你误解了Haskell类型系统。

首先,你的“quadsum”函数已经被curried了。您可以编写“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的类型取决于列表参数的长度,这意味着动态类型化。正如有人所说,多变量函数可以做某些事情(尽管没有列表参数),但这需要归仁几个毫克型的hackery。

+0

它不会意味着动态打字;这意味着*依赖打字*。 – 2015-10-08 21:07:14

你甚至不能“手动”列出的情况下对不同长度:

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不能把任意“元数”,你必须决定一个功能。出于同样的原因,您不能将任意列表转换为元组:您不能说元组将拥有多少元素,但类型系统需要知道。

通过使用“forall”(参见http://www2.tcs.ifi.lmu.de/~abel/fomega/hm.html),可以通过将f限制为递归类型a = a→a来解决这个问题。然而,我无法得到它的工作(看来我必须告诉Leksah在某处使用标志-XRankNTypes),而对f的限制会让你的功能变得毫无用处。

[编辑]

一下,最接近你想要的思考可能是某种减少功能。正如Paul指出的,这与foldl,foldr类似(但是下面的版本没有“额外论证”并且与foldl相比具有同质类型。注意空列表的缺失基本情况)

mf :: (a → a → a) -> [a] -> a 
mf f [x] = x 
mf f (x:y:xs) = mf f ((f x y) : xs) 

我碰到了同样的问题:我有一个像

someFunc :: Int -> Int -> Int -> Int 

功能我很乐意做的就是一个神奇的功能,使得例如

listApply :: [Int] -> (Int -> Int -> Int -> Int) -> Int 

这样我可以说

listApply [1,2,3] someFunc 

本能地看来,约翰的回答同意,它应该是可能的o做一些类型系统魔术为了做到这一点。有类似问题的解决方案涉及通过一系列明确的滚动和展开(例如,参见类型和编程语言的第20章或this thread中的第4篇文章)来显式提供iso-recursive数据类型。

我在类型解决方案上砍了一段时间;感觉可能,但在决定尝试模板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 |] 

(记得要使用的语言编译或-XTemplateHaskell命令行开关。)

要使用这个,你叫lApply接头内,像这样:

> $(lApply [1,2] "+") 
3 

请注意,我有要使用包含我想调用的函数名称的字符串:我无法直接将函数提升到ExpQ,但我可以引用其全局绑定。我可以看到这可能会让人讨厌。另外,由于我们遍历列表的方式,参数必须在列表中以相反顺序呈现。

还有一些其他的皱纹:为了将其推广到其他数据类型,这些类型必须在Lift类中具有相应的实例。例如,双没有实例,但你可以很容易地使一个:

instance Lift Double where 
     lift x = return $ LitE (RationalL (toRational x)) 

点燃的数据类型不具有DoubleL构造,但RationalL可以在它的地方使用,因为它会以拼接的一般成员分数类。

如果您希望将此类功能与混合类型作为参数一起使用,您将无法传递列表,因为列表不能是混合类型。你可以使用元组来做到这一点,使用模板哈斯克尔实际上并不困难。在这种情况下,你需要创建一个函数来生成一个函数的AST,该函数接受一个元组中包含适当类型的元组,并将其映射到你想要的函数调用。或者,你可以将你的参数类型包装在适当制作的ADT中,顺便说一下,你也可以使用Template Haskell创建。这是作为练习留给读者:)

最后,所有的标准模板Haskell限制适用。例如,由于GHC阶段限制,您无法从定义该模块的模块调用该函数。

模板Haskell是有趣和有趣的东西,但要完全诚实的iso-recursive数据类型解决方案可能是一个更高的性能,并且显然不需要额外使用TH。我会回来后,如果/当我得到那个工作后发布:)

我要编辑我的另一篇文章,但这是足够大的自己。

下面是使用“类型魔法”进行处理的一种方法,但是我觉得它有点不太理想,因为它需要特定于特定数量参数的函数的提升函数(详见下文)。

让我们首先定义一个递归的数据类型

data RecT a = RecR a 
      | RecC (a -> RecT a) 

所以RECT类型变量可以是只是包着一个结果(RECR),也可以是一个不断递归(RECC)。

现在,我们如何采取措施并将其转换为RecT a类型?

价值观很简单:

valRecT x = RecR x 

RECR x是类型RECT一个显然。

如何使用一个参数,如id?

idRecT x = RecC $ \x -> RecR x 

RecC包装一个函数,该函数接受一个变量并返回类型RecT a。表达

\x -> RecR x 

就是这样一个功能,因为当我们观察到之前RECR x是类型RECT一个的。

更一般地,任何一个参数的功能,可以解除:

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一个的参数和另一并返回一个新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 

所以,优点和这种方法的缺点是什么?那么,模板Haskell版本可以执行部分​​列表应用程序;这不是真正的这里提出的isorecursive类型的解决方案(虽然我们原则上可以用一些丑陋来解决这个问题)。类型解决方案还有一个缺点:有更多的样板代码与其相关联:我们需要一个listNRecT用于我们想要使用的所有N.最后,如果我们希望将其应用于混合变量类型的函数,那么将其推广到类比元组解决方案并不那么容易。

当然,另一个有趣的可能性是通过使用Template Haskell来生成listNRecT函数来增强简洁性;这消除了一些样板,但从某种意义上来说,这两种实现都有缺点。