Elixir类型规范和参数化类型变量

时间:2015-11-03 00:49:53

标签: types elixir dialyzer

我试图弄清楚如何在Elixir类型和函数规范中组合参数化类型类型变量。举个简单的例子,让我们说我正在定义一个Stack模块:

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

使用第3行的参数化类型规范,我可以定义一个创建一个只应包含整数的新堆栈的函数:

@spec new_int_stack() :: Stack.t(integer)
def new_int_stack(), do: Stack.new

到目前为止,这么好。现在我想确保只有整数可以推入这个堆栈。例如,透析器应该没问题:

int_stack = new_int_stack()
Stack.push(int_stack, 42)

但是透析器应该抱怨这个:

int_stack = new_int_stack()
Stack.push(int_stack, :boom)

我无法弄清楚push函数的类型规范应该强制执行的内容。在Erlang中,我非常确定这种语法可以解决问题:

-spec push(Stack, Value) -> Stack when Stack :: Stack.t(Value).

有没有办法用Elixir @spec来表达这个约束?

1 个答案:

答案 0 :(得分:6)

(我对普通的Erlang更流利,但代码应该很容易移植。)

如果你单独写int_push/2(就像你做new_int_stack/0一样),那么你当然可以写:

-spec int_push(integer(), stack(integer())) -> stack(integer()).

这应该允许Dialyzer检测滥用,纯粹是由于Item参数被指定为integer()

通用规范最接近的是:

-spec push(T, stack(T)) -> stack(T) when T :: term().

不幸的是,从Erlang 18开始,Dialyzer并没有在最严格的意义上阅读这个规范(要求所有实例T都是统一的)。它只需要每个Tterm()

因此,Erlang或Elixir不会发出任何警告。

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.