Haskell 即席多态性

Haskell ad hoc polymorphism

我正试图了解 haskell 中的临时多态性,即具有相同的函数为不同的参数类型提供不同的行为。

但是当下面的测试代码编译时

{-# LANGUAGE MultiParamTypeClasses #-}

class MyClass a b where
    foo :: a -> b

instance MyClass Bool Int where
    foo True = 0
    foo False = 1

instance MyClass Double Double where
    foo x = -x

如果我尝试使用类似

的方式来调用它
foo True

ghci 对我大吼:

No instance for (MyClass Bool b0) arising from a use of `foo'
The type variable `b0' is ambiguous
Possible fix: add a type signature that fixes these type variable(s)
Note: there is a potential instance available:
  instance MyClass Bool Int -- Defined at test.hs:6:10
Possible fix: add an instance declaration for (MyClass Bool b0)
In the expression: foo True
In an equation for `it': it = foo True

但是,如果我指定 return 类型,它会起作用:

foo True :: Int -- gives 0

为什么需要这个? Bool的参数类型应该足以解决歧义。

另外:这是实现类似行为的 "best" 方法吗? (不将函数重命名为 fooBoolfooDouble

您面临的问题是重载是由 class 中的 所有 类型决定的——包括仅显示为 [=50= 的类型] 类型。您可以同时拥有 MyClass Bool IntMyClass Bool String 的实例,并且它能够根据预期的类型来消除歧义。

Haskell 类型classes 的核心设计权衡之一是 "open world assumption"。 Haskell 类型实例是隐式全局的:特定类型(或类型序列,在这种情况下)在整个程序中只能有 一个 实例,它被隐式导出到所有使用该类型的模块。

这使得在没有意识到的情况下很容易获得某些 class 的新实例,因此 Haskell 类型检查器假定实例 可能 可能存在于类型的任何有效组合。在您的情况下,这意味着虽然 MyClass Bool Int 是唯一使用 Bool 的实例,但它与其他可能的 MyClass Bool b 实例仍然不明确。

一旦你为整个表达式的类型添加了注释,它就不再是模棱两可的了,因为 ab 都是固定的。

要获得您期望的行为,您可以使用 FunctionalDependencies。这些允许您指定对于任何给定的 a 只有 一个 可能 b,这将使 GHC 正确推断类型。它看起来像这样:

class MyClass a b | a -> b where

当然,这确实有意放弃了一些灵活性:现在您不能同时拥有 MyClass Bool IntMyClass Bool String 的实例。

Tikhon Jelvis详细阐述了问题并提出使用函数依赖,但还有一个替代方案:type families。您的代码变为

{-# LANGUAGE TypeFamilies #-}

class MyClass a where
    type R a
    foo :: a -> R a

instance MyClass Bool where
    type R Bool = Int
    foo True  = 0
    foo False = 1

instance MyClass Double where
    type R Double = Double
    foo x = -x

我们用的associated type synonyms here. I like these explicit type level functions, but if you don't, it's OK to use fundeps, because differences比较隐蔽