Skip to content

Instantly share code, notes, and snippets.

@fxg42
Last active January 21, 2022 12:48
Show Gist options
  • Save fxg42/ef243cd17869df99a87c to your computer and use it in GitHub Desktop.
Save fxg42/ef243cd17869df99a87c to your computer and use it in GitHub Desktop.
Maybe monad with an Elixir macro dsl
defmodule Optional do
defmacro __using__(_opts) do
quote do
require unquote(__MODULE__)
import unquote(__MODULE__)
end
end
def unit(nil), do: {:err, nil}
def unit(value), do: {:ok, value}
def lift(function) do
fn input -> unit(function.(input)) end
end
def bind({:ok, optional}, functor), do: functor.(optional)
def bind(err, _), do: err
defmacro pipe_into(input, [do: {_, _, transformations}]) do
transformations
|> Enum.map(&lift_ast/1)
|> Enum.reduce(unit_ast(input), &reduce_bind_ast/2)
end
defp lift_ast({:+, _, [fn_ref]}) do
quote do: lift(unquote(fn_ref))
end
defp lift_ast(fn_ref) do
fn_ref
end
defp unit_ast(val_ast) do
quote do: unit(unquote(val_ast))
end
defp reduce_bind_ast(fn_ref, acc) do
quote do: bind(unquote(acc), unquote(fn_ref))
end
end
defmodule Session do
@state %{"a" => 1, "b" => 2}
def map_to_database_id(idParam), do: @state[idParam]
end
defmodule Repository do
def find_by_id(_id), do: %{:name => "world", :greeting => "hello"}
end
defmodule Factory do
def serialize(record), do: Poison.encode(record)
end
defmodule Controller do
use Optional
def find_by_id(idParam) do
if idParam != nil do
id = Session.map_to_database_id(idParam)
if id != nil do
record = Repository.find_by_id(id)
if record != nil do
reply Factory.serialize(record)
else
reply {:err, status: 404}
end
else
reply {:err, status: 400}
end
else
reply {:err, status: 400}
end
end
# bind(bind(bind(unit(idParam), lift(map_to_database_id)), lift(find_by_id), serialize)
def find_by_id_with_monad(idParam) do
unit(idParam)
|> bind(lift(&Session.map_to_database_id/1))
|> bind(lift(&Repository.find_by_id/1))
|> bind(&Factory.serialize/1)
|> reply
end
def find_by_id_with_dsl(idParam) do
pipe_into(idParam) do
(+ &Session.map_to_database_id/1)
(+ &Repository.find_by_id/1)
&Factory.serialize/1
end
|> reply
end
defp reply({:ok, payload}) do
%{status: 200, payload: payload}
end
defp reply({:err, _}) do
%{status: 500}
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment