13

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.

lehins
  • 9,642
  • 2
  • 35
  • 49
  • 4
    I don't think you can. But you could always make `fooBarClassFunc'` the method, and then define `fooBarClassFunc :: forall b a. ...` as a regular function. Same effect for users, a little strange for instantiators... – luqui Feb 14 '19 at 22:35
  • @liqui Yes, of course. But I was more curious if it is possible on the typeclass itself, maybe some special syntax or something that I am not aware of. I don't particularly need to solve that either, this is just pure curiosity. – lehins Feb 14 '19 at 22:40
  • I naively believed that `QuantifiedConstraints` would allow to specify this as well, but it does not seem to allow anything like `class forall b a . (C a, C b) => K a b where`. Further, one might want the order to vary from method to method, for maximum generality. – chi Feb 14 '19 at 23:28
  • Keep in mind you can still change parameter order in certain situations e.g. you can do `class X t where { func :: forall a b. t a -> t b -> t (a, b) }`. You just can't do it with `t`, which is the type on which the typeclass is defined. Not that that helps very much... – bradrn Feb 15 '19 at 02:41

1 Answers1

2

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.

AntC
  • 2,623
  • 1
  • 13
  • 20
  • There is no need to add another class in order to have a work around, adding a single function `otherFooBarClassFunc :: forall b a . FooBar a b => a -> b -> String`, where `otherFooBarClassFunc = fooBarClassFunc` would suffice. My question concerns the method attached to the class, not the things that are derived from it. – lehins Feb 15 '19 at 09:56