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 的参数类型应该足以解决歧义。
另外:这是实现类似行为的“最佳”方式吗? (不将函数重命名为fooBool
和fooDouble
)
【问题讨论】:
老实说:我不会从MultiParamTypeClasses
开始,但在你的情况下,你可能也想看看 Functional dependencies
;) (如果不清楚 - 请点击链接 -那里很好地解释了问题 - 包括一个可能的解决方案 - 说扩展)
对于这个任务我会使用type families而不是函数依赖。
@user3237465 将其添加为答案
【参考方案1】:
您面临的问题是重载是由类中的所有类型决定的——包括那些只作为返回类型出现的类型。您可以同时拥有 MyClass Bool Int
和 MyClass Bool String
的实例,并且它能够根据预期的类型来消除歧义。
Haskell 类型类的核心设计权衡之一是“开放世界假设”。 Haskell 类型实例是隐式全局的:特定类型(或类型序列,在这种情况下)在整个程序中只能有 一个 实例,该实例隐式导出到使用该类型的所有模块。
这使得在不知不觉中获取某个类的新实例变得非常容易,因此 Haskell 类型检查器假定实例可能可能存在于任何有效的类型组合中。在您的情况下,这意味着虽然 MyClass Bool Int
是唯一使用 Bool
的实例,但它与其他可能的 MyClass Bool b
实例仍然不明确。
为整个表达式的类型添加注释后,它就不再模棱两可了,因为a
和b
都是固定的。
要获得您期望的行为,您可以使用FunctionalDependencies
。这些允许您指定对于任何给定的b
,只有 一个 可能的a
,这将使 GHC 正确推断类型。它看起来像这样:
class MyClass a b | a -> b where
当然,这确实会有意放弃一些灵活性:现在您不能同时拥有 MyClass Bool Int
和 MyClass Bool String
的实例。
【讨论】:
谢谢,这为我解决了一些问题。然而,即使使用 FunctionalDependencies,调用foo 1.73
也会失败。你也能解释一下吗?
如果会失败,因为1.73
本身不是Double
,而是更通用的Fractional
- 所以你必须做foo (1.73 :: Double)
或修改你的foo
实例为Fractional a
@wonce:这里的问题是1.73
本身是多态的:它可以是任何Fractional
类型,例如Float
、Double
或Rational
。所以,再一次,使用哪个实例可能存在歧义。【参考方案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 即席多态性的主要内容,如果未能解决你的问题,请参考以下文章