Haskell是否有可变参数函数/元组?

25

uncurry函数仅适用于接受两个参数的函数:

uncurry :: (a -> b -> c) -> (a, b) -> c

如果我想要解开具有任意参数数量的函数的柯里化,我可以编写单独的函数:

uncurry2 f (a, b)          = f a b
uncurry3 f (a, b, c)       = f a b c
uncurry4 f (a, b, c, d)    = f a b c d
uncurry5 f (a, b, c, d, e) = f a b c d e

但是这很快就变得乏味了。有没有办法将其概括,这样我只需编写一个函数?


不是那样,但无论如何,你为什么要这样做?根据我的经验,只有极少数情况需要超过uncurry2。 - Paul Johnson
7
@Paul:没有具体的原因,但只要我看到任何形式的重复模式,我就会想如何将其归纳和抽象化。 - fredoverflow
3个回答

23

尝试使用tuple包中的uncurryN。像所有形式的重载一样,它是使用类型类实现的。在这种情况下,通过手动拼写实例来表示长达15元组,这应该足够了。

类型类也可以实现可变元函数。其中一个例子是Text.Printf。在这种情况下,它是通过对函数类型进行结构归纳来完成的。简单地说,它的工作原理如下:

class Foo t

instance Foo (IO a)
instance Foo b => Foo (a -> b)

foo :: Foo

我相信你不难看出foo可以实例化为IO aa -> IO ba -> b -> IO c等类型。QuickCheck也使用了这种技术。

然而,结构归纳在元组上不起作用,因为n-元组与n+1-元组完全无关,所以必须手动列出这些实例。


6
有趣的是,Data.Tuple.Curry 的源代码中提到,不同的实例实际上是由一个独立的程序生成的,然后被粘贴进来,就好像它们是手动输入的一样。 - Stuart Cook

11
寻找使用过度的类型系统技巧来伪造这种东西的方法是我的爱好之一,所以信任我,结果相当丑陋。特别是,请注意元组不是递归定义的,因此没有真正的直接抽象它们的方式;就Haskell的类型系统而言,每个元组大小完全不同。
因此,任何直接处理元组的可行方法都需要代码生成——可以使用TH,或者像tuple包一样使用外部工具。
要在不使用生成的代码的情况下伪造它,您必须首先诉诸于使用递归定义——通常是带有“nil”值以标记结尾的正确嵌套对,例如(,)和()或与它们等效的东西。您可能会注意到,这类似于以(:)[]为基础定义列表的方式——事实上,这种递归定义的伪元组可以被视为类型级数据结构(类型列表)或异构列表(例如,HList就是这样工作的)。
缺点包括但不限于,用这种方式构建的实际使用比值得的更加笨拙,实现类型系统技巧的代码通常令人困惑且完全不可移植,最终结果也不一定等价——例如,(a,(b,(c,())))(a,b,c) 之间存在多个非平凡的差异。
如果你想看看它有多可怕,可以看看我在GitHub上的内容,特别是这里面的部分。

3

没有一种直接的方式可以编写适用于不同数量参数的uncurry的单一定义。

不过,可以使用模板Haskell来生成许多手动编写的变量。


你可以使用Daniel Fridlender和Mia Indrika的arity family函数模式编写n元uncurry。在论文“我们需要依赖类型吗?”中给出了zipWithN的示例。 - stephen tetley
“绝对没有办法”可能有点过于大胆了。我把我的答案改成了“没有直接的方法”。 - Stuart Cook
@stephentetley 这个实现在哪个包里被实现了吗? - CMCDragonkai

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接