MonadEffect
In the previous folder, we saw that we could print content to the console using specialLog
. Underneath, we're using log
, the function with the type, String -> Effect Unit
. Since "bind
outputs the same box-like type it receives," how was this possible?
In this file, we'll show one way to workaround this limitation. This solution will be used frequently in real code wherever the Effect
monad is used. However, this solution doesn't necessarily work for other monads. Still, it is conceptually easy to understand and creates scaffolding. That scaffolding will make it easier to understand other workarounds to this restriction that we'll discuss in the Application Structure
folder.
Reviewing a Previous Workaround: Lifting one Monad into another
When overviewing the ""bind
outputs the same box-like type it receives" restriction, we described the previous workaround:
import Prelude -- for the (+) and (~>) function aliases
data Box1 a = Box1 a
data Box2 a = Box2 a
class LiftSourceIntoTargetMonad sourceMonad targetMonad where {-
liftSourceMonad :: forall a. sourceMonad a -> targetMonad a -}
liftSourceMonad :: sourceMonad ~> targetMonad
instance LiftSourceIntoTargetMonad Box2 Box1 where
liftSourceMonad :: Box2 ~> Box1
liftSourceMonad (Box2 a) = Box1 a
bindAttempt :: Box1 Int
bindAttempt = do
four <- Box1 4
six <- liftSourceMonad $ Box2 6
pure $ four + six
-- type class instances for Monad hierarchy
instance Functor Box1 where
map :: forall a b. (a -> b) -> Box1 a -> Box1 b
map f (Box1 a) = Box1 (f a)
instance Apply Box1 where
apply :: forall a b. Box1 (a -> b) -> Box1 a -> Box1 b
apply (Box1 f) (Box1 a) = Box1 (f a)
instance Bind Box1 where
bind :: forall a b. Box1 a -> (a -> Box1 b) -> Box1 b
bind (Box1 a) f = f a
instance Applicative Box1 where
pure :: forall a. a -> Box1 a
pure a = Box1 a
instance Monad Box1
-- Needed to print the result to the console in the REPL session
instance (Show a) => Show (Box1 a) where
show (Box1 a) = show a
MonadEffect
When we have an Effect
-based computation that we want to run in some other monadic context, we can use liftEffect
from MonadEffect if the target monad has an instance for MonadEffect
:
class (Monad m) <= MonadEffect m where
liftEffect :: Effect ~> m
Aff
has an instance for MonadEffect
, so we can lift Effect
-based computations into an Aff
monadic context. Below was how we defined specialLog
. You can see it in the next file:
specialLog :: String -> Aff Unit
specialLog message = liftEffect $ log message
Referring back to our previous "local state" example, the ST
monad does not have an instance for MonadEffect
. This decision is intentional: state manipulation of that kind should be pure and not have any side-effects. That's why it exists inside of its own monadic context: to ensure that those who attempt to do so get compiler errors. This is a safety precaution, not a "we wanted to be jerks who frustrate you" decision.
As we saw previously in the Switching-Context.purs
file, running multiple Aff
computations in an Effect
monadic context doesn't always lead to a predictable output. However, running multiple Effect
-based computations in an Aff
monadic context is much more predictable.