Haskell ad hoc多态性

won*_*nce 4 polymorphism haskell overloading typeclass

我试图了解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
Run Code Online (Sandbox Code Playgroud)

如果我尝试使用类似的东西来调用它

foo True
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

但是,如果我指定返回类型,它的工作原理如下:

foo True :: Int -- gives 0
Run Code Online (Sandbox Code Playgroud)

为什么需要这个?Bool的参数类型应该足以解决歧义.

另外:这是实现类似行为的"最佳"方式吗?(不将函数重命名为fooBoolfooDouble)

Tik*_*vis 10

您遇到的问题是重载由类中的所有类型决定- 包括仅作为返回类型出现的类型.你可以有实例两个MyClass Bool IntMyClass Bool String,这将是能够基于预计什么类型的歧义.

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

这使得它很容易得到一些类的新实例没有意识到这一点,所以哈斯克尔typechecker假设情况可能可能存在的任何类型的有效组合.在您的情况下,这意味着虽然MyClass Bool Int是唯一使用的实例Bool,但它仍然与其他可能的MyClass Bool b实例不明确.

一旦你添加注释整个表达式的类型,它不再是模糊的,因为这两个ab是固定的.

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

class MyClass a b | a -> b where
Run Code Online (Sandbox Code Playgroud)

当然,这不故意扔了一定的灵活性:现在你不能有两个实例MyClass Bool IntMyClass Bool String.

  • if会失败,因为`1.73`本身不是'Double`而是更通用的`Fractional` - 所以你必须做`foo(1.73 :: Double)`或修改`foo`实例为`Fractional a` (3认同)

use*_*465 7

Tikhon Jelvis详细阐述了这个问题,并建议使用功能依赖,但还有另一种选择:类型系列.你的代码变成了

{-# 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
Run Code Online (Sandbox Code Playgroud)

我们在这里使用关联类型同义词 我喜欢这些显式类型级别的函数,但如果你不喜欢,可以使用fundeps,因为差异相当微妙.