Skip to content

Instantly share code, notes, and snippets.

@jcelliott
Last active August 18, 2023 14:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jcelliott/db4d4d5af0ef409b799287658407e20a to your computer and use it in GitHub Desktop.
Save jcelliott/db4d4d5af0ef409b799287658407e20a to your computer and use it in GitHub Desktop.
Small Elixir implementation of Railway Oriented Programming
defmodule Railway do
@moduledoc """
Railway implements the ideas from this blog post: https://zohaib.me/railway-programming-pattern-in-elixir/
It is essentially an implementation of the 'Either' monad constrained to the Elixir :ok/:error
result tuple convention. It can replace many straightforward uses of a 'with' statement.
Use it like this:
```
import Railway
value
|> function_that_might_fail()
~> handle_success() # will only be called if the previous result was '{:ok, _}'
~> do_something_else()
```
"""
# credo:disable-for-next-line Credo.Check.Readability.FunctionNames
defmacro left ~> right do
quote do
(fn ->
case unquote(left) do
{:ok, x} ->
x |> unquote(right)
{:error, err} ->
{:error, err}
other ->
raise """
Railway used with unexpected result format.
Result should be in the format '{:ok, result}' or '{:error, reason}'.
Got #{inspect(other)}
"""
end
end).()
end
end
@doc ~S"""
Works like the unix `tee` command. It will pass the input unchanged to the
next step in the pipeline, while also passing it to the called function
Examples:
iex> import Railway
iex> "apple" |> tee(String.upcase())
{:ok, "apple"}
iex> {:ok, "apple"} ~> tee(String.upcase())
{:ok, "apple"}
"""
defmacro tee(args, func) do
quote do
(fn ->
unquoted_args = unquote(args)
unquoted_args |> unquote(func)
{:ok, unquoted_args}
end).()
end
end
@doc ~S"""
Wraps a value in an {:ok, value} tuple.
Examples:
iex> import Railway
iex> "apple" |> String.upcase() |> ok()
{:ok, "APPLE"}
"""
defmacro ok(value) do
quote do
{:ok, unquote(value)}
end
end
@doc ~S"""
Wraps a value in an {:ok, value} tuple.
Examples:
iex> import Railway
iex> "apple" |> ok(String.upcase)
{:ok, "APPLE"}
"""
defmacro ok(args, func) do
quote do
(fn ->
result = unquote(args) |> unquote(func)
{:ok, result}
end).()
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment