Skip to content

Instantly share code, notes, and snippets.

@rainbyte
Last active October 25, 2017 17:51
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rainbyte/c88f6cd77eb9c4daecdddbe04fd0ce43 to your computer and use it in GitHub Desktop.
Save rainbyte/c88f6cd77eb9c4daecdddbe04fd0ce43 to your computer and use it in GitHub Desktop.
Some thoughts about monads

Monads Conceptually

Fundamental knowledge

Before talking about monads, we should review 3 concepts:

Functions

They appear in the form f :: a -> b, where :: indicates the type of f.

This means that f receives something of type a and gives something of type b.

Functions are one-to-one relations: when you give f some a, you obtain a b.

Those types, a and b, could be any types, even functions.

When you see g :: a -> b -> c, that really means g :: a -> (b -> c).

If you see h :: (a -> b) -> c, it means that h consumes a function and produces a c.

Concrete types and Type constructors

There are concrete types like Int, Char, Bool, etc.

We could mention values of those types (eg. 'a' :: Char).

There are other types like [a], Map a b, Maybe a, IO a, etc.

These types are not concrete, they have parameters, they construct types.

We have to say who are those a's and b's to name posible values.

After that, we can mention the values (eg. [1, 2, 3] :: [Int]).

Higher order types

Sometimes we want to have type constructors also as parameters.

If we have f :: t a -> a, we know that a can be anything.

But t is a type constructor, because it has a parameter.

For some t, t a could be [a], Tree a, Maybe a, etc.

The nature of a higher order type

When we talk about some t a, it is likely that t has some purpose.

  • Lists [a] are used to store things of type a, one after the other.

  • Trees like Tree a are also used to store data, with other structure.

  • A type Maybe a indicates if something of type a is present or not.

  • An IO a says that input/output will be done to produce an a.

We could mention even more examples, but I'm sure you got the idea.

This means for t a that:

  • There is a context whose nature depends on what t is.

  • The a values, if any, will be found in that context.

We will work inside t context, and take advantage of it.

Finally, Monads

We could think about Monads as some kind of programming design pattern.

Haskell let's us encode patterns, and enforce them with the compiler.

First we should know that a monad m defines at least two functions:

pure :: a -> m a
bind :: m a -> (a -> m b) -> m b

Let me break down them for you, first we can talk about the types:

  • pure and bind are functions.

  • a and b will be concrete types.

  • m is a type constructor (like the ones we mentioned before).

  • m surely will have its own meaning, its nature.

pure

Now let's talk about pure :: a -> m a.

pure receives an a to produce some m a.

This means that a is introduced into de context of m.

If m a is [a], then a could be stored in it.

If m a is Maybe a, it means that we have an a which exists.

If m a is IO a, we already have an a, then i/o operation is no needed.

As you can see, pure is a way to give a a meaning with respect to m.

bind

And what about bind :: m a -> (a -> m b) -> m b?

bind is the operation which gives power to the Monad, let's see why.

It receives a m a, which is a value a in the context of m.

This m a could be obtained using pure, as we saw previously.

It also has a function a -> m b, to finally produce an m b.

bind is implemented given a deep knowledge on the nature of m.

This means that bind knows how to obtain an a from the m a.

Also, at the same time, it knows how to analize the context of m.

What does it mean analize the context? It means take decisions.

Given the a and the context around it, the next step could be decided.

This is not a minor detail, using this power we could:

  • Execute the function a -> m b or produce an m b without it.

  • Decide to omit certain future operations.

  • Modify the value a in selected situations.

  • Modify the result m b before submit it.

  • Execute custom effects.

What does this mean for the types we saw before?

Lists [a]

In the case of [a], you can take decisions while inspecting or creating a list.

This means that you could omit or modify the list based on its own elements.

It is used to provide comprehensions, a powerful tool for working with lists.

Maybe a

This type represents the notion of the presence of some a.

As a monad it can omit areas of code where some a is not available.

And this is done without lots of if's or case's.

This is possible because bind itself encodes all this bookkeeping.

Think about the Maybe monad before writing nested conditionals.

It also gives a way to avoid null checks in other languages.

IO a

When m a is IO a, we have some specials powers given by the compiler.

Those are only available inside the context of m.

We can access it via the bind function, which knowns the context.

bind enforces various properties, so IO could not be used outside.

In this way, IO is separated of your pure functions.

But they can coexist thanks to the pure and bind functions.

What to do next?

Find places where you are using (or could use) type constructors.

See if you can find repeated patterns in all that code.

Try to understand the inner nature of those types.

Search if they have pure and bind implemented.

Try to use (or even implement) them by yourself.

Think cases where an enforced decision mechanism could help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment