Solved: Typing problems with polymorphic recursion and typeclasses

oleg@pobox.com oleg@pobox.com
Tue, 6 May 2003 20:25:03 -0700 (PDT)


Martin SULZMANN wrote:

> Here's a simplified version. 

> h :: (Show ha) => t -> ha
> h b = g (h b)
>  > > g:: (Show ga) => ga -> gb
>  > > g = undefined

In an attempt to discover the type signature of h, let us lift it:

> g:: (Show ga,Show gb) => ga -> gb
> g = undefined

> class (Show ha) => H t ha where
>     h:: t->ha
>     h b = g (h b)


gives us an error:

    Could not deduce (H t ha1) from the context (H t ha)
    Probable fix:
        Add (H t ha1) to the class or instance method `h'
    arising from use of `h' at /tmp/e.hs:10
    In the first argument of `g', namely `(h b)'
    In the definition of `h': g (h b)

However, if we change the class H into

> class (Show ha) => H t ha | t->ha where
>     h:: t->ha
>     h b = g (h b)

everything typechecks. It seems very hard for me to avoid the thought
that the type of the original h should be
	forall t ha. (Show ha) => t -> ha | t -> ha

or, to re-write it into valid Haskell,
	forall t ha. (H t ha) => t -> ha

That might answer my previous question...

Regarding your observation:

> g:: (Show ga) => ga -> gb
> g = undefined
> h :: (Show ha) => t -> ha
> h b = g (h b)

> Same problem occurs. Though this is strange.

> Consider type inference for 

> h b = g (h b)

> We generate the following constraints:

> th = tb -> ga, Show ga  -- results from g (h b)
> th = tb -> gb           -- taking into account h b = g (h b)

> In case of 

> h :: (Show ha) => t -> ha
> h b = g (h b)

> we additionally generate

> th = t -> ha, Show ha

> Overall, we find

>  th = tb -> ga, Show ga 
>  th = tb -> gb,
>  th = t -> ha, Show ha

> as my above calculation shows there shouldn't be a problem in the
> first place.

I think I can see the problem: if we have a function
	foo:: t1 -> t2
then GHC definitely knows that foo relates t1 and t2 by a functional
dependency -- indeed, foo is a function.

However, when presented with a type signature
	bar:: (C t3) => t3 -> t4
the compiler doesn't know if bar is a regular function, or if it is a
method in the class C. In either case, the signature is just the
same. If bar is a bona fide function, then t3 and t4 are related by a
functional dependency. If bar is a method of C, things are not so
clear. Well, they are clear for the single-parameter class C without
overlapping instances (which GHC actually allows). However, for
	baz:: (D t5 t6 t7) => t6 -> t8
there could conceivably be instances of baz which relate the same t6
to two different t8. That's why we have to explicitly specify the
functional dependency.

I guess this might probably answer the original question Levent Erkok
posed three years ago.