【问题标题】:Cannot create a spec in Erlang无法在 Erlang 中创建规范
【发布时间】:2017-09-26 15:45:04
【问题描述】:

我正在尝试在 Erlang 模块中创建一个非常简单的规范,但出现此错误。

未定义函数的规范 compare/2

这是我的代码:

-module(spec_example).
-spec compare(any(), any()) -> less | equal | greater.

-record(heap_node, { item :: any(),
                     children :: [#heap_node{}] }).

-record(priority_queue, { root :: #heap_node{} | nil,
                          comparer :: compare() }).

我无法在此处定义比较函数,因为它将作为外部参数提供。我在 GitHub 中找到了 similar examples,我猜它们都可以正常工作。

我在模块和头文件中都试过了,但错误是一样的。我一定错过了一些非常基本的东西。

【问题讨论】:

  • 您的意思是使用-type 而不是-spec
  • @Dogbert 我是 Erlang 的新手。我不应该为函数使用 spec 属性吗?

标签: erlang


【解决方案1】:

您引用的示例是头文件 (.hrl),而不是源文件 (.erl),他们将其导入到应该以某种方式运行的文件的顶部。

这似乎是一种强制少数模块都以相同方式运行的方式。这让我感到困惑,因为这种行为定义正是存在要处理的行为

另外,请注意他们的api.hrl 导入wf.hrl,然后这些是imported all over the place in the project。从我看一眼该代码可以看出,确实没有理由不将其定义为行为。 (可能是有原因的,但看一下代码并不明显......我想不出一个很好的理由来避免在这种情况下使用行为。)

功能规格

定义函数规范时,您需要有一个底层函数来使用它进行注释。所以这本身就是非法的:

-spec add(integer(), integer()) -> integer().

但这是合法的:

-spec add(integer(), integer()) -> integer().

add(A, B) ->
    A + B.

使用类型、规范、edoc等的示例:https://github.com/zxq9/zuuid

定义行为

那么“什么是行为?”假设你想在你的程序中有一个位置,你可以动态地选择要调用的模块,并且你必须期望该模块的行为方式与其他类似定义的模块相同。如果您需要调用foo:frob/2,它的工作方式(就类型而言)应该与bar:frob/2 完全相同。所以我们会在某处写一个行为定义,可能在foo_bar.erl 内部,然后将我们的模块foo.erlbar.erl 声明为foo_bar 类型的行为:

-module(foo_bar).

-callback frob(integer(), inet:socket()) -> ok.

后来……

-module(foo).
-behavior(foo_bar).

frob(A, B) ->
    % ...

如果模块 foo 缺少指定类型的函数 frob/2,则会引发警告,因为它无法匹配其声明的行为。

这对于简化您检查、管理和保持有组织的模块间 API 兼容性的方式非常有用。另外请注意,您可以在模块上定义任意数量的回调,并且可以在模块中声明任意数量的行为。

见:How to create and use a custom Erlang behavior?

【讨论】:

  • 感谢@zxq9 的精彩回答。它使规范错误一目了然。你能否建议我如何定义一个函数签名(接受 2 个参数并返回这 3 个值之一),以便我可以在代码的其他部分重用它?这在 Erlang 代码中是可能的还是常见的?
  • @UfukHacıoğulları Speccing 是每个函数,不可重复使用。代码中出现类似(但不完全相同)规范的地方是,当您在顶部的 gen_server 上有一个接口函数(例如,gen_server:call/2,以及该调用的处理程序的实现)内部通常会有相似但不完全相同的规范。如果您有一个公共 API 需要多个模块匹配,那么您应该定义一个适用于所有模块的行为。否则,相同的规范在代码中重复出现多次表明你可能可以重构。
  • @UfukHacıoğulları 这可能会成为一个相当长的讨论,并且模组会将其从 cmets 中删除。如果您想通过一个示例或您发现重复规范的具体案例进行讨论,请访问Erlang chat room
【解决方案2】:

-spec 只能键入在当前模块中声明的顶级函数,不能键入作为参数传递给顶级函数或存储在记录中的匿名函数。对于这些,您可以使用 -typefun(X) -> Y 语法:

-type compare() :: fun((any(), any()) -> less | equal | greater).

-record(heap_node, { item :: any(),
                     children :: [#heap_node{}] }).

-record(priority_queue, { root :: #heap_node{} | nil,
                          comparer :: compare() }).

如果我现在创建一个priority_queue,比较器是一个单元函数:

main() ->
  #priority_queue{root = nil, comparer = fun(_) -> equal end}.

然后运行 ​​Dialyzer,我得到:

Record construction #priority_queue{root::'nil',comparer::fun((_) -> 'equal')} violates the declared type of field comparer::fun((_,_) -> 'equal' | 'greater' | 'less')

【讨论】:

  • 类型定义正是我想要的。我也试过了,但显然语法错误。然后我认为只有规格适用于功能。感谢您和 @zxq9 提供这些出色的答案。
【解决方案3】:

我无法在此处定义比较函数,因为它将作为外部参数提供。

对于这种情况,您需要 -type,正如 Dogbert 在 cmets 中提到的那样:

-type compare() :: fun((any(), any()) -> less | equal | greater).

-record(priority_queue, { root :: #heap_node{} | nil,
                          comparer :: compare() }).

因为您想描述 的类型,而不是模块中名为 compare 的函数的规范。

【讨论】:

    猜你喜欢
    • 2021-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-21
    • 1970-01-01
    • 2012-03-01
    相关资源
    最近更新 更多