Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Showing an example of using the with keyword & usage/output of dialyzer
# 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
You can’t perform that action at this time.