Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Explicit forall on a type class function

Since ghc-8.0 we have a very nice extension called TypeApplications. Which allows us instead of:

λ> show (5 :: Int)
"5"

do so something like that:

λ> :set -XTypeApplications
λ> show @Int 5
"5"

Which is really cool. It becomes a bit more involved when we add more type variables, but there are rules that can be used to determine the exact order, and they are very well documented:

showFooBar :: (Show a, Show b) => a -> b -> String
showFooBar a b = show a ++ " and " ++ show b

So in the function above we would first supply a and then b:

λ> showFooBar @Int @Double 3 4
"3 and 4.0"

That's great, but what if I'd like to change the order? No problem there, we can use ExplicitForAll extension (or some other that imply it) to specify it:

{-# LANGUAGE ExplicitForAll #-}

showFooBar :: forall b a . (Show a, Show b) => a -> b -> String
showFooBar a b = show a ++ " and " ++ show b

And now we reversed the order of types that we are going to apply:

λ> showFooBar @Int @Double 3 4
"3.0 and 4"

The problem is that I can't seem to figure out how to achieve the same affect for functions that are part of a type class. Consider this example:

{-# LANGUAGE MultiParamTypeClasses #-}

class (Show a, Show b) => FooBar a b where
  fooBarClassFunc :: a -> b -> String

I can't put forall on a function now (eg. fooBarClassFunc :: forall a b . a -> b -> .., cause that changes the meaning of the function and obviously does not compile.

So, the question is, how do you change the order of type variables for the purpose of TypeApplication inside the type class methods?

Edit

Just in case, I have tried InstanceSigs extension, and it completely ignores the order of forall type variables as far as TypeApplications are concerned, which is a good thing, otherwise we would end up with behavior that is determined by the instance, rather than the class.

like image 573
lehins Avatar asked Feb 14 '19 22:02

lehins


1 Answers

how do you change the order of type variables for the purpose of TypeApplication inside the type class methods?

@luqui's answer is good enough, I would think. But why not this:

class (Show b, Show a) => FooBar b a where
  fooBarClassFunc :: a -> b -> String

You only have one method, so the only consideration driving the order of parameters to the class is for the purpose of TypeApplication inside the methods.

If you have two or more methods for which you want the order of TypeApplication to be different (@chi's point, but why?), then for the other methods either luqui's suggestion, or (equivalently) an other class with a superclass constraint and a default implementation.

class (Show a, Show b, FooBar b a) => OtherFooBar a b where
  otherFooBarClassFunc :: a -> b -> String
  otherFooBarClassFunc = otherFooBarClassFunc'  -- default
instance {-# NOOVERLAPPABLE #-} OtherFooBar a b  where {}  -- take default

(Assuming otherFooBarClassFunc' is defined in the main class; and that's where the real instance definition goes on.)

There is a lot to be said for one method per class, of course.

{-# NOOVERLAPPABLE #-} wot we do not 'ave is my little in-joke.

like image 185
AntC Avatar answered Nov 11 '22 01:11

AntC