Haskell:对Num类型进行模式匹配

5

为什么Haskell不能对Num类型执行模式匹配,而不需要我们指定Eq作为一个类型类呢?

例如:

h :: Num a => a -> a
h 0 = -1
h x = x + 1

当编译此函数时,ghci报错:

    * Could not deduce (Eq a) arising from the literal `0'
      from the context: Num a
        bound by the type signature for:
                   h :: forall a. Num a => a -> a
        at functions.hs:9:1-20
      Possible fix:
        add (Eq a) to the context of
          the type signature for:
            h :: forall a. Num a => a -> a
    * In the pattern: 0
      In an equation for `h': h 0 = - 1
   |
10 | h 0 = -1
   |   ^

将函数定义更改为以下内容即可编译并运行完美:

h :: (Num a, Eq a) => a -> a
h 0 = -1
h x = x + 1

*Main> h 0
-1
*Main>

1
它需要能够确定值何时为==0以检查您的模式,因为0是“Num a => a”,而不是数据构造函数。 - Ry-
1个回答

6

Haskell 2010报告中,标题为模式匹配的非正式语义的部分:

将数字、字符或字符串文字模式k与值v匹配成功,如果v==k

因此,当您使用文字(如0)作为模式时,其含义取决于==Eq类的一种方法)。

例如,您的函数h

h 0 = -1
h x = x + 1

可以重写为

h x | x == 0 = -1
h x          = x + 1

您(隐式)使用了==方法,因此需要一个Eq约束。

这里有两个关于Haskell与许多其他语言不同的重要观察:

  1. 并非所有类型都定义了相等的概念。只有当xy的类型具有Eq实例时,才能询问x == y
  2. 数字类型集合未固定。 数字文字可以采用任何具有Num实例的类型。您可以定义自己的类型并使其成为Num的实例,它不一定也必须具有Eq的实例。因此,并非所有“数字”都可以进行比较。
因此,你的函数的上下文不能仅仅是“必须是一个数字”。更具体地说,上下文必须是“必须是带有等式测试的数字”,以确保有一种方法来检查是否等于<0>以执行模式匹配。

现在我很好奇:是否存在任何没有等式的Num实例?还是他们只是将该类型类作为可选项保留,以防万一?这样做是为了让您可以在不同的等价关系之间进行选择:1/2 == 2/4,或者不选择,Expr 1 + Expr 1 == Expr 2,或者不选择? - Davislor
2
@Davislor 我看到的被引用了很多次的例子是“你可以为函数创建一个合理的实例”(例如https://wiki.haskell.org/Num_instance_for_functions),而函数*不能*有一个好的`Eq`实例。但通常会附带一个警告:“你可能不应该使用那个`Num`实例”。另一个想到的可能性是表示带有变量的表达式的数据类型。 Num中的操作与Eq中的操作实际上没有任何关系,因此说当你没有提供另一个时,你不能拥有其中一个,现在比以前更少用。 - Ben
但是毫无疑问,Haskell可以表示关于Churchnumerals的相等性吧?所有这些计算机科学教授都不可能忽略这一点。 - Davislor
更严肃的是,由于 GHC 可以传递函数指针,我相信一定有一种方法可以强制它实现某种形式的身份。但这已经偏离了主题。感谢提供这些绝妙的例子! - Davislor
2
@Davislor GHC确实允许您查看运行时内部;请查看此答案中定义的unsafeAddr函数。但是,即使您通过它们所表示的内存地址比较函数,它仍然不是一个好的相等性测试,因为它会在许多情况下产生False,其中您正在比较两个不同的表示实际上是相同函数的内容。如果您想要“引用相等性”,正确的方法是使用IORef(它确实具有Eq实例)或类似的东西。 - Chris Martin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接