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_

    View full-size slide

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

    View full-size slide

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

    end

    View full-size slide

  4. But what about errors?

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  11. So how do we implement this
    in Elixir?

    View full-size slide

  12. 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

    View full-size slide

  13. Lets do this a different way…

    View full-size slide

  14. This is Haskell the dog
    and Monad the bear

    View full-size slide

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

    failure

    end
    end

    View full-size slide

  16. 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

    View full-size slide

  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
    Put down that Monad,
    theres another way.

    View full-size slide

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

    View full-size slide

  19. 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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  23. 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

    View full-size slide

  24. Ensign Bell’s top tip: 

    Usage with
    Ecto.Repo.transaction

    View full-size slide

  25. 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

    View full-size slide