Created
June 23, 2016 20:08
-
-
Save mgwidmann/c30a9a795cd20634f7ca31f8314689a8 to your computer and use it in GitHub Desktop.
Showing an example of using the with keyword & usage/output of dialyzer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Assuming we have some code like this | |
defmodule Processor do | |
# A lot of code uses the following format, and its a good way to write code | |
# However, when we have potential failures at each step things get complicated | |
def process(user, meta) do | |
user | |
|> verify_authenticity(meta[:credentials]) | |
|> process_payment(meta[:charges]) | |
|> issue_resource | |
|> generate_response | |
end | |
# A very common error of preventing exceptions is to return atoms indicating the status | |
# of the data, typically in the form {:ok, data} or {:error, reason} format. | |
def verify_authenticity(user, _credentials) do | |
if(:random.uniform > 0.50, do: {:ok, user}, else: {:error, "Credentials are incorrect"}) | |
end | |
def process_payment(err = {:error, reason}, _charges), do: err | |
def process_payment({:ok, user}, _charges) do | |
if(:random.uniform > 0.50, do: {:ok, user}, else: {:error, "Payment failed"}) | |
end | |
def issue_resource(err = {:error, reason}), do: err | |
def issue_resource({:ok, _user}) do | |
if(:random.uniform > 0.50, do: {:ok, [some: :resource]}, else: {:error, "Could not issue resource"}) | |
end | |
def generate_response(err = {:error, reason}), do: err | |
def generate_response({:ok, resource}) do | |
[resource: resource] # If we got here its all good | |
end | |
end | |
# To remove the unneccessary functions handling errors, since they never should have been called anyhow, we can use with | |
defmodule WithProcessor do | |
# Although its a very small use case, piping code that has potential for failure becomes problematic | |
# to maintain because of the necessary to add both the :ok/:error version for each function, even if | |
# all you do is return the error once an error has occured. | |
# Elixir 1.2 added the `with` keyword with a lighter feature set. A few problems still remained. | |
# 1. Guards were not possible so if a guard was necessary in any statement, `with` could not be used | |
# 2. The result of the with expression would still need to be processed. This gave in to the else clause | |
# of the `with` keyword For example: | |
# with ..., | |
# ..., # statements here | |
# do | |
# result | |
# end |> case do # Result could not be either {:ok, data} or {:error, reason} | |
# {:ok, data} -> {:ok, data} # Often just returned and did nothing else | |
# {:error, reason} -> handle_error(reason) # Error handling here | |
# end | |
def process(user, meta) do | |
with :ok <- verify_authenticity(user, meta[:credentials]), | |
:ok <- process_payment(user, meta[:charges]), | |
{:ok, resource} when is_list(resource) <- issue_resource(user), | |
resource <- generate_response(resource) | |
do | |
resource | |
else | |
{:error, reason} -> | |
IO.puts "We were unable to process your request for the following reason: #{reason}" | |
nil | |
end | |
end | |
def verify_authenticity(_user, _credentials) do | |
if(:random.uniform > 0.50, do: :ok, else: {:error, "Credentials are incorrect"}) | |
end | |
def process_payment(_user, _charges) do | |
if(:random.uniform > 0.50, do: :ok, else: {:error, "Payment failed"}) | |
end | |
def issue_resource(_user) do | |
if(:random.uniform > 0.50, do: {:ok, [some: :resource]}, else: {:error, "Could not issue resource"}) | |
end | |
def generate_response(resource) do | |
[resource: resource] # If we got here its all good | |
end | |
end | |
# Dialyzer | |
defmodule DialyzeExamples do | |
# http://elixir-lang.org/docs/v1.0/elixir/Kernel.Typespec.html | |
def argument_error(), do: {} + %{} | |
def call_pattern(), do: call_pattern(:ok) | |
def call_pattern(ok) when is_list(ok), do: :ok | |
def multiple_levels(start = nil) do | |
level_one(start) | |
end | |
defp level_one(data) do | |
level_two({:here, :is, data}) | |
end | |
defp level_two(info) do | |
elem(info, 2) |> String.upcase | |
end | |
@spec underspec() :: binary() | number() | |
def underspec() do | |
"some binary" # cant return a number | |
end | |
end | |
# After adding the dialyze project to your mix.exs file | |
# https://github.com/fishcakez/dialyze | |
# Then run the following to see output | |
$ mix dialyze |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment