We have seen how to model one effect as a request-reply interaction of an expression and a request handler (interpreter). Let us generalize, from a Get0 request for an integer to arbitrary effects with arbitrary return types.

The EDLS paper by Cartwright and Felleisen has, in fact, shown the general solution (on p.7). The idea is still the same: ``The meaning of a program phrase is a computation, which may be a value or an effect.... an effect can be viewed as a message to the central authority plus enough information to resume the suspended calculation.'' We now stress that different computations may have values of different types; different effects carry different messages, each with their own response type. We generalize the CRead data type of the previous section correspondingly:

data Comp req a where Val :: a -> Comp req a E :: req x -> (x -> Comp req a) -> Comp req a

(This data type declaration is in the new-style GADT syntax although it is not a GADT. It is an existential, which looks cleaner in the new syntax: see below.) Like CRead earlier, the data type Comp expresses the meaning of a computation as either a value Val x or an effect E req k . Comp is first parameterized by the type of the value a and then by the type of the request message req . A request message type defines a collection of, generally, several request messages, each with its own response type. For example, a hypothetical request type ReqIO may contain the message GetChar with the response type Char and the message PutChar with the response type () ; the latter has a payload: the character to print. Such ReqIO type is written in Haskell as a GADT (GADTs are designed to express API interfaces):

data ReqIO x where GetChar :: ReqIO Char PutChar :: Char -> ReqIO ()

ReqIO

* -> *

Comp

E r k :: Comp req a

r

req x

k:: x -> Comp req a

x

r

x

Comp ReqIO Int

GetChar

PutChar

x

The general Comp data type describes the meaning of the earlier ReaderLang expressions just as well as the made-to-order CRead . First, we define the type of ReaderLang 's requests, with a single message GetI , for an implicit integer:

data GetI x where GetI :: GetI Int

GetI

GetI

ReaderLang

Comp GetI Int

instance ReaderLang (Comp GetI Int) where int n = Val n ask = E GetI Val add (Val x) (Val y) = Val (x+y) add (E r k) y = E r (\x -> add (k x) y) add x (E r k) = E r (\y -> add x (k y))

Comp GetI Int

CRead

We want further generalizations: We want a language with more types than just integers, and more operations than just the addition. Before extending ReaderLang with, say, subtraction, booleans and comparison, let us take a closer look at the meaning of add , repeated below:

add (Val x) (Val y) = Val (x+y) add (E r k) y = E r (\x -> add (k x) y) add x (E r k) = E r (\y -> add x (k y))

Of the three cases in the definition, only the first deals with the summation. The other two are the boilerplate of the effect propagation -- the boilerplate that begs to be factored out. In fact, it is factored out in EDLS (p.7), under the name of `handler'. We give that function a more conventional name:

bind :: Comp req a -> (a -> Comp req b) -> Comp req b bind (Val x) f = f x bind (E r k) f = E r (\x -> bind (k x) f)

Val

inV

Comp

add

add e1 e2 = bind e1 $ \x -> bind e2 $ \y -> Val (x+y)

add

bind

ReaderLang

add

bind

bind

While in the generalization spirit, let's make GetI ask not just for integers but for values of an arbitrary type. The new request type Get is parameterized by the type e of asked values:

data Get e x where Get :: Get e e

ask

ask :: Comp (Get e) e ask = E Get Val

It is still a request to the context for an implicit value, which is no longer restricted to be an integer. The operation bind has been defined already in the general form, for any request req . The old rlExp example -- which asks for two integers and computes their sum incremented by one -- may now be re-written, under the name rlExp2 , as:

rlExp2 :: Comp (Get Int) Int rlExp2 = bind ask $ \x -> bind ask $ \y -> Val (x + y + 1)

bind

bind

>>=

Val

return

rlExp2 :: Comp (Get Int) Int rlExp2 = do x <- ask y <- ask return (x + y + 1)

The renaming pre-supposes that Comp req is a monad:

instance Monad (Comp req) where return = Val (>>=) = bind

Functor

Applicative

Comp req

rlExp3 :: Comp (Get Int) Int rlExp3 = do x <- rlExp2 y <- ask return (x * y - 1) rlExp4 :: Comp (Get Int) Bool rlExp4 = do x <- rlExp3 y <- ask return (x > y)

The other side of interactions -- request handlers -- also have room for generalization. The runReader0 handler of Get0 requests from the previous section now reads

runReader :: e -> Comp (Get e) a -> a runReader e (Val x) = x runReader e (E Get k) = runReader e $ k e _ = runReader 2 rlExp2 :: Int -- 5

The code is the same (modulo the replacement of Get0 with Get ). The type signature is more general: the asked value, and the return value of the computation are not restricted to integers. The new runReader can handle rlExp2 , and also rlExp3 and rlExp4 (the latter produced a boolean):

_ = runReader 2 rlExp3 :: Int -- 9 _ = runReader 2 rlExp4 -- True

The further generalization is perhaps less expected -- especially if we are too used to the monadic view, where there is usually only one way to `run' a monad. From the standpoint of EDLS (and its followers like extensible-effects), an effectful expression does not `do' anything -- rather, it asks for it. It is a request handler that does the doing. There is generally more than one way to interpret a request. The runReader e interpreter gives the same reply e on each Get request -- realizing the so-called `environment', or `reader' effect. Get requests may also be handled differently, for example:

feedAll :: [e] -> Comp (Get e) a -> Maybe a feedAll _ (Val a) = Just a feedAll [] _ = Nothing feedAll (h : t) (E Get k) = feedAll t (k h)

feedAll

Nothing

rlExp3

feedAll

_ = feedAll [2,3,4] rlExp3 :: Maybe Int -- Just 23

Get

getchar

ask

ask

To strengthen what we have learned and to demonstrate the generality of the approach, let's define another effect. Whereas Get was asking the context for a value, we will now be telling the context a value (e.g., to log it), which becomes the payload of the effect message. Formally, the request message and its type are defined as:

data Put o x where Put :: o -> Put o ()

tell :: o -> Comp (Put o) () tell x = send (Put x)

Put

send

send :: req x -> Comp req x send req = E req return

Declaring the request type (and, optionally, defining the helper like tell ) is all we need to do to introduce a new effect. It can be used right away, for example, for simple logging:

wrExp :: Show a => Comp (Put String) a -> Comp (Put String) () wrExp m = do tell "about to do m" x <- m tell (show x) tell "end"

One may interpret Put by recording all told values, returning them as a list at the end, when the handled expression has nothing more to tell:

runWriter :: Comp (Put o) x -> ([o],x) runWriter (Val x) = ([],x) runWriter (E (Put x) k) = let (l,v) = runWriter (k ()) in (x:l,v)

_ = runWriter (wrExp (return 1)) -- (["about to do m","1","end"],())

QUIZ: What other Put interpreters can you write?

In conclusion, we have put the intuition of effects as interactions into practice, as a framework to define, use and handle arbitrary effects. We have seen the connection to the free(r) monads. We will now learn how to combine effects.