25

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

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

如果我想对具有任意数量参数的函数进行 uncurry,我可以编写单独的函数:

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

但这很快就会变得乏味。有什么方法可以概括这一点,所以我只需要编写一个函数吗?

4

3 回答 3

23

uncurryN元组包中尝试。像所有形式的重载一样,它是使用类型类实现的。在这种情况下,通过手动拼出最多 15 个元组的实例,这应该绰绰有余。

可变参数函数也可以使用类型类。其中一个例子是Text.Printf。在这种情况下,它是通过对函数类型的结构归纳来完成的。简化后,它的工作原理如下:

class Foo t

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

foo :: Foo

应该不难看出foo可以实例化为类型IO aa -> IO b等等a -> b -> IO cQuickCheck也使用这种技术。

但是,结构归纳不适用于元组,因为n元组与n+1元组完全无关,所以这就是必须手动拼写实例的原因。

于 2011-08-28T12:32:41.267 回答
11

寻找使用过度类型系统技巧来伪造这种东西的方法是我的爱好之一,所以当我说结果非常丑陋时请相信我。特别要注意,元组不是递归定义的,因此没有真正的方法可以直接抽象它们;就 Haskell 的类型系统而言,每个元组的大小都是完全不同的。

因此,任何直接使用元组的可行方法都需要代码生成——使用 TH 或使用tuple包的外部工具。

要在不使用生成代码的情况下伪造它,您必须首先使用递归定义——通常是带有“nil”值的右嵌套对来标记结束,或者(,)()它们等效。您可能会注意到,这类似于列表的定义,(:)并且[]- 事实上,这种递归定义的伪元组可以被视为类型级数据结构(类型列表)或异构列表(例如,HList 以这种方式工作)。

缺点包括但不限于,实际使用以这种方式构建的东西可能比它的价值更尴尬,实现类型系统技巧的代码通常令人困惑且完全不可移植,最终结果不是无论如何都必须等价——例如(a, (b, (c, ())))和之间存在多个重要的差异。(a, b, c)

如果你想知道它变得多么可怕,你可以看看我在 GitHub 上的东西,尤其是这里的部​​分。

于 2011-08-28T19:19:41.993 回答
2

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

但是,可以使用Template Haskell生成许多不同的变体,否则您必须手动编写这些变体。

于 2011-08-28T12:32:16.743 回答