johncowie
johncowie

Reputation: 317

How can I pass a monadic function as an argument with a flexible type variable?

Apologies for the potentially vague question title - I'm not sure how to phrase it because I have a pretty poor understanding of what the problem is.

Basically, how do I make the following compile? :-p

{-# LANGUAGE MultiParamTypeClasses #-}

class (Monad m) => MyClass m a where
  valM :: m (Maybe a)
  val  :: m a

f :: (MyClass m a) => (m a -> IO a) -> IO (a, Maybe a)
f g = do
  x <- g val
  yM <- g valM
  return (x, yM)

GHC (v8.2.2) complains that a is a rigid type variable and can't seem to cope with the idea that (g val) and (g valM) could produce values of different types. I've tried using RankNTypes but to no avail.

Is there an extension I can use to help the compiler, or is there something conceptually broken with what I'm trying to do from a type-inference point of view?

Upvotes: 2

Views: 158

Answers (1)

Alexis King
Alexis King

Reputation: 43842

You’re right that you need RankNTypes, but you’re missing a forall. The correct type for f is:

f :: MyClass m a => (forall b. m b -> IO b) -> IO (a, Maybe a)

…since the function passed to f must work for any result type, and it shouldn’t be related to the a in the result.


It’s also potentially worth noting that this sort of function is also known as a natural transformation, and the natural-transformation package provides a (~>) type alias for such functions:

type (~>) f g = forall a. f a -> g a

Therefore, using that type alias, you could also write f like this:

f :: MyClass m a => (m ~> IO) -> IO (a, Maybe a)

Upvotes: 8

Related Questions