0
votes

Apologies, I dabble in Haskell in a minor way, once ever 2 years, and then I can't get my head around it.

Its best to just do the example.

> {-# LANGUAGE MultiParamTypeClasses #-}
> {-# LANGUAGE ScopedTypeVariables #-}
> {-# LANGUAGE AllowAmbiguousTypes #-}
> {-# LANGUAGE FlexibleInstances #-}
> {-# LANGUAGE FlexibleContexts #-}


> class Foo a b where
>     foo :: a -> b
>     
> class Bar a b where
>     bar :: a -> b

> data A a = A a
> data B b = B b

> instance Foo (A a) a where
>   foo (A a) = a

> instance Bar (B b) b where
>   bar (B b) = b

so, what I THINK I'm doing is create to data types and then defining two functions based on the the membership of the datatype in the class....

so this works....

> f1 x = foo x
> f2 x = bar x

> x1 :: String
> x1 = f1 $ A "1"
> x2 :: String
> x2 = f2 $ B "1"

simple...

worry #1...if I remove the type declaration of x1 and x2, ghc complains

• Ambiguous type variable ‘b1’ arising from a use of ‘f1’
      prevents the constraint ‘(Foo (A [Char]) b1)’ from being solved.
      Relevant bindings include x1 :: b1 (bound at catdog.lhs:27:3)
      Probable fix: use a type annotation to specify what ‘b1’ should be.
      These potential instance exist:
        instance Foo (A a) a -- Defined at catdog.lhs:17:12
    • In the expression: f1 $ A "1"
      In an equation for ‘x1’: x1 = f1 $ A "1"
   |
27 | > x1 = f1 $ A "1"    |        ^^^^^^^^^^

which is a worry, isnt it obvious?...and this sort of thing will happen again.....

if I write

> f x = bar (foo x)

to me a perfectly reasonable thing...ghc agrees!

I ask it for the type...I get

f :: (Bar a1 b, Foo a2 a1) => a2 -> b

I can buy that.

like a good programmer I paste the type in

> f :: (Bar a1 b, Foo a2 a1) => a2 -> b
> f x = bar (foo x)

and "BOOM"...

• Could not deduce (Foo a2 a0) arising from a use of ‘foo’
      from the context: (Bar a1 b, Foo a2 a1)
        bound by the type signature for:
                   f :: forall a1 b a2. (Bar a1 b, Foo a2 a1) => a2 -> b
        at catdog.lhs:32:3-39
      The type variable ‘a0’ is ambiguous
      Relevant bindings include
        x :: a2 (bound at catdog.lhs:33:5)
        f :: a2 -> b (bound at catdog.lhs:33:3)
      These potential instance exist:
        instance Foo (A a) a -- Defined at catdog.lhs:17:12
    • In the first argument of ‘bar’, namely ‘(foo x)’
      In the expression: bar (foo x)
      In an equation for ‘f’: f x = bar (foo x)
   |
33 | > f x = bar (foo x)    |              ^^^^^

so ghc is telling me the type it inferred without a type declaration, its now not sure about!

now...there is usually one cog in my head that gets turned backwards by using scala or f# or some other OO style type system, that I have to turn the other way around....am I going mad?

1
I'm certainly no expert on any of this, but for worry #1 what I think is happening is that, when you omit the type signature, GHC is seeing that it needs to have an instance Foo (A String) b and can't figure out what b is. I guess it's not capable of seeing that your script has only one such instance in this case (with b being String) - if you had more than one then it would certainly have just cause to complain about an ambiguity. - Robin Zigmond
as for the second one, this is even further away from my comfort zone, but I would guess that GHC has no idea what a1 is, and can't read the constraints as saying "I want there to be some type a1 for which the constraints Bar a1 b and Foo a2 a1 hold simultaneously". Not sure how you could set this up (or if it's possible at all) so I will leave that to others more knowledgeable. - Robin Zigmond

1 Answers

2
votes

Let's look at your example again:

> class Foo a b where
>     foo :: a -> b

The problem here is that a given a could correspond to multiple given bs; you could have, for example, both instance Foo Int Char and instance Foo Int Bool. This causes the problems with type inference you've been seeing.

> f1 x = foo x
> f2 x = bar x

> x1 :: String
> x1 = f1 $ A "1"
> x2 :: String
> x2 = f2 $ B "1"

Here, for example, you've specified both a and b, so GHC knows you're talking about instance (A String) String. When you remove the signatures, GHC doesn't know what b to use. You haven't defined any other instances, and you don't intend to, but GHC doesn't know that.

So how do we fix this problem? We use {-# LANGUAGE FunctionalDependencies #-}:

> class Foo a b | a -> b where
>     foo :: a -> b

Basically, this tells GHC that for any given a, there is only one corresponding b. This should resolve your problems (although admittedly I haven't tested this yet). For more on functional dependencies, see this SO answer, which also addresses your bar (foo x) problem.