下面的 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.
首先是一个简短的答案,使用透析器的格言:
- 透析器永远不会错。(Erlang程序员经常背诵)
- 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.
- 你能发现代码中的错误吗?
- 透析器会发现错误吗?花点时间尝试确定您可以推断出 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().