透析器错过了类型规范的错误



下面的 Erlang 代码在其类型规范中似乎有一个明显的错误,但透析器说一切正常。是我误解了还是这是透析器中的错误?在 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.

首先是一个简短的答案,使用透析器的格言:

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

格言2是任何"为什么透析器没有发现这个错误"问题的"标准"答案(诚然不是很令人满意)。


更多解释性答案:

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


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

制度 1:

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

制度2:

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

透析器在制度2下运作。在您的情况下,如果您致电foo(hello)

1> c(foobar).
{ok,foobar}
2> foobar:foo(hello).
ok
3> 

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

请记住,透析器是乐观的。它比喻性地相信你的 代码,并且因为函数有可能调用 [foo] 成功了...,透析器将保持沉默。无类型 在这种情况下报告错误。

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() 参数的类型。

答:myand()的第一个参数必须是布尔值()...好吧,这实际上不是真的 - 看看myand()的最后一句。 第一个参数也可以是任何东西。 三个函数子句告诉我们,第一个参数的所有可能值都是:true、false 或任何值。包含所有三种可能性的类型是 any()。然后透析器查看第二个参数,透析器对第二个参数的类型得出相同的结论。 因此,透析器推断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() 参数——但显然透析器孤立地收集每个变量的信息:

+---------------------------------------+
|          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.

"Programming Erlang"第152页警告说,如果你使用透析器,不要使用_作为参数。

2)或者,如果要枚举的情况太多,则可以使用guards来指定参数类型:

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().

相关内容

  • 没有找到相关文章

最新更新