Current section
Files
Jump to
Current section
Files
lib/named_fn.ex
defmodule NamedFn do
@moduledoc """
Named function for Elixir.
"""
@doc """
Define a named function.
iex> import NamedFn
iex> fac = named_fn :fac do
...> 0 -> 1
...> x when x > 0 -> x * fac(x - 1)
...> end
iex> fac.(5)
120
"""
defmacro named_fn(name, do: clauses) do
clauses = Macro.escape(clauses)
quote do
NamedFn.define_named_fn(__ENV__, unquote(name), unquote(clauses), binding())
end
end
@doc false
def define_named_fn(env, name, clauses, bindings) do
quoted = {:fn, [], clauses}
{_, scope} = :elixir_env.env_to_scope(env)
# scope is record `elixir_erl` in "elixir.hrl"
var_names = elem(scope, 4)
{read, _} = env.current_vars
erl_bindings =
bindings
|> Enum.map(fn {var, value} ->
erl_var = var_names[read[{var, nil}]]
{erl_var, value}
end)
env =
env
# Suppress warning
|> Map.put(:prematch_vars, :apply)
# When `env.function` is `nil` it refuse to compile
|> Map.update!(:function, fn
nil -> {:define_named_fn, 4}
function -> function
end)
# Equivalent to {erl_ast, _, _} = :elixir.quoted_to_erl(quoted, env)
{expanded, _} = :elixir_expand.expand(quoted, env)
{erl_ast, _} = :elixir_erl_pass.translate(expanded, scope)
{{:fun, _, {:clauses, ex_clauses}}, _} = :ast_walk.expr(erl_ast, replace_fn_name(name), [])
{:value, result, _} = :erl_eval.expr({:named_fun, env.line, name, ex_clauses}, erl_bindings)
result
end
defp replace_fn_name(name) do
fn
# If used without args, pass is as variable.
state, {:call, _, {:atom, line, ^name}, []} ->
{{:var, line, name}, state}
state, {:call, line1, {:atom, line2, ^name}, args} ->
{{:call, line1, {:var, line2, name}, args}, state}
state, others ->
{others, state}
end
end
end