Haskell ad hoc polymorphism

ⅰ亾dé卋堺 提交于 2019-12-10 07:31:04

问题


I'm trying to get my head around ad-hoc polymorphism in haskell, that is having the same function provide different behaviour for different argument types.

But while the following test code compiles

{-# 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

if I try to call it using something like

foo True

ghci yells at me:

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

However, if I specify the return type, it works:

foo True :: Int -- gives 0

Why is this needed? The parameter type of Bool should be enough to resolve the ambiguity.

Also: Is this the "best" way to achieve similar behaviour? (without renaming the functions to fooBool and fooDouble)


回答1:


The problem you're facing is that the overloading is determined by all of the types in the class—including ones that only appear as return types. You could have instances for both MyClass Bool Int and MyClass Bool String, and it would be able to disambiguate based on what type is expected.

One of the core design tradeoffs with Haskell typeclasses is the "open world assumption". Haskell type instances are implicitly global: a specific type (or sequence of types, in this case) can only have one instance in the whole program, which is implicitly exported to all the modules using that type.

This makes it really easy to get new instances of some class without realizing it, so the Haskell typechecker assumes that instances could possibly exist for any valid combination of types. In your case, this means that while MyClass Bool Int is the only instance using Bool, it's still ambiguous with other possible MyClass Bool b instances.

Once you add an annotation for the type of the whole expression, it stops being ambiguous because both a and b are fixed.

To get the behavior you expect, you can use FunctionalDependencies. These allow you to specify that there is only one possible b for any given a, which will let GHC infer the type correctly. It would look something like this:

class MyClass a b | a -> b where

Of course, this does intentionally throw out some flexibility: now you can't have instances for both MyClass Bool Int and MyClass Bool String.




回答2:


Tikhon Jelvis elaborated the problem and proposed to use functional dependencies, but there is an alternative: type families. Your code becomes

{-# 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

We use associated type synonyms here. I like these explicit type level functions, but if you don't, it's OK to use fundeps, because differences are rather subtle.



来源:https://stackoverflow.com/questions/30039135/haskell-ad-hoc-polymorphism

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!