> I have yet to see a followable code case demonstrating the use of custom monads (not IO or Maybe or lists etc) that show why (when) I should ever write my own, to understand even just using them better generally.
For an interpreter I'm working on I have an "Eval" type, implemented as a newtype wrapper around a transformer stack. This gives me a context to work in where I can access variables in scope (carried in a ReaderT) and fail with error (handled by an ExceptT). The code (simplified) looks a little like this:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaExpr #-}
newtype Eval a = Eval (Reader EvalContext (ExceptT String m) a)
deriving (Functor, Applicative, Monad, MonadReader EvalContext, MonadError String)
lookupVariable :: Variable -> Eval Value
lookupVariable v = asks (findVarInContext v) >>= \case
Nothing -> throwError ("unbound variable: " ++ show v)
Just result -> pure result
lookupFunction :: FunctionName -> Eval ([Value] -> Value)
lookupFunction f = asks (findFuncInContext f) >>= \case
Nothing -> throwError ("unknown function: " ++ show f)
Just f' -> pure f'
evaluateFunction :: Function -> Eval Value
evaluateFunction (Function name args) =
lookupFunction function <*> mapM evaluateExpr args
> That looks like more of a combination of existing monads than a truly new monad.
Well sure, but if we're going to exclude those then the grandparent's ask is a bit high: either we present something useless and pedagogic, or we present something useful that exists in a library somewhere, or we present something new and useful... that we should then also put in a library somewhere!
For an interpreter I'm working on I have an "Eval" type, implemented as a newtype wrapper around a transformer stack. This gives me a context to work in where I can access variables in scope (carried in a ReaderT) and fail with error (handled by an ExceptT). The code (simplified) looks a little like this: