我知道有长度为2到15的元组的预定义Eq实例.
为什么不将元组定义为某种递归数据类型,以便它们可以被分解,允许为compare任意长度元组的函数定义函数 ?
毕竟,编译器确实支持任意长度的元组.
ert*_*tes 10
您可能会问自己,该广义比较函数的类型是什么.首先,我们需要一种方法来编码组件类型:
data Tuple ??? = Nil | Cons a (Tuple ???)
Run Code Online (Sandbox Code Playgroud)
我们真的无法用问题代替问号.结论是常规ADT是不够的,所以我们需要我们的第一语言扩展,GADTs:
data Tuple :: ??? -> * where
Nil :: Tuple ???
Cons :: a -> Tuple ??? -> Tuple ???
Run Code Online (Sandbox Code Playgroud)
然而,我们最终会出现问号.填充漏洞需要另外两个扩展,DataKinds和TypeOperators:
data Tuple :: [*] -> * where
Nil :: Tuple '[]
Cons :: a -> Tuple as -> Tuple (a ': as)
Run Code Online (Sandbox Code Playgroud)
如您所见,我们需要三种类型的系统扩展来编码类型.我们现在可以比较吗?嗯,回答并不是那么简单,因为如何编写独立的比较函数实际上并不明显.幸运的是,类型类机制允许我们采用简单的递归方法.但是,这次我们不只是在值级别上进行递归,而且还在类型级别上进行递归.显然空元组总是相等的:
instance Eq (Tuple '[]) where
_ == _ = True
Run Code Online (Sandbox Code Playgroud)
但编译器再次抱怨.为什么?我们需要另一个扩展,FlexibleInstances,因为它'[]是一种具体的类型.现在我们可以比较空元组,这不是那么引人注目.非空元组怎么样?我们需要比较头部以及元组的其余部分:
instance (Eq a, Eq (Tuple as)) => Eq (Tuple (a ': as)) where
Cons x xs == Cons y ys = x == y && xs == ys
Run Code Online (Sandbox Code Playgroud)
似乎有意义,但繁荣!我们得到另一个投诉.现在编译器需要FlexibleContexts,因为我们在上下文中有一个非完全多态的类型Tuple as.
这是总共五种类型的系统扩展,其中三种只是为了表达元组类型,并且它们在GHC 7.4之前不存在.需要另外两个进行比较.当然有一个回报.我们得到了一个非常强大的元组类型,但由于所有这些扩展,我们显然不能将这样的元组类型放入基础库中.
你总是可以用二进制元组重写任何n元组.例如,给定以下4元组:
(1, 'A', "Hello", 20)
Run Code Online (Sandbox Code Playgroud)
您可以将其重写为:
(1, ('A', ("Hello", (20, ()))))
Run Code Online (Sandbox Code Playgroud)
将其视为一个列表,其中(,)扮演(:)(即"cons")()的角色并扮演[](即"nil")的角色.使用这个技巧,只要你根据"二进制元组列表"来表示你的n元组,那么你可以无限期地扩展它,它将自动导出正确的Eq和Ord实例.