Explicit forall on a type class function
Asked Answered
H

1

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.

Hued answered 14/2, 2019 at 22:34 Comment(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...Backrest
@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.Hued
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.Nickynico
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...Killdeer
F
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.

Fryer answered 15/2, 2019 at 9:26 Comment(1)
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.Hued

© 2022 - 2024 — McMap. All rights reserved.