My Answer
After reading more about macros in Elixir, talking to the community and reading about NewType
, I have refined my ideas. While the exact implementation of my original idea is not possible, with some changes you can still get the core benefit of NewType
.
Changes to original idea
- No usage of
Name("John")
syntax. As explained in this post this syntax is not valid in Elixir. - No
defguard
. Because the type is@opaque
it is not possible to have a guard that analyses the internal structure of the data without having dialyzer complaining. Since the main goal here is to have Dialyzer help me detect issues, and since the internal structure of the opaque data can only be analyzed by functions that belong to the module itself, this means this idea is not possible. - No verification on data type when invoking
new
. Originally I thought about having some verification mechanism, but this is not necessary, since dialyzer will let the user know if the user is invokingnew
with an incorrect parameter. - No self-generated functions. Instead of having
Age.age?
orName.name?
I have opted for the more generalNewType.is_type?/2
, which will accomplish the same and is more general.
Code
With these changes in mind, this is the macro I came up with:
defmodule NewType do
defmacro deftype(name, type) do
quote do
defmodule unquote(name) do
@opaque t :: {unquote(name), unquote(type)}
@spec new(value :: unquote(type)) :: t
def new(value), do: {unquote(name), value}
@spec extract(new_type :: t) :: unquote(type)
def extract({unquote(name), value}), do: value
end
end
end
@spec is_type?(data :: {atom, any}, new_type :: atom) :: boolean
def is_type?({type, _data}, new_type) when type == new_type, do: true
def is_type?(_data, _new_type), do: false
end
Which can be used like:
type.ex:
defmodule Type do
import NewType
deftype Name, String.t()
end
test.ex:
defmodule Test do
alias Type.Name
@spec print(Name.t()) :: binary
def print(name), do: Name.extract(name)
def run do
arg = 1
name = Name.new(arg) # dialyzer detects error !
{:ok, name}
end
end