Created
March 26, 2015 07:48
-
-
Save zabirauf/17ced02bdf9829b6956e to your computer and use it in GitHub Desktop.
Railway Oriented Programming macros in Elixir
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
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
I wanted to be able to have a group of computations (successive function calls) which depends on each other. If I write
f1 ~>> f2
I want f1 to try something and if it is successful I do not try f2, but if f1 cannot do its job (not because of an error) then f2 tries to do something else. So I added a{:skip, _}
tuple to bypass others successives functions called through~>>
.Note that, as implemented below, if the last call returns
{:ok, _}
, it is considered an ok value (atomok
means ok which is good) but it should mean that the call was not successful as it is the case in the previous calls of the~>>
chain. When we return{:skip, _}
it is considered successful. Thus the semantic is inversed. I did that to allow injecting{:ok, _}
in the first step of~>>
chain and let it go through evaluation. But it might probably be better to do something else. I am thinking about it. Currently, the last function call in the~>>
chain has to know it is the last to behave correctly.A test module might be:
And an execution might be: