6
votes

Say I have a multi-param typeclass:

class B b => C a b | a -> b where
    getB :: a -> (T, b)

Then I want a function:

f :: C a b => a -> c
f = g . getB

Which uses another function g :: B b => (T, b) -> c and getB, so an instance C a b is needed.

(Disclaimer: the real problem is much more complicated, the above-mentioned is only a simplified version.)

The problem is, given the functional dependency C a b | a -> b, we know that b can be completely decided by a, so theoretically I should be possible not to mention b in the type of f (since it is not used elsewhere but in instance C a b), but I didn't find any way to achieve this.

Also note that due to the existence of constraint B b in class C, I think I cannot use a TypeFamilies extension instead, for the syntax of that leaves nowhere for the type constraint B b to live.

So is there any way that I can hide the implementation details (the irrelevant type parameter b) from the user of this function f?

2

2 Answers

7
votes

Using TypeFamilies, you could rewrite C into a single-parameter class as follows:

class B (BOf a) => C a where
  type BOf a :: *
  getB :: a -> (T, BOf a)

Which gives you the better looking constraint for f:

f :: C a => a -> c

There is (sadly) no way to omit parameters of a multi-parameter class to obtain a similar result.

4
votes

If you turn on RankNTypes you can make a type alias that lets you elide b like this:

type C' a t = forall b. C a b => t
f :: C' a (a -> c)
f = g . getB

You can even include further contexts, if necessary, inside the second argument:

f' :: C' a ((Num a, Ord c) => a -> c)
f' = g . getB

This is pretty non-standard, though. You'll want to leave an explanatory comment... and the effort of writing it might be bigger, in the end, than the effort of just including a b at each use of C.