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

Railway Oriented Programming in Elixir

Railway Oriented Programming in Elixir

Error handling techniques in Elixir

Mustafa Turan

March 28, 2017
Tweet

More Decks by Mustafa Turan

Other Decks in Programming

Transcript

  1. Summary Railway Oriented Programming is a clean functional approach for

    handling errors - Error Handling in Elixir - Error Handling with Railway Oriented Programming Approach - Tip: The bang! - Questions
  2. Error Handling in Elixir - Let it crash? - Try

    Rescue After blocks - Try Catch blocks - Trap exit signal - Information Tuples with Conditionals - {:error, …} or {:ok, …} - If else / case cond do - Railway Oriented Programming - WITH Clause (Elixir 1.2) - Pattern Matching in Function Level
  3. Let it crash? Ariane 5 flight 501 (1996) - Engines

    exploited by a bug - that was not found in previous models :) - Do you want to cause an exploit?
  4. Let it crash? The purpose of ‘Let it Crash!’ -

    Fresh start - Generally should not save the state and reload - Consider it like a restart your computer - Sometimes save data - Sometimes not - Ask this question to yourself: - Is restarting the process solve my problem, or NOT? - Samples (it depends!): - DB connection error, crash, then restart on init - Retryable
  5. Raise Errors Simple Raise iex> raise "Oh no error is

    coming!" ** (RuntimeError) Oh no error is coming! Simple Raise with Type iex> raise ArgumentError, message: "Invalid arg!" ** (ArgumentError) Invalid arg! Simple Raise with Type defmodule SampleError do defexception message: "this is special one" end
  6. try do opts |> Keyword.fetch!(:file_path) |> File.read! |> SampleModule.just_do_it! rescue

    e in KeyError -> IO.puts "missing :file_path option" e in File.Error -> IO.puts "unable to read file" e in SampleError -> IO.inspect e end Rescue Errors Catch With Pattern Matching in Rescue Clause
  7. try do opts |> Keyword.fetch!(:file_path) rescue e in KeyError ->

    IO.puts "missing :file_path option" after IO.puts "I will print at both case!" end Rescue Errors and After Need something to execute on both failure and success cases - Increment metrics ?
  8. Throw and Catch - Not common - You can even

    catch the ‘exit’ signals - You can pass the value and fetch that value on catch block try do for x <- 0..10 do if x == 2, do: throw(x) IO.puts(x) end catch x -> "Caught: #{x}" end 0 1 "Caught: 2"
  9. Trap Exit def handle_info({:EXIT, _pid, reason}, state), do: {:stop, reason,

    state} def terminate(reason, state) do # do sth in here :ok end Inside your init() function: Process.flag(:trap_exit, true) Inside your module:
  10. ‘If Else’ and ‘Case Cond’ Clauses - No ‘return’ clause

    in Elixir - Simple Checks - email_exists?(email) - Complex Controls (Sample: User.Login) - validate_email_exists() - validate_email_password_match() - validate_email_confirmation() - validate_one_time_password() - insert_session_token()
  11. Nested Conditions / If Else and Case Cond maybe_user =

    fetch_user(email) case maybe_user do {:ok, user} -> case password_match(user, password) do {:ok, true} -> case is_confirmed?(user) do ... end {:error, msg} -> {:error, msg} end {:error, msg} -> {:error, msg} end
  12. Railway Oriented Programming email_exists? pwd_correct? email_confirm? otp_valid? insert_session Sample: User

    login with several checks result input email_exists? pwd_correct? email_confirm? otp_valid? insert_session result input
  13. Railway Oriented Programming with ‘WITH’ ‘with’ clause - Matches patterns

    with result of the function - if matches executes next - Else executes else block
  14. Railway Oriented Programming with ‘WITH’ with {:ok, user} <- fetch_user(email),

    {:ok, true} <- password_match(user, password), {:ok, ...} <- … do sth() else {:error, error} -> handle_me(error) end
  15. Railway Oriented Programming with Pattern Matching on Function Level and

    Pipes Pattern matching - on function level Pipe operator - to pass result of function to next function's first argument
  16. Railway Oriented Programming with Pattern Matching on Function Level and

    Pipes def process(params) do params |> validate_email_exists() |> validate_email_password_match() |> validate_email_confirmation() |> validate_one_time_password() |> insert_session_token() end defp validate_email_password_match({:error, opts}), do: {:error, opts} defp validate_email_confirmation({:error, opts}), do: {:error, opts} defp validate_one_time_password({:error, opts}), do: {:error, opts} defp insert_session_token({:error, opts}), do: {:error, opts} # You can also create a macro to create error catching functions automatically
  17. Tip: The bang! Not a rule BUT: - In Elixir

    generally functions has two forms - without bang! - some_function(....) - {:ok, result} - {:error, “Some message or any type of data”} - with bang! - some_function!(...) - result - Raise an error - Elixir ‘bang’ package - @bang {[list_of_func_name_arg_count_tuples], {CallbackModule, :callback_fn}} - https://github.com/mustafaturan/bang
  18. Tip: The bang! defmodule SomeBangModule do def raise_on_clause({:ok, some_val}), do:

    some_val def raise_on_clause({:error, err}), do: raise err end defmodule OtherModule do @bang {[do_sth: 1], {SomeBangModule, :raise_on_clause}} def do_sth({:ok, some_val}) do if some_val > 0, do: {:ok, “Good!”}, else: {:error, “Invalid”} end end