I would like to impose a constraint on my types in Elixir. I am trying to do this with the @type
directive.
defmodule Thing do
@enforce_keys [:foo, :bar]
defstruct [:foo, :bar]
@type t :: %__MODULE__{
foo: String.t(),
bar: integer()
}
end
I would like to constrain Thing
structs to something like {foo :: string, bar :: int}
, but this seems to pass through just fine:
iex(39)> obj = %Thing{foo: "foo", bar: "bar"}
%Thing{bar: "bar", foo: "foo"}
iex(40)> obj
%Thing{bar: "bar", foo: "foo"}
Is there an easy way to impose type constraints to prevent this from happening?
Yes, harness pattern matching for your needs at runtime. Typespecs only help during compile time – and they are voluntary; code will still compile even if they are not obeyed.
defmodule Thing do
# ...your original code from above is here...
def new(foo, bar) when is_binary(foo) and is_integer(bar) do
%__MODULE__{foo: foo, bar: bar}
end
end
…then just go like this:
iex> Thing.new("foo", "bar")
# this will raise an error
3 Likes
Elixir doesn’t have a static type system enforced by the compiler - there is a separate tool that you use for analysis using these types called dialyzer. While the runtime does have a strong type system, it doesn’t know about the @type or @spec annotations that you make.
@dimitarvp’s answer is the best one - the pattern match constraints will also inform the success typing analysis of dialyzer
- so you will get the benefit of type requirements statically and at runtime. If you want to get started using dialyzer
with Elixir I recommend this mix task that I maintain.
6 Likes