# A dumb example.

We will make a (very) simple banking app.

• It will attempt to withdraw funds
• Logs a message if it fails
• Updates the current balance if it succeeds

# Types first!

)
=> Int
-> m (Maybe Int)
)
=> Int
-> m (Maybe Int)

withdraw desired = do
amount <- getCurrentBalance
if amount < desired
then do
log "not enough funds"
return Nothing

else do
putCurrentBalance \$ amount - desired
return \$ Just amount

# But how can we test it?

A new datatype describing if we're running for real:

data Mode = ForReal
| Test (IORef Int)
)
=> Mode
-> Int
-> m (Maybe Int)

withdraw mode desired = do
amount <- case mode of
ForReal    -> getCurrentBalance
Test ioref -> liftIO \$ readIORef ioref
if amount < desired
then do
log "not enough funds"
return Nothing

else do
let putAction =
case mode of
ForReal    -> putCurrentBalance
Test ioref -> liftIO . writeIORef ioref
putAction \$ amount - desired
return \$ Just amount

# This sucks!

• IO is directly exposed
• Test code is interspersed with our real logic
• No compiler guarantees that we mocked all of our IO

# Wouldn't it be nice...

... if we could just write the program that we cared about?

# Polymorphism to the rescue!

getCurrentBalance :: m Int
putCurrentBalance :: Int -> m ()

# The code we want to write.

)
=> Int
-> m (Maybe Int)

withdraw desired = do
amount <- getCurrentBalance
if amount < desired
then do
log "not enough funds"
return Nothing

else do
putCurrentBalance \$ amount - desired
return \$ Just amount

By adding this new constraint, we can abstract over IO.

Our application and test code can swap out different monads.

# All is right in the world.

Or is it?

This abstraction comes with a heavy cost.

# We need a carrier...

newtype IOBankT m a = IOBankT
{ runIOBankT :: IdentityT m a
}

# one that behaves with MTL...

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

deriving ( Functor
, Applicative
, ...
)

getCurrentBalance = ...
putCurrentBalance = ...

# and doesn't need to be at the top of the stack...

getCurrentBalance = lift getCurrentBalance
putCurrentBalance = lift . getCurrentBalance

getCurrentBalance = lift getCurrentBalance
putCurrentBalance = lift . getCurrentBalance

getCurrentBalance = lift getCurrentBalance
putCurrentBalance = lift . getCurrentBalance

-- so many more

# Things that take a lot of work don't get done.

Even if they're best practices.

Boilerplate gets in the way.

# Monad transformers are a hack.

Everything else we use in Haskell composes.

# There's a better way.

Monadic programs expressed as data structures we can manipulate.

Provided by the freer-effects package.

# Eff to the Rescue!

withdraw :: ( Member Bank   r
, Member Logger r
)
=> Int
-> Eff r (Maybe Int)

withdraw desired = do
amount <- getCurrentBalance
if amount < desired
then do
log "not enough funds"
return Nothing

else do
putCurrentBalance \$ amount - desired
return \$ Just amount

# Small change. Big impact.

)
=> Int
-> m (Maybe Int)
withdraw :: ( Member Bank   r
, Member Logger r
)
=> Int
-> Eff r (Maybe Int)

# Listen to the types.

withdraw :: ( Member Bank   r
, Member Logger r
)
=> Int
-> Eff r (Maybe Int)

# No nominal typing.

withdraw :: ( Member Bank   r
, Member Logger r
)
=> Int
-> Eff r (Maybe Int)

# Effects as data.

data Bank a where
GetCurrentBalance :: Bank Int
PutCurrentBalance :: Int -> Bank ()
getCurrentBalance :: Member Bank r
=> Eff r Int
getCurrentBalance = send GetCurrentBalance
putCurrentBalance :: Member Bank r
=> Int
-> Eff r ()
putCurrentBalance amount = send \$ PutCurrentBalance amount

# Still too much boilerplate?

data Bank a where
GetCurrentBalance :: Bank Int
PutCurrentBalance :: Int -> Bank ()

makeFreer ''Bank

# Don't forget the lumberjack.

data Logger a where
Log :: String -> Logger ()

makeFreer ''Logger

# What's left?

withdraw :: ( Member Bank   r
, Member Logger r
)
=> Int
-> Eff r (Maybe Int)

# The REPL can help.

> :kind Eff

