Packages

Happy path programming in elixir. Alternative to ok_jose, elixir's 1.2 `with` keyword and that kind of stuff.

Current section

Files

Jump to
happy lib happy.ex
Raw

lib/happy.ex

defmodule Happy do
@moduledoc """
Happy path programming in elixir.
"""
# happy block with at least two expressions
# using custom unhappy path
@doc false
defmacro happy_path([do: block = {:__block__, _, xs = [a, b | c]},
else: unhappy]) do
if Enum.any?(xs, &pattern_match?/1) do
make_happy(a, b, c, unhappy)
else
block
end
end
# happy block with at least two expressions
# using default unhappy path
@doc false
defmacro happy_path([do: block = {:__block__, _, xs = [a, b | c]}]) do
if Enum.any?(xs, &pattern_match?/1) do
make_happy(a, b, c, [])
else
block
end
end
@doc false
defmacro happy_path([do: expr, else: _]), do: expr
@doc false
defmacro happy_path([do: expr]), do: expr
# append unhappy path to case when no more expressions remain
defp make_happy({:case, m = [happy_path: true], [e, [do: xs]]}, [], unhappy) do
{:case, m, [e, [do: xs ++ unhappy]]}
end
defp make_happy(a, [], _u), do: a
defp make_happy(a, [b | xs], u), do: make_happy(a, b, xs, u)
# create nested case expression when two consecutive match found
defp make_happy(a = {:=, _, _}, b = {:=, _, _}, xs = [_ | _], u) do
make_happy(a, [make_happy(b, xs, u)], u)
end
# create a case expression from a to b and continue with rest expressions
defp make_happy(a = {:=, _, _}, b, xs, u) do
{e, p} = pattern_match(a)
quote do
case(unquote(e)) do
unquote(p) -> unquote(b)
end
end |> happy_form |> make_happy(xs, u)
end
#
defp make_happy({:when, _, [p, {:=, _, [w, e]}]}, b, xs, u) do
quote do
case(unquote(e)) do
unquote(p) when unquote(w) -> unquote(b)
end
end |> happy_form |> make_happy(xs, u)
end
# create another nested case when another pattern matching found in chain
defp make_happy(a = {:case, [happy_path: true], _}, b = {:=, _, _}, xs, u) do
make_happy(a, [make_happy(b, xs, u)], u)
end
# append `b` expression to current block case(p -> ax)
defp make_happy({:case, [happy_path: true],
[e, [do: [{:->, _, [[p], {:__block__, _, ax}]}]]]},
b, xs, u) do
quote do
case(unquote(e)) do
unquote(p) ->
unquote_splicing(ax)
unquote(b)
end
end |> happy_form |> make_happy(xs, u)
end
# create a block by appending `b` expression to current case(p -> a)
defp make_happy({:case, [happy_path: true], [e, [do: [{:->, _, [[p], a]}]]]},
b, xs, u) do
quote do
case(unquote(e)) do
unquote(p) ->
unquote(a)
unquote(b)
end
end |> happy_form |> make_happy(xs, u)
end
# create a block with `a` and `b` and continue with chain
defp make_happy(a, b, xs, u) do
quote do
unquote(a)
unquote(b)
end |> make_happy(xs, u)
end
# mark a form with happy metadata
defp happy_form({x, m, y}) do
{x, [happy_path: true] ++ m, y}
end
# is the given form a pattern match?
defp pattern_match?({:when, _, [_, {:=, _, [_, _]}]}), do: true
defp pattern_match?({:=, _, [_, _]}), do: true
defp pattern_match?(_), do: false
# a = b = c
defp pattern_match({:=, l, [a, {:=, m, [b, c]}]}) do
pattern_match({:=, l, [{:=, m, [a, b]}, c]})
end
defp pattern_match({:=, _, [a, b]}), do: {b, a}
end