This post is not about what monads are, I expect that you already know about them. The post is to see how you can use monads in Elixir using already created libraries.

Monads are a great way of handling side effect in functional language. It makes the code much more readable, maintainable and composable. Elixir language is not bundled with monads but it has an even more powerful construct i.e. meta programming. Using metaprogramming you can add monads in your program and use it in such a way that they feel like part of the language itself.

There are multiple monad libraries available in Elixir. I explored two libraries which I think had the most complete implementation (relatively) of Monads those were Monad and MonadEx. Both are great libraries. I think the MonadEx is a more complete library with the required functions to use monad. I've been using rmies/monad because the macro provided by rmies/monad feels more like part of the language as they chain with |> , in case of rob-bron/MonadEx you have to create anonymous functions to chain.

We will explore rmies/monad library in this post and see how to use the following monads

Getting dependency: There is some issue in the rmies/monad library, so the examples here won't work with that for now. I've fixed the issue, you should include the following dependency for now till I send the pull request and fix goes to the main repo.

{:monad, git: "https://github.com/zabirauf/monad.git", branch: "develop"}

Operators

Bind

The bind operator is not >>= like Haskell but instead it's |> . The pipe operator only works as bind operator in a specific block (which I'll discuss). The type of bind operator is

f a -> (a -> f b) -> f b

It takes a wrapped value f a and a function which takes a value and returns a wrapped value a -> f b . Bind applies value in the wrapper to that function and gets another wrapped value.

It currently does not have operators for Functor.fmap and Applicative.apply but if you are interested in that, it's available in the MonadEx library.

Monads

Lets see some example of all the monads provided by MonadEx library

Maybe:

Maybe monad is the simplest where the return value is either something or nothing. It is represented as

# In case of something {:just, some_value} # In case of nothing :nothing

But you don't have to create those structs yourself, there are functions to do that.

defmodule ExampleMaybe do require Monad.Maybe, as: Maybe import Maybe # The division operator which return something if successfull # otherwise in case of failure returns nothing def my_div(_numerator,0) do # Returns nothing Maybe.fail(nil) end def my_div(numerator, denominator) do # Returns {:something, result} Maybe.return(numerator/denominator) end # Sum always returns something def my_sum(a,b) do # Returns {:just, result} Maybe.return(a+b) end def output(x) do case is_nothing(x) do true -> IO.puts "No value gotten" false -> x |> from_just |> IO.puts end end # A successful scenario def scenario1() do # The |> opeartor in Maybe.p acts as bind val = Maybe.p do Maybe.return(100) |> my_sum(50) |> my_div(2) end output(val) # Outputs "75.0" end # A scenario where division by zero is done def scenario2() do val = Maybe.p do Maybe.return(0) |> my_sum(0) |> (&my_div(100, &1)).() end output(val) # Outputs "No value gotten" end end

You use the Monad.p do .. end block to use the bind operator and all the functions are chained using |> which becomes bind operator in this block. The functions used in it should return either {:just, something} created by Maybe.return(something) or return :nothing using Maybe.fail(nil) .

Error

The Error monad is very similar to Maybe monad but instead of nothing you return an error with a reason. It is represented as

# In case of a valid value {:ok, value} # In case of error {:error, error_reason}

The Error monad is missing required functions to extract the value or error from the structure, so we have to depend directly on the structure.

Lets see and example of it which is very similar to Maybe monad example.

defmodule ExampleError do require Monad.Error, as: Error import Error # The division operator which return ok and result if successfull # otherwise in case of failure returns error with reason def my_div(_numerator,0) do # Returns {:error, reason} Error.fail("Division by zero is not allowed") end def my_div(numerator, denominator) do # Returns {:ok, result} Error.return(numerator/denominator) end def my_sum(a,b) do # Sum always returns {:ok, result} Error.return(a+b) end def output(x) do case x do {:error, reason} -> IO.puts "Error: #{reason}" {:ok, value} -> IO.puts value end end # Successful scenario def scenario1() do # Here the |> opeartor in Error.p acts as bind operator val = Error.p do Error.return(100) |> my_sum(50) |> my_div(2) end output(val) # Outputs "75.0" end # Scenario where division fails due to division by zero def scenario2() do val = Error.p do Error.return(0) |> my_sum(0) |> (&my_div(100, &1)).() end output(val) # Outputs "Error: Division by zero is not allowed" end end

The functions used for Error monad should return either {:ok, something} created by Error.return(something) or return {:error, reason} using Error.fail(reason) .

This monad is same as the one that I recreated in the post Railway Oriented Programming in Elixir. You can use this instead of recreating and hence making error handling so much easier and elegant.

