Packages

Some convenient macros to enable railsway-oriented programming in Elixir

Current section

Files

Jump to
rop lib rop.ex
Raw

lib/rop.ex

# https://gist.github.com/zabirauf/17ced02bdf9829b6956e
# https://github.com/remiq/railway-oriented-programming-elixir
defmodule Rop do
defmacro __using__(_) do
quote do
import Rop
end
end
@doc ~s"""
Extracts the value from a tagged tuple like {:ok, value}
Raises the value from a tagged tuple like {:error, value}
Raise the arguments else
For example:
iex> ok({:ok, 1})
1
iex> ok({:error, "some"})
** (RuntimeError) some
iex> ok({:anything, "some"})
** (ArgumentError) raise/1 expects an alias, string or exception as the first argument, got: {:anything, "some"}
"""
def ok({:ok, x}), do: x
def ok({:error, x}), do: raise x
def ok(x), do: raise x
@doc ~s"""
No need to stop pipelining in case of an error somewhere in the middle
Example:
iex> inc = fn(x)-> {:ok, x+1} end
iex> 1 |> (inc).() >>> (inc).()
{:ok, 3}
"""
defmacro left >>> right do
quote do
(fn ->
case unquote(left) do
{:ok, x} -> x |> unquote(right)
{:error, _} = expr -> expr
end
end).()
end
end
@doc ~s"""
Wraps a simple function to return a tagged tuple with `:ok` to comply to the protocol `{:ok, result}`
Example:
iex> 1 |> Integer.to_string
"1"
iex> 1 |> bind(Integer.to_string)
{:ok, "1"}
iex> inc = fn(x)-> x+1 end
iex> 1 |> bind((inc).()) >>> (inc).()
3
iex> 1 |> bind((inc).()) >>> bind((inc).())
{:ok, 3}
"""
defmacro bind(args, func) do
quote do
(fn ->
result = unquote(args) |> unquote(func)
{:ok, result}
end).()
end
end
@doc ~s"""
Wraps raising functions to return a tagged tuple `{:error, ErrorMessage}` to comply with the protocol
Example:
iex> r = fn(_)-> raise "some" end
iex> inc = fn(x)-> x + 1 end
iex> 1 |> bind((inc).()) >>> try_catch((r).()) >>> bind((inc).())
{:error, %RuntimeError{message: "some"}}
"""
defmacro try_catch(args, func) do
quote do
(fn ->
try do
unquote(args) |> unquote(func)
rescue
e -> {:error, e}
end
end).()
end
end
@doc ~s"""
Like a similar Unix utility it does some work and returns the input as output.
See [tee (command), Unix](https://en.wikipedia.org/wiki/Tee_(command)).
Example:
iex> inc = fn(x)-> IO.inspect(x); {:ok, x + 1} end
iex> 1 |> tee((inc).()) >>> tee((inc).()) >>> tee((inc).())
{:ok, 1}
"""
defmacro tee(args, func) do
quote do
(fn ->
unquote(args) |> unquote(func)
{:ok, unquote(args)}
end).()
end
end
end