Current section
Files
Jump to
Current section
Files
lib/tagged/constructor.ex
defmodule Tagged.Constructor do
@moduledoc ~S"""
Generates macros for constructing, and destructuring tagged value tuples.
By default, the macro name is the same as the tag, but can be overriden with
the `as: name` keyword argument.
This module is always executed.
## Examples
- Define constructors with the same name as the tag
defmodule Tagged.Status
use Tagged
deftagged ok(term())
deftagged error(term())
end
iex> require Tagged.Status
iex> import Tagged.Status, only: [ok: 1]
iex> ok(:computer)
{:ok, :computer}
iex> with ok(it) <- Keyword.fetch([a: "bacon"], :a), do: "Chunky #{it}!"
"Chunky bacon!"
- Override constructor name
defmodule Tagged.Outcome
use Tagged
deftagged ok, as: success(term())
deftagged error, as: failure(term())
end
iex> require Tagged.Outcome
iex> import Tagged.Outcome, only: [failure: 1, success: 1]
iex> failure(:is_human)
{:error, :is_human}
iex> with success(it) <- {:ok, "Computer"}, do: "OK, #{it}!"
"OK, Computer!"
"""
@moduledoc since: "0.1.0"
##############################################################################
##
## Public API ends here, internal helper functions follows
##
##############################################################################
@doc false
@spec __deftagged__(Keyword.t()) :: Macro.t()
def __deftagged__(params) do
tag = Keyword.get(params, :tag)
ex_tag = Keyword.get(params, :ex_tag)
module = Keyword.get(params, :module)
name = Keyword.get(params, :name)
arity = Keyword.get(params, :arity)
gen_constructor(tag, module, name, arity, ex_tag)
end
defp gen_constructor(tag, module, name, 0, ex_tag) do
quote do
@doc """
Constructor `#{unquote(name)}/0` for `#{unquote(ex_tag)}` tags.
iex> require #{unquote(module)}
iex> import #{unquote(module)}
iex> with #{unquote(name)}() <- :#{unquote(ex_tag)}, do: true
true
iex> with #{unquote(name)}() <- :not_#{unquote(ex_tag)}, do: true
:not_#{unquote(ex_tag)}
"""
defmacro unquote(name)(), do: unquote(tag)
end
end
defp gen_constructor(tag, module, name, arity, ex_tag) do
cons_args = Macro.generate_arguments(arity, nil)
ex_match = for(_ <- 1..arity, do: "_") |> Enum.join(", ")
ex_vals = for(i <- 1..arity, do: "#{i}") |> Enum.join(", ")
ex_match = "#{name}(#{ex_match})"
ex_hit = "{:#{ex_tag}, #{ex_vals}}"
ex_miss = "{:not_#{ex_tag}, #{ex_vals}}"
quote do
@doc """
Constructor `#{unquote(name)}/#{unquote(arity)}` for `#{unquote(tag)}`
tagged value tuples. Can also be used to destructure tuples.
iex> require #{unquote(module)}
iex> import #{unquote(module)}
iex> with #{unquote(ex_match)} <- #{unquote(ex_hit)}, do: true
true
iex> with #{unquote(ex_match)} <- #{unquote(ex_miss)}, do: true
#{unquote(ex_miss)}
"""
defmacro unquote(name)(unquote_splicing(cons_args)) do
args = [unquote_splicing(cons_args)]
tag = unquote(tag)
quote do: {unquote(tag), unquote_splicing(args)}
end
end
end
end