Reader

Reader monad is used to pass around a context across all of your function composition. Examples of reader monad include

Dependency injection, where you want to pass an external dependency across all your functions.

Passing in configuration

Passing in user request

The reader monad passes whatever you want silently i.e. you don't have to make it an argument and you can get its value anytime in the function. Lets see an example of it

defmodule ExampleReader do require Monad.Reader, as: Reader import Reader # We create a greeting string by getting name as argument # and getting greeting from the reader monad def greeting(name) do # The value for the reader can be read in Reader.m block Reader.m do # Getting the greeting by calling ask function greeting <- ask return "#{greeting}, #{name}" end end # If the greeting is hello it puts exclamation mark # otherwise adds . at end of string def done(input) do Reader.m do greeting <- ask case (greeting == "Hello") do true -> return "#{input} !!!" false -> return "#{input}." end end end # Adds a new line to whatever string it gets def add_newline(input) do return("#{input}

") end # Outputs "Hello, Zohaib !!!

" def scenario1() do # You use the run function to call put the value and # execute the functions with that value in context. # # You compose the functions by using |> operator # which acts as bind operator in Reader.p block run("Hello", Reader.p do return("Zohaib") |> greeting |> done |> add_newline end) end # Outputs "Welcome, Zohaib.

" def scenario2() do run("Welcome", Reader.p do return("Zohaib") |> greeting |> done |> add_newline end) end end

Here we use return from the functions which we want in the composition. If we want to read the value of the context then under the Reader.m do .. end block we ask for its value by variable_name <- ask , simple as that. To compose the functions we use the Reader.p do .. end block and use the |> pipe operator to compose the functions. We use the run function where the first argument is what is passed across the functions and the second argument is a function or composition of functions.

Writer

The writer monad is just what the name suggests i.e. it allows you to write some values. A good example of using writer monad is to have logs with each operation that you do.

Lets see the example

defmodule ListWriter do use Monad.Writer # Called when the writer monad is started # to initialize def initial do [] end # Called whenever you put new value to the writer def combine(new, acc) do acc ++ new end end defmodule ExampleWriter do import ListWriter # We return the sum and write logs to the writer def my_sum(a,b) do # If you want to write then you have to # call tell in your created writers m block, # As we defined ListWriter so in this # case its ListWriter.m ListWriter.m do tell ["Adding #{a} and #{b}"] return a+b end end # We return the subtraction and write logs to the writer def my_subtraction(a,b) do ListWriter.m do tell ["Subtracting #{a} and #{b}"] return a-b end end # Outputs "{8, ["Adding 5 and 10", "Subtracting 15 and 7"]}" def scenario1() do # We run the writer monad by calling run # and pass in the function or composition of function # # The |> operators becomes bind in p block of your # writer. As we defined ListWriter so its ListWriter.p # block here run(ListWriter.p do return(5) |> my_sum(10) |> my_subtraction(7) end) end end

Here we have to create a module that implements some methods required by the writer monad. In the above example we have created ListWriter and that implements two required functions initial and combine(new, acc) . In initial you initialize the structure to which values will be written and in combine(new,acc) you write the new value to the acc accumulated values. This module should be defined external to the module in which it is being used.

The way to use it is that in the functions you use the ListWriter.m do .. end block and in that you use tell to write the value, this in turn will call the combine function where you add that value to your structure.

The functions should use return to return whatever they want. You can compose function by chaining them using |> pipe operator in ListWriter.p do .. end block.

The output of scenario1 is {8, ["Adding 5 and 10", "Subtracting 15 and 7"]} where the first elment is the result of your functions and second element is the list of logs you wrote.

State

State monad is similar to Reader monad but in it along with reading the value you can also write the value hence maintaining side effect across functions in a more functional and maintainable way.

defmodule ExampleState do require Monad.State, as: State import State # Returns sum and increments state def my_sum(a,b) do # Use get to read the state and # use put to write the state. These # functions should be called in State.m block State.m do x <- get put x+1 return a+b end end # Returns subtraction and increments state def my_subtraction(a,b) do State.m do x <- get put x+1 return a-b end end # Outputs "{8, 2}" def scenario1() do # Call run to run the state monad run(0, State.p do return(5) |> my_sum(10) |> my_subtraction(7) end) end end

In this monad we use the State.m do .. end block in the functions where we want to read and write the state. We call variable_name <- get to get the state and put new_state to update the state. In the above example we pass 0 and increment it as the functions are called. In the end the output of scenario1 is {8, 2} where the first value is the output of the composition and second is the state at the end.

You can get all these example from the git repo

Reference

Follow @zabirauf