为什么我不能在Haskell中比较任意长度的元组?

时间:2013-02-04 10:55:15

标签: haskell typeclass

我知道有预定义的Eq个实例for tuples of lengths 2 to 15

为什么元组不能被定义为某种递归数据类型,以便它们可以被分解,允许定义一个适用于任意长度元组的compare的函数?

毕竟,编译器确实支持任意长度的元组。

4 个答案:

答案 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元组,​​那么你可以无限扩展它,它将自动派生出正确的EqOrd个实例。

答案 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的列表系统也有语法糖。