为什么类型类而不仅仅是模式匹配?

时间:2012-02-02 03:56:39

标签: haskell pattern-matching typeclass

这是一个哲学问题,但我希望通过官方文件或“上帝之词”(读作:SPJ)来回答。是否有一个特定的原因,Haskell委员会选择以类型的形式要求显式接口而不是基于模式匹配的更统一的解决方案?

例如,请Eq

class Eq a where
    (==), (/=) :: a -> a -> Bool
    x == y = not $ x /= y
    x /= y = not $ x == y

instance Eq Int where
    (==) = internalIntEq

为什么我们不能做这样的事情(忍受伪Haskell):

(==), (/=) :: a -> a -> Bool
default x == y = not $ x /= y             -- 1
default x /= y = not $ x == y

(Int a) == (Int b) = a `internalIntEq` b  -- 2

也就是说,如果Haskell允许普通数据类型的模式匹配,那么:

  • 程序员可以创建ad-hoc类,即instance将是隐式的(2)

  • 仍可以推断和静态匹配类型(SupportsEqualsEquals a => ...

  • 默认实施将“免费”

  • 可以轻松扩展课程而不会破坏任何内容

需要一种方法来指定默认模式(1),尽管在其他模式之前声明,但始终与最后匹配。这些假设特征中的任何一个都与Haskell中固有的东西相冲突吗?正确推断类型会变得困难或不可能吗?这似乎是一个强大的功能,它与Haskell的其余部分很好地融合在一起,所以我认为这是一个很好的理由我们不做那样的方式。这种ad-hoc多态的机制是否只是 ad-hoc?

2 个答案:

答案 0 :(得分:12)

  

ad-hoc多态的这种机制是否过于简单?

这个问题只是与Philip Wadler和Steve Blott的1988年论文How to make ad-hoc polymorphism less ad-hoc有关,他们提出了类型类的概念。 Wadler可能就此而言是“神的话语”。

我在建议的“任何Haskell数据类型的模式匹配”技术中看到了一些问题。

模式匹配技术不足以定义多态常量,例如mempty :: Monoid a => a

模式匹配技术仍然会回归到类型类,除了更糟糕的方式。键入类分类类型(参见图)。但模式匹配技术使其相当模糊。您究竟应该如何指定函数foobar是“同一”类的一部分? 类型类约束将变得完全不可读如果您必须为您使用的每个单态多态函数添加新的约束

模式匹配技术将新语法引入Haskell,使语言规范复杂化default关键字看起来并不那么糟糕,但“类型”上的模式匹配是新的且令人困惑。

“普通数据类型”的模式匹配失败了无点样式。我们(==) = intEq取代(Int a) == (Int b) = intEq a b而不是a -> a -> Foo;这种人工模式匹配可防止eta减少

最后,完全改变了我们对类型签名的理解。 a目前是保证,无法检查输入。除了两个输入属于同一类型之外,没有任何关于[a] -> [a]输入的假设。 {{1}}再次表示无法以任何有意义的方式检查列表的元素,为您提供Theorems for Free(另一张Wadler文件)。

可能有办法解决这些问题,但我的总体印象是Type类已经以优雅的方式解决了这个问题,并且建议的模式匹配技术不会带来任何好处,同时会导致一些问题。

答案 1 :(得分:4)

我不知道上帝的话语,但这里有一些论点。

不再在同一模块中定义一个函数。你现在可以写

(==) = internalIntEq
(==) = internalFloatEq

这使代码的可读性降低。有一个名为“TypeBasedNameResolution”的提议,它做了类似的事情,但重要的事实是这种类型分支只对来自不同模块的(==)进行。

让编译器添加标识符是不好的做法。在您的情况下,您自动创建类型类SupportsEqualsEquals。一个新用户可能会问,“它来自哪里”,并且没有相应的来源定义它。

跳过编写实例签名并没有像你想象的那样多给你。您可以通过以下方式获取必要的参数ghci :t internalIntEq我想这可能更方便,但我宁愿有一个工具我可以问“Eq == internalIntEq的实例类型是什么。”。

更高级的课程功能尚不清楚。你在哪里放置相关的类型和功能依赖?这些对我来说非常重要!!

您的默认设置使模块化编译更加困难。你不会免费获得可扩展的课程。考虑,

f :: Supports[==] a => a -> a -> Bool
f = (/=)

据我了解,这将编译成

f :: Instance (Supports[==]) a -> a -> a -> Bool
f eq_inst x y = not (x eq_inst.== y)

现在,如果我为特定类型/=提供新的a_0个实例,并将一些x :: a_0提供给f,那么

f x x = not (x == x)
-- computation you really want: f x x = x /= x, using the new /= instance for a_0

您可能会问,“何时会将f限制为Supports[==]而不是Supports[/=]?”但是,上下文可以来自不仅仅是功能的签名;它们可以来自高阶函数等等。

希望它有所帮助。