Haskell的特设多态性

5

我正在尝试理解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

然而,如果我指定返回类型,它就能正常工作:
foo True :: Int -- gives 0

这是为什么需要呢?Bool类型的参数应该足以解决模棱两可的情况。

另外: 这是否是实现类似行为的“最佳”方式?(不重命名函数为fooBoolfooDouble


3
说实话,我不会从MultiParamTypeClasses开始,但在你的情况下,你可能也想看一下Functional dependencies ;)(如果不清楚,请点击链接 - 问题在那里得到了很好的解释,包括可能的解决方案 - 该扩展名) - Random Dev
对于这个任务,我会使用type families而不是函数依赖。 - effectfully
@user3237465 把它作为答案添加 - Random Dev
2个回答

11
您面临的问题是重载是由类中的所有类型(包括仅出现为返回类型的类型)确定的。您可以同时拥有 MyClass Bool Int MyClass Bool String 的实例,并且它能够根据期望的类型进行消歧义。
Haskell类型类的核心设计权衡之一是“开放世界假设”。Haskell类型实例隐含地是全局的:特定类型(或者在这种情况下,一系列类型)只能在整个程序中具有一个实例,该实例隐式导出到使用该类型的所有模块中。
这使得很容易获得某些类的新实例而不会意识到它,因此Haskell类型检查器假定实例可能存在于任何有效的类型组合中。在您的情况下,这意味着虽然 MyClass Bool Int 是唯一使用 Bool 的实例,但它仍与其他可能的 MyClass Bool b 实例存在歧义。
一旦为整个表达式添加类型注释,它就不再是模棱两可的,因为 a b 都被固定了。
为了获得您期望的行为,您可以使用FunctionalDependencies。这使您可以指定对于任何给定的a,只有一个可能的b,这将让GHC正确地推断类型。它可能看起来像这样:
class MyClass a b | a -> b where

当然,这样做会有意地放弃一些灵活性:现在你不能同时拥有 MyClass Bool IntMyClass Bool String 的实例。

谢谢,这为我解决了一些问题。然而,即使使用FunctionalDependencies,调用“foo 1.73”也会失败。你能解释一下吗? - wonce
3
如果只输入1.73,会失败,因为它本身不是Double类型,而是更通用的Fractional类型。因此,您需要使用foo (1.73 :: Double)或修改foo的实例,使其适用于Fractional a类型。 - Random Dev
@wonce:这里的问题在于1.73本身是多态的:它可以是任何Fractional类型,如FloatDoubleRational。因此,再次出现了可能使用哪个实例的歧义。 - Tikhon Jelvis

7

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

我们在这里使用关联类型同义词。我喜欢这些明确的类型级函数,但如果您不喜欢,使用功能依赖也可以,因为差异相当微妙。

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