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?
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With