Slide 1

Slide 1 text

EMPEX HALLOWEEN Railway Programming in Elixir with with Chris Bell • @cjbell_

Slide 2

Slide 2 text

<3 |>

Slide 3

Slide 3 text

Allows us to easily express a set of combined steps and operations on data

Slide 4

Slide 4 text

def process_checkout(order) do order |> update_order() |> capture_payment() |> send_notification_email() |> update_stock_levels()
 end

Slide 5

Slide 5 text

But what about errors?

Slide 6

Slide 6 text

We use tagged tuples to represent success and error states.

Slide 7

Slide 7 text

{:ok, _} | {:error, _}

Slide 8

Slide 8 text

How do we stop the execution of these functions if one fails?

Slide 9

Slide 9 text

Update Order Capture Payment Send Email Update Stock process_checkout Order {:ok, order} {:error, reason}

Slide 10

Slide 10 text

On success we pass through to the next function, on error we switch.

Slide 11

Slide 11 text

Scott Wlaschin’s Railway Orientated Programming http://fsharpforfunandprofit.com/rop/

Slide 12

Slide 12 text

So how do we implement this in Elixir?

Slide 13

Slide 13 text

defp update_order({:ok, order}) do # Do logic
 {:ok, order} end
 defp update_order({:error, reason}) do
 # Pass through {:error, reason} end
 # Repeat for every method

Slide 14

Slide 14 text

Lets do this a different way…

Slide 15

Slide 15 text

This is Haskell the dog and Monad the bear

Slide 16

Slide 16 text

defmodule Result do def bind({:ok, value}, func) do func.(value) end def bind({:error, _} = failure,_) do 
 failure
 end end

Slide 17

Slide 17 text

import Result
 
 def process_checkout(order) do order |> bind(&update_order/1) |> bind(&capture_payment/1) |> bind(&send_notification_email/1) |> bind(&update_stock_levels/1)
 end

Slide 18

Slide 18 text

import Result
 
 def process_checkout(order) do order |> bind(&update_order/1) |> bind(&capture_payment/1) |> bind(&send_notification_email/1) |> bind(&update_stock_levels/1)
 end Put down that Monad, theres another way.

Slide 19

Slide 19 text

with

Slide 20

Slide 20 text

Allows us to execute a set of clauses only if the previous clause matches

Slide 21

Slide 21 text

def process_checkout(order) do with {:ok, order} <- update_order(order), {:ok, order} <- capture_payment(order), {:ok, order} <- send_notification_email(order), {:ok, order} <- update_stock_levels(order), do: {:ok, order}
 end

Slide 22

Slide 22 text

If the clause doesn’t match, the non matching value is returned and all subsequent steps are skipped.

Slide 23

Slide 23 text

Update Order Capture Payment Send Email Update Stock process_checkout Order {:ok, order} {:error, reason}

Slide 24

Slide 24 text

As of Elixir 1.3 we can also use an else block to handle non- matching clauses

Slide 25

Slide 25 text

def process_checkout(order) do with {:ok, order} <- update_order(order), {:ok, order} <- capture_payment(order), {:ok, order} <- send_notification_email(order), {:ok, order} <- update_stock_levels(order) do 
 {:ok, order} else {:error, %Changeset{} = cs} -> handle_error(cs) {:error, reason} -> handle_other_error(reason) end
 end

Slide 26

Slide 26 text

Ensign Bell’s top tip: 
 Usage with Ecto.Repo.transaction

Slide 27

Slide 27 text

def process_checkout(order) do Repo.transaction(fn ->
 do_process_checkout(order)
 |> case do
 {:ok, order} -> order
 {:error, cs} -> Repo.rollback(cs)
 end
 end)
 end
 
 def do_process_checkout(order) do
 # Everything here returns a Ecto.Changeset with {:ok, order} <- update_order(order), {:ok, order} <- capture_payment(order), {:ok, order} <- send_notification_email(order), {:ok, order} <- update_stock_levels(order),
 do: {:ok, order}
 end

Slide 28

Slide 28 text

Thank you. Questions? chris@madebymany.com