Skip to content

Instantly share code, notes, and snippets.

@zabirauf
Created March 26, 2015 07:48
Show Gist options
  • Save zabirauf/17ced02bdf9829b6956e to your computer and use it in GitHub Desktop.
Save zabirauf/17ced02bdf9829b6956e to your computer and use it in GitHub Desktop.
Railway Oriented Programming macros in Elixir
defmodule ROP do
defmacro try_catch(args, func) do
quote do
(fn ->
try do
unquote(args) |> unquote(func)
rescue
e -> {:error, e}
end
end).()
end
end
defmacro tee(args, func) do
quote do
(fn ->
unquote(args) |> unquote(func)
{:ok, unquote(args)}
end).()
end
end
defmacro bind(args, func) do
quote do
(fn ->
result = unquote(args) |> unquote(func)
{:ok, result}
end).()
end
end
defmacro left >>> right do
quote do
(fn ->
case unquote(left) do
{:ok, x} -> x |> unquote(right)
{:error, _} = expr -> expr
end
end).()
end
end
end
@lkuty
Copy link

lkuty commented Oct 22, 2020

Yes, it will cause the code to execute twice. Try that by sneaking in a side effect causing statement in args. A better method is to use

defmacro tee(args, func) do
  quote bind_quoted: [args: args, func: func] do
    (fn ->
      args |> func
      {:ok, args}
    end).()
  end
end

It does not work and I get the following error:

warning: variable "display" does not exist and is being expanded to "display()"
...
warning: undefined function func/1
...
== Compilation error in file lib/hydro/fish_test.ex ==
** (CompileError) lib/hydro/fish_test.ex:40: undefined function display/0
    (elixir 1.11.1) src/elixir_locals.erl:114: anonymous fn/3 in :elixir_locals.ensure_no_undef
ined_local/3
    (stdlib 3.13.2) erl_eval.erl:680: :erl_eval.do_apply/6

with the following test code:

  def display(x) do
    IO.inspect(x)
  end

  def process(input) do
    {:ok, input}
    ~>> f1
    ~>> f2
    ~>> f3
    >>> f4
    >>> (tee display)
  end

When I change the macro and rewrite it as below, it works.

  defmacro tee(args, func) do
    quote bind_quoted: [args: args], unquote: true do
      (fn ->
        args |> unquote(func)
        {:ok, args}
      end).()
    end
  end

Any idea ? Maybe Elixir tries to invoke the function in the process of unquoting in the bind_quoted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment