*CSE 320: Programming Languages @ CSU, San Bernardino*

# Lab 3: Effects

Welcome to the third and final lab using Haskell. In this lab, we will discuss effects, how to control them, and how a collection of typeclasses makes working with them more pleasant.

## Context

Until now, we’ve tiptoed around the type signature for the `main`

function in Haskell. In case you were wondering, it looks like this:

```
main :: IO ()
```

This means that `main`

is a function that takes in no parameters, and returns an `IO`

struct containing nothing (indicated by `()`

, the unit type, as discussed in the first lab).

So what is this `IO`

struct? Think of it as a “context” in which a value of some type can be placed. When you do input-output in Haskell, it’s done in this `IO`

context. So, for example, the function `putStrLn`

looks like this:

```
putStrLn :: String -> IO ()
```

This means that `putStrLn`

takes in a Haskell string, and returns an empty `IO`

context (there’s no return value for printing something, so there’s no value for the `IO`

context to hold after this operation).

If you’re doing input, say, to get a single character, it looks like this:

```
getChar :: IO Char
```

This means that `getChar`

takes in no parameters, and returns a `Char`

*in the IO context*.

`IO`

actually comes with a bunch of functions for doing input-output. Here they are:

```
putChar :: Char -> IO ()
putStr :: String -> IO ()
putStrLn :: String -> IO ()
print :: (Show a) => a -> IO ()
getChar :: IO Char
getLine :: IO String
getContents :: IO String
interact :: (String -> String) -> IO ()
type FilePath = String
readFile :: FilePath -> IO String
writeFile :: FilePath -> String -> IO ()
appendFile :: FilePath -> String -> IO ()
readIO :: Read a => String -> IO a
readLn :: Read a => IO a
```

Notice that *all* the functions doing output return some `IO ()`

, while *all* the functions getting input return some `IO`

type (`Char`

or `String`

, depending on whether you’re getting a character or string).

*All input-output in Haskell goes through these IO functions.*

## Operating in the Context

The question then is what to do with the context once you have it. For example, let’s say you have some `IO Char`

value, and want to modify the character inside. How would you do that? You need some way to modify the value inside, maybe like this:

```
modify :: IO Char -> IO Char
modify (IO c) = IO (toLower c)
```

But hey, doesn’t it seem weird to have a function that only works for `IO`

characters? Let’s split out the part specific to `IO`

from the part specific to `Char`

:

```
modifyIO :: IO Char -> IO Char
modifyIO c = mapIO modify c
mapIO :: (a -> b) -> IO a -> IO b
mapIO f (IO x) = IO (f x)
modify :: Char -> Char
modify c = toLower c
```

So now we have a pure, non-`IO`

function for modifying the character, and a generic function for applying the non-`IO`

function in the `IO`

context!

It turns out there are other places this same pattern can be used. What if we have a value of type `Maybe Char`

, and we want to modify the `Char`

in the same way we just did with `IO`

. It looks like this:

```
modifyMaybe :: Maybe Char -> Maybe Char
modifyMaybe c = mapMaybe modify c
mapMaybe :: (a -> b) -> Maybe a -> Maybe b
mapMaybe f (Just x) = Just (f x)
mapMaybe f (Nothing) = Nothing
modify :: Char -> Char
modify c = toLower c
```

Hang on! `mapMaybe`

’s type signature is almost identical to the one for `mapIO`

! Can we make a function that works for both? We can!

```
class Functor f where
fmap :: (Functor f) => (a -> b) -> f a -> f b
```

That’s it! We’ve defined a new type class, called `Functor`

that contains one function, `fmap`

, which is the generic version of those `mapIO`

and `mapMaybe`

functions we wrote ourselves. It turns out that both the `IO`

type and `Maybe`

type are `Functors`

! In fact, any type that can be mapped over in the same way is a functor, meaning we can operate on values in the context of that type, without polluting the functions we’re applying by forcing them to know things about the context.

## Sequencing Contextual Operations

Okay, so we can operate on a value in a context, without having to mess up our functions by making them care about the context itself. What if we want to sequence contextual operations? Can we do it?

Imagine we have a function in a context, like so:

```
$ let x = Just (+ 3)
x :: (Num a ) => Maybe (a -> a)
```

We now have a function that maps numbers to numbers, inside the `Maybe`

context. Unfortunately, we can’t give this function to `fmap`

, because its type signature is wrong! It expects a function `(a -> b)`

