Haskell ad hoc polymorphism

I am trying to figure out ad-hoc polymorphism in haskell, which has the same function, provides different behavior for different types of arguments.

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 Bool parameter type must be sufficient to eliminate ambiguity.

See also: Is this the best way to achieve this behavior? (without renaming functions to fooBool

and fooDouble

)

+3


source to share


2 answers


The problem you are having is that overloading is defined by all types of a class, including those that only show up as return types. You can have instances of both for MyClass Bool Int

and MyClass Bool String

, and it will be able to resolve errors based on what type is expected.

One of the main proposals for designing with Haskell classes is the "open world guess". Instances of a Haskell type are implicitly global: a particular type (or a sequence of types in this case) can have only one instance in the entire program, which is implicitly exported to all modules using that type.

This makes it easy to get new instances of a class without implementing it, so the Hecell typechecker assumes that instances can exist for any valid combination of types. In your case, this means that while it MyClass Bool Int

is the only instance using it Bool

, it is still ambiguous with other possible instances MyClass Bool b

.

After adding an annotation to the type of the entire expression, it ceases to be ambiguous, since both a

and are corrected b

.



To get the expected behavior, you can use FunctionalDependencies

. This allows you to indicate that there is only one possible, b

for any given a

, that will allow GHC to infer the correct type. It will look something like this:

class MyClass a b | a -> b where

      

Of course, this intentionally throws away some flexibility: now you cannot have instances for MyClass Bool Int

and MyClass Bool String

.

+10


source


Tikhon Elvis developed the problem and suggested using functional dependencies, but there is an alternative: family types . Your code will be

{-# 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 are using related type synonyms here. I love these explicit type level functions, but if you don't, it's okay to use fundeps because the differences are pretty subtle.

+7


source







All Articles