我知道有预定义的Eq
个实例for tuples of lengths 2 to 15。
为什么元组不能被定义为某种递归数据类型,以便它们可以被分解,允许定义一个适用于任意长度元组的compare
的函数?
毕竟,编译器确实支持任意长度的元组。
答案 0 :(得分:10)
你可能会问自己,这种广义比较函数的类型是什么。首先,我们需要一种方法来编码组件类型:
data Tuple ??? = Nil | Cons a (Tuple ???)
我们可以用问题替换问题。结论是常规ADT是不够的,所以我们需要我们的第一语言扩展,GADTs:
data Tuple :: ??? -> * where
Nil :: Tuple ???
Cons :: a -> Tuple ??? -> Tuple ???
然而我们最终会出现问号。填充漏洞需要另外两个扩展,DataKinds和TypeOperators:
data Tuple :: [*] -> * where
Nil :: Tuple '[]
Cons :: a -> Tuple as -> Tuple (a ': as)
如您所见,我们需要三种类型的系统扩展才能对类型进行编码。我们现在可以比较吗?嗯,回答并不是那么简单,因为如何编写独立的比较函数实际上并不明显。幸运的是,类型类机制允许我们采用简单的递归方法。但是,这次我们不只是在值级别上进行递归,而且还在类型级别上进行递归。显然空元组总是相等的:
instance Eq (Tuple '[]) where
_ == _ = True
但编译器再次抱怨。为什么?我们需要另一个扩展,FlexibleInstances,因为'[]
是一个具体的类型。现在我们可以比较空元组,这不是那么引人注目。非空元组怎么样?我们需要比较头部以及元组的其余部分:
instance (Eq a, Eq (Tuple as)) => Eq (Tuple (a ': as)) where
Cons x xs == Cons y ys = x == y && xs == ys
似乎有意义,但繁荣!我们得到另一个投诉。现在编译器需要FlexibleContexts,因为我们在上下文中有一个非完全多态的类型Tuple as
。
这总共有五种类型的系统扩展,其中三种只是为了表达元组类型,它们在GHC 7.4之前不存在。需要另外两个进行比较。当然有一个回报。我们得到了一个非常强大的元组类型,但由于所有这些扩展,我们显然不能将这样的元组类型放入基础库。
答案 1 :(得分:8)
你总是可以用二进制元组重写任何n元组。例如,给出以下4元组:
(1, 'A', "Hello", 20)
您可以将其重写为:
(1, ('A', ("Hello", (20, ()))))
将其视为列表,其中(,)
扮演(:)
角色(即“cons”),()
扮演[]
角色(即“nil”) “)。使用这个技巧,只要你根据“二进制元组列表”来表示你的n元组,那么你可以无限扩展它,它将自动派生出正确的Eq
和Ord
个实例。
答案 2 :(得分:2)
compare
的类型为a -> a -> Ordering
,表明两个输入必须属于同一类型。根据定义,不同arities的元组是不同的类型。
然而,您可以使用HLists or GADTs来接近问题来解决您的问题。
答案 3 :(得分:0)
我只想添加到ertes的回答,你不需要一个扩展来做到这一点。以下代码应该是haskell98以及2010兼容。并且其中的数据类型可以一对一映射到元组,但单例元组除外。如果你在两元组之后进行递归,你也可以实现这一点。
module Tuple (
TupleClass,
TupleCons(..),
TupleNull(..)
) where
class (TupleClassInternal t) => TupleClass t
class TupleClassInternal t
instance TupleClassInternal ()
instance TupleClassInternal (TupleCons a b)
data (TupleClassInternal b) => TupleCons a b = TupleCons a !b deriving (Show)
instance (Eq a, Eq b, TupleClass b) => Eq (TupleCons a b) where
(TupleCons a1 b1) == (TupleCons a2 b2) = a1 == a2 && b1 == b2
你也可以派生出Eq。当然,使用TypeOperators看起来会有点凉,但是haskell的列表系统也有语法糖。