Don't Try This at Home: Building a Return Statement in Elixir
defmodule YourModule do
use Returnable
defr sample_fun(x) do
if x == 5 do
return :awesome
end
x + 5
end
end
iex> YourModule.sample_fun(5)
:awesome
iex> YourModule.sample_fun(7)
12
With the help of three simple macros you start breaking all the rules. Makes you feel pretty cool yeah?
This is available as a package on Github. Maybe I'll publish to hex? Special thanks to Steven Proctor and the rest of the DFWBeamer meetup for helping with the idea. A lot of inspiration from this post too.
return
, the Return Statement that Throws
This is the simplest of the macros. return expr
throws whatever value expr
evaluates to. This makes it easy to break out early.
defmacro return(expr) do
quote do
throw({unquote(__MODULE__), :return, unquote(expr)})
end
end
ret
, the Returnable Block
Whereas return
throws, a ret
block catches, immediately returning that value.
# Strip off the nested do's
defmacro ret(do: expr) do
quote do
Returnable.ret(unquote(expr))
end
end
defmacro ret(expr) do
quote do
try do
unquote(expr)
catch
:throw, {unquote(__MODULE__), :return, val} -> val
end
end
end
This allows you write things like:
ret do
x = val + 5
if x == 10, do: return :invalid
x + y
end
x + y
will only be evaluated and returned from this block if x
is not 10, else :invalid
will be returned.
defr
, the Returnable Function
With a return statement and returnable block in place, writing a returnable function as easy as creating a macro to wrap the def
function body in a ret
block.
defmacro defr(call, expr) do
quote do
def unquote(call) do
ret do
unquote(expr)
end
end
end
end
Final Thoughts
While I don't recommend ever using this package seriously, it does change the way we look at Elixir functions. For better or worse, you can now return from list comprehensions early.
defr fetch_user(users, id) do
for user <- users do
if user.id == id, do: return user
end
end
Have suggestions? Raise an issue on the Github repo. Contributions are welcome!