Haskell 即席多态性

Posted

技术标签:

【中文标题】Haskell 即席多态性【英文标题】:Haskell ad hoc polymorphism 【发布时间】:2015-07-14 08:53:16 【问题描述】:

我试图了解 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

【问题讨论】:

老实说:我不会从 MultiParamTypeClasses 开始,但在你的情况下,你可能也想看看 Functional dependencies ;) (如果不清楚 - 请点击链接 -那里很好地解释了问题 - 包括一个可能的解决方案 - 说扩展) 对于这个任务我会使用type families而不是函数依赖。 @user3237465 将其添加为答案 【参考方案1】:

您面临的问题是重载是由类中的所有类型决定的——包括那些只作为返回类型出现的类型。您可以同时拥有 MyClass Bool IntMyClass Bool String 的实例,并且它能够根据预期的类型来消除歧义。

Haskell 类型类的核心设计权衡之一是“开放世界假设”。 Haskell 类型实例是隐式全局的:特定类型(或类型序列,在这种情况下)在整个程序中只能有 一个 实例,该实例隐式导出到使用该类型的所有模块。

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

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

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

class MyClass a b | a -> b where

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

【讨论】:

谢谢,这为我解决了一些问题。然而,即使使用 FunctionalDependencies,调用 foo 1.73 也会失败。你也能解释一下吗? 如果会失败,因为1.73 本身不是Double,而是更通用的Fractional - 所以你必须做foo (1.73 :: Double) 或修改你的foo 实例为Fractional a @wonce:这里的问题是1.73 本身是多态的:它可以是任何Fractional 类型,例如FloatDoubleRational。所以,再一次,使用哪个实例可能存在歧义。【参考方案2】:

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。我喜欢这些显式类型级别的函数,但如果你不喜欢,也可以使用fundeps,因为differences 相当微妙。

【讨论】:

以上是关于Haskell 即席多态性的主要内容,如果未能解决你的问题,请参考以下文章

在 Haskell 中存储多态回调

haskell中的多态函数列表?

Haskell类具有多态数据定义的实例

Haskell语言的其他特性——洪峰老师讲创客道(三十六)

论Haskell的复用性(上篇)——多态

Haskell 演示 OOP 设计模式的等价物 [重复]