Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Railway Programming in Elixir with with

Chris Bell
October 27, 2016

Railway Programming in Elixir with with

Slides from my talk at the Empire City Halloween event.

Chris Bell

October 27, 2016
Tweet

More Decks by Chris Bell

Other Decks in Technology

Transcript

  1. EMPEX HALLOWEEN Railway Programming in Elixir with with Chris Bell

    • @cjbell_
  2. <3 |>

  3. Allows us to easily express a set of combined steps

    and operations on data
  4. def process_checkout(order) do order |> update_order() |> capture_payment() |> send_notification_email()

    |> update_stock_levels()
 end
  5. But what about errors?

  6. We use tagged tuples to represent success and error states.

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

  8. How do we stop the execution of these functions if

    one fails?
  9. Update Order Capture Payment Send Email Update Stock process_checkout Order

    {:ok, order} {:error, reason}
  10. On success we pass through to the next function, on

    error we switch.
  11. Scott Wlaschin’s Railway Orientated Programming http://fsharpforfunandprofit.com/rop/

  12. So how do we implement this in Elixir?

  13. 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
  14. Lets do this a different way…

  15. This is Haskell the dog and Monad the bear

  16. defmodule Result do def bind({:ok, value}, func) do func.(value) end

    def bind({:error, _} = failure,_) do 
 failure
 end end
  17. 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
  18. 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.
  19. with

  20. Allows us to execute a set of clauses only if

    the previous clause matches
  21. 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
  22. If the clause doesn’t match, the non matching value is

    returned and all subsequent steps are skipped.
  23. Update Order Capture Payment Send Email Update Stock process_checkout Order

    {:ok, order} {:error, reason}
  24. As of Elixir 1.3 we can also use an else

    block to handle non- matching clauses
  25. 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
  26. Ensign Bell’s top tip: 
 Usage with Ecto.Repo.transaction

  27. 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
  28. Thank you. Questions? chris@madebymany.com