为什么可以';我不能比较Haskell中任意长度的元组吗?
我知道有预定义的为什么可以';我不能比较Haskell中任意长度的元组吗?,haskell,typeclass,Haskell,Typeclass,我知道有预定义的Eq实例 为什么元组不被定义为某种递归数据类型,这样它们就可以被分解,从而允许定义一个用于处理任意长度元组的compare函数 毕竟,编译器确实支持任意长度的元组。一种类型的compare是,这表明两个输入必须是相同的类型。根据定义,不同算术的元组是不同的类型 但是,您可以通过以下两种方法解决问题。您始终可以根据二进制元组重写任何n元组。例如,给定以下4元组: (1, 'A', "Hello", 20) 您可以将其改写为: (1, ('A', ("Hello", (20, ()
Eq
实例
为什么元组不被定义为某种递归数据类型,这样它们就可以被分解,从而允许定义一个用于处理任意长度元组的compare
函数
毕竟,编译器确实支持任意长度的元组。一种类型的
compare
是,这表明两个输入必须是相同的类型。根据定义,不同算术的元组是不同的类型
但是,您可以通过以下两种方法解决问题。您始终可以根据二进制元组重写任何n元组。例如,给定以下4元组:
(1, 'A', "Hello", 20)
您可以将其改写为:
(1, ('A', ("Hello", (20, ()))))
可以将其视为一个列表,其中
(,)
扮演(:)
(即“cons”)的角色,()
扮演[]
(即“nil”)的角色。使用这个技巧,只要你用“二进制元组列表”来表示你的n元组,那么你就可以无限期地扩展它,它将自动派生出正确的Eq
和Ord
实例。你可能会问自己,这个广义比较函数的类型是什么。首先,我们需要一种对组件类型进行编码的方法:
data Tuple ??? = Nil | Cons a (Tuple ???)
我们真的没有什么可以用问号代替的。结论是,常规ADT是不够的,因此我们需要我们的第一语言扩展GADTs:
data Tuple :: ??? -> * where
Nil :: Tuple ???
Cons :: a -> Tuple ??? -> Tuple ???
然而,我们最终还是有了问号。填补这些漏洞还需要另外两个扩展名DataTypes和TypeOperator:
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
似乎有道理,但轰!我们又接到一个投诉。现在编译器需要flexibleContext,因为上下文中有一个不完全多态的类型,Tuple as
总共有五种类型的系统扩展,其中三种只是为了表示元组类型,它们在GHC7.4之前是不存在的。另外两个是需要比较的。当然有回报。我们得到了一个非常强大的元组类型,但由于所有这些扩展,我们显然无法将这样的元组类型放入基本库。我只是想补充一下ertes的答案,您不需要一个扩展就可以做到这一点。以下代码应该与Haskell 98以及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
你也可以直接推导出等式。当然,使用类型运算符看起来有点酷,但haskell的列表系统也有语法糖。那么你到底是如何看待“分解”元组的呢?@NikitaVolkov喜欢列表上的
头和尾。为什么元组不能有这样的函数呢?因为列表是同质的,也就是说,它们的所有项都是同一类型的。另一方面,元组包含不同的类型,这就是为什么如果它们的项不同,你甚至不能比较相同类型的元组,因为compare
需要相同的类型,也就是说,你不能将(Int,Bool)
与(Int,Int)
@NikitaVolkov具有相同类型的元组具有相同的长度和相同的元素类型,因此,可以比较这些元组,但只有长度不超过15的实现。我想知道的是,为什么一个函数不能工作任何长度。@DanielWagner为什么需要长度超过8个字符的文件名?只需使用更多文件夹。我试图理解为什么Haskell有这些看似人为的限制,仅此而已。我知道我可以做到,但问题是:为什么Haskell在内部不这样工作?这正是我所说的“递归数据类型”的意思。n元组最初的定义是为了避免递归嵌套的二进制元组所带来的无谓提升。不幸的是,这是一个不明智的决定,因为我们无法回头修复它。如果您以这种方式追溯定义n元组,您将破坏许多n元组的类型类实例,这些实例现在将与二进制元组的实例重叠。免费提升
到底意味着什么?所以底线是:这是因为遗留的原因?需要注意的一点是嵌套元组有更多的地方可以放在底部。未赋值元组只能对整个值或特定元素具有底部。嵌套元组的任何“尾部”都可以有底部。这不是一个不明智的决定。n元组与异构列表不同。我们两者都有,我们可以两者都用,但是没有理由你在这个答案中给出的公式比另一个更好。对不起,这不是我想问的。我不知道如何进一步澄清。IIUC,@Odin问的是比较相同(任意元组)类型的两个元组的问题。@Conal在他的问题下看到了Odin的评论:“由于所有这些扩展,我们显然不能将这样的元组类型放入基库。”GADT、数据种类、类型运算符、FlexibleInstances和FlexibleContext。也许有一天(很快?你会梦想)它们会被集成到Haskell的定义中。为了保持元组的严格行为,我想你应该使用