, not `f (a -> b)`

. But could we write a function that *does* work with `x`

?

```
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: (Applicative f) => f (a -> b) -> f a -> f b
```

We’ll call `<*>`

“apply.” “Apply” is a function that takes in a function in a context, a value in the same context, and uses the function to modify the value, returning a new value, still in the same context. `pure`

is a simple function to take a value and put it into the context.

But just what kind of context is it? Well, it’s an applicative one! An applicative context is a context that can implement the `Applicative`

typeclass. Simple as that.

But how does this help with sequencing? Well, let’s look at `Applicative Maybe`

!

```
instance Applicative Maybe where
pure x = Just x
(<*>) (Just f) x = fmap f x
(<*>) (Nothing) _ = Nothing
```

So `pure`

simply wraps the value in a `Just`

, and “apply” applies the function if it’s there, or passes on `Nothing`

if there’s no function. Let’s see it in action!

```
$ pure (+) <*> Just 3 <*> Just 5
Just 8
$ pure (+) <*> Just 3 <*> Nothing
Nothing
$ pure (+) <*> Nothing <*> Just 5
Nothing
```

So, `pure (+)`

, takes `(+)`

(which has type `Num a => a -> a -> a`

) and puts it into the `Maybe`

context, resulting in a value of just `Num a => Maybe (a -> a -> a)`

.

“Apply” then first applies the function to a value of type `Maybe 3`

, resulting in a value of type `Num a => Maybe (a -> a)`

, thanks to partial application.

The second “apply” applies the function to `Just 5`

, resulting in a value of type `Maybe a`

. In this case, the value is `8`

, which is the result of `3 + 5`

.

That is how `Applicative`

provides sequencing! With partial application, you give it a function with multiple parameters, and then *apply* that function to those parameters, all while staying in the same context! And thanks to `pure`

, you still don’t need to pollute your functions by making them care about the context in which they’ll be applied!

### Working with Contextual Functions

So, we know how to apply a pure function to a contextual value (with `fmap`

), and we know how to apply a contextual function to contextual values. But what if we have a function like `putChar`

, from earlier?

Remember, `putChar`

has the following type signature:

```
putChar :: String -> IO ()
```

This function takes in a pure value and returns a contextual value. This type signature isn’t quite right for a `Functor`

, and it’s not quite right for an `Applicative`

. What is it?

```
class Applicative f => Monad f where
(>>=) :: f a -> (a -> f b) -> f b
```

This function, which we’ll call “bind,” takes in a contextual value, and a function (like `putChar`

) from a pure value to a contextual value, and returns a contextual value. Using it might look like this:

```
$ pure 'a' >>= putChar
```

`pure 'a'`

gives us a value of type `IO Char`

, and `putChar`

has the type `Char -> IO ()`

. These match up perfectly with “bind”! So `IO`

is a monad!

With the monad, we have the ability to sequence operations together! To show this, let’s write a little helper function, which we’ll call “sequence”:

```
(>>) :: Monad f => f a -> f b -> fb
(>>) x y = x >>= (\_ -> y)
```

This function says to run `x`

first, throw away the result, and then run `y`

. So, if we wanted to print multiple characters, it could look this this!

```
$ (pure 'a' >>= putChar) >> (pure 'b' >>= putChar)
```

In fact, Haskell provides a convenient notation for sequencing `IO`

operations, that desugars into exactly this!

```
main = do
x <- getLine
putStrLn ("You typed: " ++ x)
```

This turns into:

```
main = getLine >>= (\x -> putStrLn ("You typed: " ++ x))
```

None of this is specific to `IO`

! We can actually use it for any type that implements the `Monad`

interface, like `Maybe`

or `[]`

(the list type).

### Conclusion

So, we make sure effects can only happen inside a particular context by having all the functions with effects only operate in that context. Then we use `Functor`

, `Applicative`

, and `Monad`

to make working with those contexts nice and easy, giving us the ability to operate on contextual values (with `Functor`

), sequence contextual operations (with `Applicative`

), and have that sequencing carry values (with `Monad`

). Collectively, these provide an extremely strong and useful interface for controlling effects.

**For this lab, I want something a little different. Please type a response to the following question: “what makes Functor, Applicative, and Monad different? Why are all Applicatives inherently Functors, and why are all Monads inherently Applicatives (and therefore **

*also*Functors). Email this typed response to me, as a PDF, before the next lab.”