I am encountering a problem with Monad Transformers, but I think it's helpful to include some context of how I got to the state I'm currently in, so I'll start with a rough explanation of my program:
The project is an interpreter for a simple (toy) programming language. I have a monad that is used to represent evaluation. It has a definition that looks like:
type Eval a = ReaderT Environment (ExceptT String (State ProgState a))
This works quite nicely, and I can happy write an evaluation function:
eval :: Expr -> Eval Value
eval (Apply l r) = ...
eval ...
The Value datatype has a slight quirk in that I embed Haskell functions of type Value -> EvalM Value. To do this I added a generic type parameter to the definition, which I then instantiate with EvalM:
data Value' m
= IntVal Int
...
| Builtin (Value' m -> m (Value' m))
type Value = Value' EvalM
Things were going well, but then I had to write a function that heavily interleaved code using the Eval monad with IO operations. This looked kinda horrendous:
case runEval ({-- some computation--}) of
Right (val, state') -> do
result <- -- IO stuff here
case runEvaL {-- something involving result --} of
...
Left err -> ...
The function had like 5 levels of nesting, and was also recursive... definitely ugly :(. I hoped adapting to use a Monad Transformer would be the solution:
type EvalT m = ReaderT Environment (ExceptT String (StateT ProgState m))
This refactor was relatively painless: mostly it involved changing type-signatures rather than actual code, however there was a problem: Builtin. Given a expression that was applying argument x to a value of the form Builtin f, the eval function would simply return f x. However, this has type Eval Value, but the refactored eval needs to have type-signature:
eval :: Monad m => EvalT m Value
As far as Fixing this (i.e. making it typecheck) is concerned, I can think of a couple solutions each of which has a problem:
- Implementing some kind of analog to
liftwhere I can takeEval atoEvalT m a.- Problem: I'm not aware of how to do this (or if it's even possible)
- Changing the
Valuetype so that it is indexed by an inner monad, i.e.Value m = Value' (EvalT m).- Problem: now anything containing a
Value mhas to be parameterized bym. I feel that it would unnecessarily clutters up the type-signatures of anything containing aValue, which is a problem given the initial motivation to do this change was cleaning up my code.
- Problem: now anything containing a
Of course, there may be a much better solution that I haven't thought of yet. Any feedback/suggestions are appreciated :).
Builtinto contain a value of an ADT describing all possible built-in functions, rather than an actual function?EvalTlooks identical to yourEvalto me...? Also, did you perhaps meantype Value = Value' EvalM(rather thandata ...)?