5

下面的 Erlang 代码在它的类型规范中似乎有一个明显的错误,但是 dialyzer 说一切正常。是我误解还是这是透析器中的错误?在 Erlang 19.3 上运行

-module(foobar).

-export([foo/1]).

-spec foo(atom()) -> ok | {error, atom()}.
foo(Arg) -> bar(Arg).

-spec bar(atom()) -> ok | error.
bar(baz) -> error;
bar(_) -> ok.
4

2 回答 2

4

首先是一个简短的回答,使用 Dialyzer 的格言:

  1. 透析器永远不会出错。(经常被 Erlang 程序员背诵)
  2. Dialyzer 从未承诺会找出代码中的所有错误。(没那么出名)

Maxim 2 是对任何“为什么 Dialyzer 没有发现这个错误”问题的“标准”答案(诚然不是很令人满意)。


更多解释性答案:

Dialyzer 对函数返回值的分析经常过度近似。因此,类型中包含的任何值都被认为是“可能返回”的值。这有一个不幸的副作用,有时肯定会返回的值(例如您的error原子)也被认为是“可能返回”。Dialyzer 必须保证最大值 1(永远不会出错),因此在“可能返回”意外值的情况下,它不会发出警告(在 的规范中foo),除非没有任何实际指定的值可以返回。最后一部分在整个函数的级别进行检查,并且由于在您的示例中某些子句确实 return ok,因此不会生成警告。


最后,如果您希望 Dialyzer 对规格非常严格,您可以使用-Wunderspecsor -Woverspecsor -Wspec_diffs(请参阅有关每个功能的文档)

于 2018-03-04T07:58:36.383 回答
3

方案 1:

如果你的代码中有任何路径与你指定的类型匹配,那么 dialyzer报错。

方案 2:

如果您的代码中有任何路径与您指定的类型匹配,那么透析器将不会报告错误。

diaylyzer 在制度 2 下运行。在您的情况下,如果您致电foo(hello)

1> c(foobar).
{ok,foobar}

2> foobar:foo(hello).
ok

3> 

...然后foo()使用所需的参数类型 atom() 调用,并foo()返回所需的类型之一,ok,因此透析器不会报告错误。

请记住,Dialyzer 是乐观的。它对您的代码具有象征意义,并且因为对 [foo] 的函数调用有可能成功......,Dialyzer 将保持沉默。在这种情况下不会报告类型错误。

http://learnyousomeerlang.com/dialyzer

透析器可能比您的示例更令人困惑,例如:

-module(my).
-export([test/0, myand/2]).
%-compile(export_all).
-include_lib("eunit/include/eunit.hrl").

test() ->
    myand({a,b}, [1,2]).

myand(true, true) -> true;
myand(false, _) -> false;
myand(_, false) -> false.
  1. 你能发现代码中的错误吗?
  2. 透析器会发现错误吗?花点时间尝试确定您可以推断出 myand() 参数的类型。

答案: to 的第一个参数myand()必须是一个 boolean() ......这实际上不是真的 - 看看 myand() 的最后一个子句。第一个参数也可以是任何东西。三个函数子句告诉我们第一个参数的所有可能值是:true、false 或任何值。包含所有三种可能性的类型是 any()。然后,dialyzer 查看第二个参数,dialyzer 对第二个参数的类型得出了相同的结论。因此,dialyzer 推断出的类型为myand()

myand(any(), any()) -> boolean().

...这意味着在透析器看来,调用myand({a,b}, [1,2])不是错误。啊??相反,我的羽毛朋友:

1> c(my).  
{ok,my}

2> my:test().
** exception error: no function clause matching my:myand({a,b},[1,2]) (my.erl, line 9)

3> 

显然,myand()代码的意图是myand()至少需要一个 boolean() 参数——但显然,dialyzer 会单独收集每个变量的信息:

+---------------------------------------+
|          1st arg info                 |
|                                       |               
|   info1     true                      |                          
|   info2     false                     |
|   info3     any                       |
|           ---------                   |
|             any() -- inferred type    |
|                                       |
+---------------------------------------+

+---------------------------------------+
|          2nd arg info                 |
|                                       |    
|   info1     true                      |
|   info2     any                       |
|   info3     false                     |
|            -------                    |
|             any() -- inferred type    |
|                                       |
+---------------------------------------+

因此,test()/myand()代码是透析器无法在您的代码中报告实际错误的情况。

有一些方法可以帮助透析器发现错误:

1) 枚举函数子句中所有可能的参数:

myand(true, true) -> true;
myand(false, true) -> false;
myand(true, false) -> false.

“Erlang 编程”p。_如果您使用透析器,152 警告不要使用for 参数。

2) 或者,如果要枚举的案例太多,可以使用守卫来指定参数类型:

myand(true, true) -> true;
myand(false, _Y) when is_boolean(_Y) -> false;
myand(_X, false) when is_boolean(_X) -> false.

3)当然,您可以使用类型规范:

 -spec myand(boolean(), boolean()) -> boolean().
于 2018-03-04T10:10:55.997 回答