Eff :: [* -> *] -> * -> *

# An exact correspondence.

StateT s (ReaderT r IO) a
Eff '[State s, Reader r, IO] a

# So what?

main runs in IO -- not in Eff.

We have one special function:

runM :: Monad m => Eff '[m] a -> m a

run :: Eff '[] a -> a

run and runM provide base cases.

# Induction.

We want a function that looks like this:

runLogger :: Eff (Logger ': r) a
-> Eff r a

It "peels" a Logger off of our eff stack.

What does it mean to run a Logger?

Maybe we want to log those messages to stdout.

runLogger :: Member IO r
=> Eff (Logger ': r) a
-> Eff r a

# All for naught?

## No!

Even though we have IO here, it's not the program that requires it; only the intepretation.

runLogger :: Member IO r
=> Eff (Logger ': r) a
-> Eff r a

runLogger = runNat logger2io
where
logger2io :: Logger x -> IO x
logger2io (Log s) = putStrLn s

# We can do the same thing for Bank.

runBank :: Member IO r
=> Eff (Bank ': r) a
-> Eff r a

runBank = runNat bank2io
where
bank2io :: Bank x -> IO x
bank2io GetCurrentBalance            =  -- use IO to return an Int
bank2io (PutCurrentBalance newValue) =  -- perform IO and return ()

# Back to the REPL.

> :t (runM . runLogger . runBank)

Eff '[Bank, Logger, IO] a -> IO a
> :t (runM . runLogger . runBank \$ withdraw 50)

IO (Maybe Int)

# But how can we test this?

{-# LANGUAGE ScopedTypeVariables #-}

ignoreLogger :: forall r a
. Eff (Logger ': r) a
-> Eff r a

ignoreLogger = handleRelay pure bind
where
bind :: forall x
. Logger x
-> (x -> Eff r a)
-> Eff r a
bind (Log _) cont = cont ()
testBank :: forall r a
. Int
-> Eff (Bank ': r) a
-> Eff r a

testBank balance = handleRelayS balance (const pure) bind
where
bind :: forall x
. Int
-> Bank x
-> (Int -> x -> Eff r a)
-> Eff r a
bind s GetCurrentBalance      cont = cont s  s
bind _ (PutCurrentBalance s') cont = cont s' ()

# Finally, pure interpretations!

> :t (run . ignoreLogger . testBank)

Eff '[Bank, Logger] a -> a
> :t (run . ignoreLogger . testBank \$ withdraw 50)

Maybe Int

# So far, this doesn't seem very reusable.

data Logger a where
Log :: String -> Logger ()

# Why not this?

data Writer w a where
Tell :: w -> Writer w ()

Note: there is no Monoid constraint here!

data Bank a where
GetCurrentBalance :: Bank Int
PutCurrentBalance :: Int -> Bank ()

# Why not this?

data State s a where
Get :: State s s
Put :: s -> State s ()

# This gives us more denotational meaning.

withdraw :: ( Member (State Int)     r
, Member (Writer String) r
)
=> Int
-> Eff r (Maybe Int)

withdraw desired = do
amount :: Int <- get
if amount < desired
then do
tell "not enough funds"
return Nothing

else do
put \$ amount - desired
return \$ Just amount

# Mo' generality = fewer problems.

More general types are more likely to already have the interpretations that you want.

# A drop-in for MTL?

Yes! But more than just that!

# A conceptually different execution model.

In MTL:

In Eff:

runReader :: Eff (Reader x ': r) a -> x -> Eff r a

# Interpreting effects in terms of one another.

data Exc e a where
ThrowError :: e -> Exc e a
makeFreer ''Exc
accumThenThrow :: ( Eq e
, Monoid e
, Member (Exc e) r
)
=> Eff (Writer e ': r) a
-> Eff r a
accumThenThrow prog = do
let (a, e) = pureWriter prog
unless (e == mempty) \$ throwError e
return a

# Non-trivial transformations.

data SetOf s a where
SetAdd      :: s -> SetOf s ()
SetContains :: s -> SetOf s Bool
makeFreer ''SetOf
dedupWriter :: ( Member (SetOf  w) r
, Member (Writer w) r
)
=> Eff r a
-> Eff r a
dedupWriter = interpose pure bind
where
bind (Tell w) cont = do
tell w
cont ()

# Summing up.

• Eff gives us the flexibility of MTL without the boilerplate.