Search code examples
haskellmonadstypeclassmonad-transformers

How can I write a function `run` that calls `runStateT` or `runReaderT`?


How can I write a generic function run that takes an object of some monad transformer, and calls the corresponding function?

Given run s,

  • If s is a StateT, run = runStateT
  • If s is a ReaderT, run = runReaderT
  • If s is a MaybeT, run = runMaybeT

I've tried creating a typeclass Runnable:

:set -XMultiParamTypeClasses
:set -XFlexibleInstances

class Runnable a b where
  run :: a -> b
  (//) :: a -> b
  (//) = run

instance Runnable (StateT s m a) (s -> m (a, s)) where
 run = runStateT

instance Runnable (ReaderT r m a) (r -> m a) where
 run = runReaderT

But when I try using run, it doesn't work. For example, let's define simpleReader, which just returns 10 when read:

simpleReader = ReaderT $ \env -> Just 10

runReaderT simpleReader ()

This outputs Just 10, like expected.

However, when I try using run, it gives me an error:

run simpleReader ()
<interactive>:1:1: error:
    • Non type-variable argument in the constraint: Runnable (ReaderT r Maybe a) (() -> t)
      (Use FlexibleContexts to permit this)
    • When checking the inferred type
        it :: forall r a t. (Runnable (ReaderT r Maybe a) (() -> t), Num a) => t

If I enable FlexibleContexts like it suggests, I get a different error:

<interactive>:1:1: error:
    • Could not deduce (Runnable (ReaderT r0 Maybe a0) (() -> t))
        (maybe you haven't applied a function to enough arguments?)
      from the context: (Runnable (ReaderT r Maybe a) (() -> t), Num a)
        bound by the inferred type for ‘it’:
                   forall r a t. (Runnable (ReaderT r Maybe a) (() -> t), Num a) => t
        at <interactive>:1:1-19
      The type variables ‘r0’, ‘a0’ are ambiguous
    • In the ambiguity check for the inferred type for ‘it’
      To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
      When checking the inferred type
        it :: forall r a t. (Runnable (ReaderT r Maybe a) (() -> t), Num a) => t

Solution

  • The output type of run is totally determined by the input type to it. Represent this as a functional dependency (-XFunctionalDependencies):

    class Runnable a b | a -> b where
      run :: a -> b
      -- side note: (//) does not belong here
    (//) :: Runnable a b => a -> b
    (//) = run
    -- instances as given
    

    Now it works. The reason your version doesn't work is because there's no way to know what the output type should be. If you have e.g.

    act :: ReaderT Env Identity Ret
    

    Then

    run act :: Runnable (ReaderT Env Identity Ret) b => b
    

    And then we're stuck, there's no way to figure out what b should be. It's e.g. possible to add another instance

    instance Runnable (ReaderT r m a) (ReaderT r m a) where
      run = id
    

    And now run act can be either a ReaderT Env Identity Ret or Env -> Identity Ret. The dependency a -> b a) allows us to infer b from a and b) restricts instance declarations to make this possible. Conflicting instances like the one I gave are rejected, and run act has the b type inferred to Env -> Identity Ret by looking at the instance you gave, as desired.