我试图了解haskell中的ad-hoc多态性,即具有相同的函数为不同的参数类型提供不同的行为。
但是,以下测试代码编译
{-# 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
)
答案 0 :(得分:10)
您遇到的问题是,重载是由类中所有类型确定的 - 包括仅作为返回类型出现的类型。您可以拥有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
的实例。
答案 1 :(得分: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
我们在这里使用associated type synonyms。我喜欢这些显式类型级别的函数,但是如果你不喜欢,可以使用fundeps,因为differences相当微妙。