Packages
croma
0.1.7
0.13.0
0.12.0
0.11.3
0.11.2
0.11.1
0.11.0
0.10.2
0.10.1
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.2
0.8.1
0.8.0
0.7.3
0.7.2
0.7.1
0.7.0
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.1
0.5.0
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.11
0.1.10
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
Elixir macro utilities to make type-based programming easier
Current section
Files
Jump to
Current section
Files
lib/croma/type_gen.ex
import Croma.Defun
alias Croma.Result, as: R
defmodule Croma.TypeGen do
@moduledoc """
Module that defines macros for ad-hoc module definitions.
`Croma` leverages Elixir's lightweight module syntax and advocate coding styles to define many modules.
Macros in this module helps defining modules in an ad-hoc way (in other words "in-line") based on existing modules.
"""
@doc """
Creates a new module that represents a nilable type, based on the given module `module`.
The module passed to `nilable/1` must define the following members:
- `@type t`
- `@spec validate(term) :: Croma.Result.t(t)`
Using the above members `nilable/1` generates a new module that also defines the same members:
- `@type t :: nil | module.t`
- `@spec validate(term) :: Croma.Result.t(t)`
## Examples
iex> use Croma
...> defmodule I do
...> use Croma.SubtypeOfInt, min: 0
...> end
This is useful in defining a struct with nilable fields using `Croma.Struct`.
...> defmodule S do
...> use Croma.Struct, not_nilable_int: I, nilable_int: Croma.TypeGen.nilable(I)
...> end
...> S.new([not_nilable_int: 0, nilable_int: nil])
%S{nilable_int: nil, not_nilable_int: 0}
"""
defmacro nilable(mod) do
nilable_impl(Macro.expand(mod, __CALLER__), Macro.Env.location(__CALLER__))
end
defp nilable_impl(mod, location) do
q = quote do
@type t :: nil | unquote(mod).t
defun validate(value: term) :: R.t(t) do
nil -> {:ok, nil}
v -> unquote(mod).validate(v)
end
end
ensure_module_defined(Croma.TypeGen.Nilable, mod, q, location)
end
@doc """
An ad-hoc version of `Croma.SubtypeOfList`.
Options for `Croma.SubtypeOfList` are not available in `list_of/1`.
Usage of `list_of/1` macro is the same as `nilable/1`.
"""
defmacro list_of(mod) do
list_of_impl(Macro.expand(mod, __CALLER__), Macro.Env.location(__CALLER__))
end
defp list_of_impl(mod, location) do
q = quote do
@type t :: [unquote(mod).t]
defun validate(list: term) :: R.t(t) do
l when is_list(l) ->
Enum.map(l, &unquote(mod).validate/1) |> R.sequence
x -> {:error, "validation error for #{__MODULE__}: #{inspect x}"}
end
end
ensure_module_defined(Croma.TypeGen.ListOf, mod, q, location)
end
defp ensure_module_defined(prefix, mod, quoted_expr, location) do
name = Module.concat(prefix, mod)
# Use processes' registered names to remember whether already defined or not
# (Using `module_info/0` leads to try-rescue, which results in strange compilation error)
case Agent.start(fn -> nil end, [name: name]) do
{:ok , _pid } -> Module.create(name, quoted_expr, location)
{:error, _already_defined} -> nil
end
name
end
end