I am trying to figure out how to combine parameterized types and type variables in Elixir type and function specs. As a simple example, let's say I am defining a Stack
module:
defmodule Stack do
@type t :: t(any)
@type t(value) :: list(value)
@spec new() :: Stack.t
def new() do
[]
end
# What should the spec be?
def push(stack, item) do
[item|stack]
end
end
Using the parameterized type spec on line 3, I can define a function that creates a new stack that should contain only integers:
@spec new_int_stack() :: Stack.t(integer)
def new_int_stack(), do: Stack.new
So far, so good. Now I want to make sure that only integers can be pushed into this stack. For example, dialyzer should be fine with this:
int_stack = new_int_stack()
Stack.push(int_stack, 42)
But dialyzer should complain about this:
int_stack = new_int_stack()
Stack.push(int_stack, :boom)
I can't figure out what the type spec on the push
function should be to enforce that. In Erlang, I'm pretty sure this syntax would do the trick:
-spec push(Stack, Value) -> Stack when Stack :: Stack.t(Value).
Is there a way to express this constraint using Elixir @spec
?
(I am more fluent in plain Erlang, but the code should be easy to port.)
If you write a separate int_push/2
(just as you did a new_int_stack/0
) then you can of course write:
-spec int_push(integer(), stack(integer())) -> stack(integer()).
This should allow Dialyzer to detect abuses, purely due to the Item
argument being specified to be an integer()
.
The closest the generic spec can get is this:
-spec push(T, stack(T)) -> stack(T) when T :: term().
Unfortunately, as of Erlang 18, Dialyzer does not read this spec in the most strict sense (requiring all instances T
to be unifiable). It just requires each T
to be a term()
.
Therefore, no warnings will be emitted in either Erlang or Elixir.
Full code for an example in Erlang:
-module(stack).
-export([new/0, new_int_stack/0, push/2, test/0]).
-type stack() :: stack(any()).
-type stack(T) :: list(T).
-spec new() -> stack().
new() ->
[].
-spec push(T, stack(T)) -> stack(T) when T :: term().
push(Item, Stack) ->
[Item|Stack].
-spec new_int_stack() -> stack(integer()).
new_int_stack() ->
new().
-spec test() -> ok.
test() ->
A = new(),
B = new_int_stack(),
push(foo, B),
ok.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With