透析器版本2.9.Erts 7.3.OTP 18.
在以下设计的erlang代码中:
-module(dialBug).
-export([test/0]).
%-export([f1/1]). % uncomment this line
test() ->
f1(1).
f1(X) when X > 5 ->
X*2.
Run Code Online (Sandbox Code Playgroud)
当透析器运行在上面的代码上时,它警告代码将不起作用,因为防护测试(X> 5)永远不会成功.
但是,当我取消注释第3行并导出f1/1函数时,透析器不再发出任何警告.
我意识到当导出f1/1时,透析器不可能知道guard子句会因为外部客户端可以使用它而失败.但是,为什么它不能再确定test/0是否正确使用f1/1?
Dialyzer作为类型检查器有一些限制.透析器并不是一个严格的都市,它是一个宽松的都市.这意味着它只会在发现声明函数的方式明显错误时给出警告,而不是某种情况下它会推断调用者可能做错了.
它将尝试推断有关调用站点的内容,但它不能超出基本类型规范声明可以传达的内容.因此,整数值可以定义为a neg_integer(),a pos_integer(),a non_neg_integer()或any integer(),但除非您已经明确定义了合法值的边界,否则无法定义任意范围,例如5..infinity,但您可以定义范围,例如5..10并获得您期望的结果.
其中一个奇怪的部分是,虽然守卫向Dialyzer提供了一些信息,因为它是一个宽容/宽松的形式,编码器的真正负担是指定功能,其定义足够严格,可以检测到呼叫站点的错误.
以下是这些东西在实际代码+ Dialyzer输出中的表现(与我一起,它有点长屏幕以完全显示所有这些,但没有什么比代码更好地展示相关问题):
原来的问题
-module(dial_bug1).
-export([test/0]).
%-export([f/1]).
test() ->
f(1).
f(X) when X > 5 ->
X * 2.
Run Code Online (Sandbox Code Playgroud)
透析日:
dial_bug1.erl:5: Function test/0 has no local return
dial_bug1.erl:8: Function f/1 has no local return
dial_bug1.erl:8: Guard test X::1 > 5 can never succeed
done in 0m1.42s
done (warnings were emitted)
Run Code Online (Sandbox Code Playgroud)
因此,在一个封闭的世界中,我们可以看到Dialyzer将回调给调用者,因为它的情况有限.
第二个变种
-module(dial_bug2).
-export([test/0]).
-export([f/1]).
test() ->
f(1).
f(X) when X > 5 ->
X * 2.
Run Code Online (Sandbox Code Playgroud)
透析师说:
done (passed successfully)
Run Code Online (Sandbox Code Playgroud)
在一个开放的世界中,呼叫者可以是任何人发送任何东西,没有努力回溯和检查未声明的,无限范围.
第三种变体
-module(dial_bug3).
-export([test/0]).
-export([f/1]).
-spec test() -> integer().
test() ->
f(-1).
-spec f(X) -> Result
when X :: pos_integer(),
Result :: pos_integer().
f(X) when X > 5 ->
X * 2.
Run Code Online (Sandbox Code Playgroud)
透析师说:
dial_bug3.erl:7: Function test/0 has no local return
dial_bug3.erl:8: The call dial_bug3:f(-1) breaks the contract (X) -> Result when X :: pos_integer(), Result :: pos_integer()
done in 0m1.28s
done (warnings were emitted)
Run Code Online (Sandbox Code Playgroud)
在一个开放的世界中,我们有一个可声明的开放范围(在这种情况下,正整数集),将找到违规的呼叫站点.
第四种变体
-module(dial_bug4).
-export([test/0]).
-export([f/1]).
-spec test() -> integer().
test() ->
f(1).
-spec f(X) -> Result
when X :: pos_integer(),
Result :: pos_integer().
f(X) when 5 =< X, X =< 10 ->
X * 2.
Run Code Online (Sandbox Code Playgroud)
透析师说:
done (passed successfully)
Run Code Online (Sandbox Code Playgroud)
在一个开放的世界里,我们有一个守卫但仍未声明的范围,我们发现Dialyzer将再次找不到违规的来电者.在我看来,这是所有这一切中最重要的变体 - 因为我们知道Dialyzer 确实从检查类型的警卫那里得到了提示,但显然它并没有从数字范围检查警卫那里得到提示.那么让我们看看我们是否宣布了一个有界但是任意的范围......
第五种变体
-module(dial_bug5).
-export([test/0]).
-export([f/1]).
-spec test() -> integer().
test() ->
f(1).
-spec f(X) -> Result
when X :: 5..10,
Result :: pos_integer().
f(X) when 5 =< X, X =< 10 ->
X * 2.
Run Code Online (Sandbox Code Playgroud)
透析师说:
dial_bug5.erl:7: Function test/0 has no local return
dial_bug5.erl:8: The call dial_bug5:f(1) breaks the contract (X) -> Result when X :: 5..10, Result :: pos_integer()
done in 0m1.42s
done (warnings were emitted)
Run Code Online (Sandbox Code Playgroud)
在这里,我们看到,如果我们用勺子喂食Dialyzer,它将按预期完成工作.
我不确定这是否被视为"虫子"或"透析器松散的约束".痛苦的主要点Dialyzer地址是本机类型失败,而不是数字边界.
所有这些......
当我在现实世界中有用的实际项目中的实际工作代码中遇到过这个问题时 - 我已经提前知道我是否正在处理有效数据,而且在极少数情况下我不是总会写这个:
{ok, Value} | {error, out_of_bounds}让调用者决定如何处理它(这样可以在每种情况下为他们提供更好的信息).一个有保障的例子是相关的 - 上面的最后一个示例有一个有界的守卫将是一个可崩溃的功能的正确版本.
-spec f(X) -> Result
when X :: 5..10,
Result :: {ok, pos_integer()}
| {error, out_of_bounds}.
f(X) 5 =< X, X =< 10 ->
Value = X * 2,
{ok, Value};
f(_) ->
{error, out_of_bounds}.
Run Code Online (Sandbox Code Playgroud)