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" 方法吗? (不将函数重命名为 fooBool
和 fooDouble
)
您面临的问题是重载是由 class 中的 所有 类型决定的——包括仅显示为 [=50= 的类型] 类型。您可以同时拥有 MyClass Bool Int
和 MyClass Bool String
的实例,并且它能够根据预期的类型来消除歧义。
Haskell 类型classes 的核心设计权衡之一是 "open world assumption"。 Haskell 类型实例是隐式全局的:特定类型(或类型序列,在这种情况下)在整个程序中只能有 一个 实例,它被隐式导出到所有使用该类型的模块。
这使得在没有意识到的情况下很容易获得某些 class 的新实例,因此 Haskell 类型检查器假定实例 可能 可能存在于类型的任何有效组合。在您的情况下,这意味着虽然 MyClass Bool Int
是唯一使用 Bool
的实例,但它与其他可能的 MyClass Bool b
实例仍然不明确。
一旦你为整个表达式的类型添加了注释,它就不再是模棱两可的了,因为 a
和 b
都是固定的。
要获得您期望的行为,您可以使用 FunctionalDependencies
。这些允许您指定对于任何给定的 a
只有 一个 可能 b
,这将使 GHC 正确推断类型。它看起来像这样:
class MyClass a b | a -> b where
当然,这确实有意放弃了一些灵活性:现在您不能同时拥有 MyClass Bool Int
和 MyClass 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比较隐蔽
我正试图了解 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" 方法吗? (不将函数重命名为 fooBool
和 fooDouble
)
您面临的问题是重载是由 class 中的 所有 类型决定的——包括仅显示为 [=50= 的类型] 类型。您可以同时拥有 MyClass Bool Int
和 MyClass Bool String
的实例,并且它能够根据预期的类型来消除歧义。
Haskell 类型classes 的核心设计权衡之一是 "open world assumption"。 Haskell 类型实例是隐式全局的:特定类型(或类型序列,在这种情况下)在整个程序中只能有 一个 实例,它被隐式导出到所有使用该类型的模块。
这使得在没有意识到的情况下很容易获得某些 class 的新实例,因此 Haskell 类型检查器假定实例 可能 可能存在于类型的任何有效组合。在您的情况下,这意味着虽然 MyClass Bool Int
是唯一使用 Bool
的实例,但它与其他可能的 MyClass Bool b
实例仍然不明确。
一旦你为整个表达式的类型添加了注释,它就不再是模棱两可的了,因为 a
和 b
都是固定的。
要获得您期望的行为,您可以使用 FunctionalDependencies
。这些允许您指定对于任何给定的 a
只有 一个 可能 b
,这将使 GHC 正确推断类型。它看起来像这样:
class MyClass a b | a -> b where
当然,这确实有意放弃了一些灵活性:现在您不能同时拥有 MyClass Bool Int
和 MyClass 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比较隐蔽