Skip to content

Instantly share code, notes, and snippets.

@kisom
Last active August 29, 2015 14:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kisom/04a08216d821f1cbeb23 to your computer and use it in GitHub Desktop.
Save kisom/04a08216d821f1cbeb23 to your computer and use it in GitHub Desktop.
Source file for the monad post.
(This post is a Literate Haskell file that was run through pandoc to
produce markdown).
It seems customary in the Haskell space to write an introduction to
monads; my attempt in this exposition is not to outdo anyone else, but
to make the topic clear enough that I can write about it. My hope is
that by doing so, it becomes more clear to me.
> module Perhaps where
At this point in my Haskell education, I am halfway through **Learn
You A Haskell** (to the point of having covered functors, but not yet
applicative functors or monads), and a quarter of the way through
**Real World Haskell**. For me to learn a language requires solving
problems, and along the way I've encountered monads.
Yesterday, I watched Brian Beckman's excellent "[Don't Fear the
Monad](http://www.youtube.com/watch?v=ZhuHCtR3xq8)" talk, and realised
I've been using a monad quite regularly in my experiments thus far:
the `Maybe` type **is** a monad. This led me to explore what makes
`Maybe` a monad and write some simple code to make it more apparent to
me.
Let's look at `Maybe`:
```haskell
ghci> :info Maybe
data Maybe a = Nothing | Just a -- Defined in `Data.Maybe'
instance Monad Maybe -- Defined in `Data.Maybe'
-- ... other instances follow
```
For the sake of exposition, I'll define my own `Maybe` called
`Perhaps`. `Nought` is the equivalent of `Nothing`, and `Only` is the
equivalent of `Just`.
> data Perhaps a = Nought | Only a deriving (Show)
A `Monad` is a type that implements two functions (actually, in
Haskell, it's four): `bind` and `return`.
Looking at the definition of `Monad`:
```haskell
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
return :: a -> m a
fail :: String -> m a
-- Defined in `GHC.Base'
```
The two easiest functions to implement are `fail` and `return`.
`fail` takes an error string and returns the equivalent of `Nothing.`
> perhapsFail :: String -> Perhaps a
> perhapsFail s = Nought
`return` is one of the important Monad functions. It takes a value of
type `a` and wraps it in the monad. Given the fact that we always have
a value for `return`, it never returns `Nought`.
> perhapsReturn :: a -> Perhaps a
> perhapsReturn x = Only x
The `(>>=)` operator is the Haskell `bind` operator. This is a bit
more tricky. It's type is `Monad m => m a -> (a -> m b) -> m b`. It's
used to **compose** monadic functions. Here is its definition:
> perhapsBind :: Perhaps a -> (a -> Perhaps b) -> Perhaps b
> perhapsBind Nought _ = Nought
> perhapsBind (Only x) f = f x
It takes a `Perhaps` value, and a function for transforming a value of
type `a` into a monad with type `b`.
The `(>>)` operator sequentially composes two monadic functions,
ignoring the result of the first action.
> perhapsShift :: Perhaps a -> Perhaps b -> Perhaps b
> perhapsShift _ x = x
Now, we can make `Perhaps` an instance of the `Monad` type class.
> instance Monad Perhaps where
> (>>=) = perhapsBind
> (>>) = perhapsShift
> return = perhapsReturn
> fail = perhapsFail
There are two rules for monads: they must be associative and they must
have an identity function. Due to Haskell's polymorphism, the identity
function is already provided. Associativity means that
```haskell
f x >>= g >>= h
```
should be the same as
```haskell
(f x >>= g) >>= h
```
should be the same as
```haskell
f x >>= (g >>= h)
```
Let's see this in action. The first function we'll send it to is a
function that returns `Nought` if the number is odd, and `Only n` if
`n` is even.
> perhapsEven :: Integer -> Perhaps Integer
> perhapsEven n
> | even n = Only n
> | odd n = Nought
Here are some examples of `perhapsEven`:
```haskell
ghci> map perhapsEven [0..5]
[Only 0,
Nought,
Only 2,
Nought,
Only 4,
Nought]
```
The next function returns a string of the number if it's given a
number that can be divided by (the number isn't zero):
> perhapsDividable :: Integer -> Perhaps String
> perhapsDividable 0 = Nought
> perhapsDividable n = Only (show n)
Some examples:
```haskell
ghci> map perhapsDividable [0..5]
[Nought,
Only "1",
Only "2",
Only "3",
Only "4",
Only "5"]
```
We can't just compose `perhapsEven` and `perhapsDividable`; their type
would be
```haskell
(Integer -> Perhaps Integer) -> (Integer -> Perhaps String)
```
The types don't line up. Instead,
> perhapsShowEvenDividable :: Integer -> Perhaps String
> perhapsShowEvenDividable n = perhapsEven n >>= perhapsDividable
`perhapsEven` has to be applied first to get to the `Perhaps` type,
then functions can be applied.
> perhapsStringLength :: String -> Perhaps Int
> perhapsStringLength s = Only (length s)
After the first application, we can compose an arbitrary number of
functions (provided the types match up):
```haskell
ghci> perhapsEven 3 >>= perhapsDividable >>= perhapsStringLength
Nought
ghci> perhapsEven 4 >>= perhapsDividable >>= perhapsStringLength
Only 1
```
So how is this useful?
Imagine `Perhaps` conveys whether a function with side effects
succeeded. Let's consider a pair of functions, `perhapsReadFile` and
`perhapsParseWidget`.
```haskell
perhapsReadFile :: FilePath -> Perhaps String
perhapsParseWidget :: String -> Perhaps Widget
```
`perhapsReadFile` either returns `Only contents` if it could read the
contents of the file, and `Nought` if nothing could be
read. `perhapsParseWidget` returns `Only w` if the string contains a
valid representation of a widget, and `Nought` otherwise.
In Go, we might write something like:
```go
func readAndUseWidget(fileName string) (w widget.Widget, err error) {
contents, err := ioutil.ReadFile(fileName)
if err != nil {
return nil, err
}
w, err := widget.Unmarshal(contents)
if err != nil {
return nil, err
}
// do some initial actions on the widget
return w, nil
}
```
In Haskell, it's
```haskell
readAndUseWidget :: FilePath -> Perhaps Widget
readAndUseWidget path = perhapsReadFile path >>= perhapsParseWidget
>>= perhapsInitWidget
```
It's much more concise, and the error handling can be abstracted out
of view. Monads allow us to safely abstract some of this away.
One useful benefit of this is allowing continued execution in the face
of failures.
If our `readAndUseWidget` wasn't using a monad, and we ran through a
sequence of files to process some widgets, an error would stop us dead
in our tracks. As an example, the following function will die if an
error occurs:
```haskell
readAndProcessWidgets :: [FilePath] -> [Widget]
readAndProcessWidgets = map readAndUseWidget
```
Given some paths to widget files, where `widget2.w` doesn't exist:
```haskell
ghci> readAndProcessWidgets ["widget1.w", "widget2.w", "widget3.w"]
[Widget Foo,*** Exception: widget2.w: openFile: does not exist (No
such file or directory)
```
We don't even get a complete list of widgets back. With the monad
version, we'd get
```haskell
ghci> map readAndUseWidget ["widget1.w", "widget2.w", "widget3.w"]
ghci> [Only Widget Foo, Nought, Only Widget Baz]
```
We get a list back, and can process the widgets that were successfully
parsed.
We've uesd the monad here to represent failure, but they can be used
to add any additional information to a result besides the pure
result. The `IO` monad carries information noting that the world the
Haskell program has been run in is unstable; the `State` monad carries
some notion of state (state is anathema to functional
programming). They carry information about computational
dependencies—knowing that the world has changed, or the
possibility that a component failed—through a sequence of
computations.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment