透析器版本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.
当透析器在上面的代码上运行时,它会警告代码不会工作,因为防护测试(X > 5)永远不会成功。
但是,当我取消注释第三行并导出f1/1函数透析器时,不再发出任何警告。
我意识到,当f1/1输出时,透析器不可能知道防护条款将失败,因为外部客户可以使用它。但是,为什么它无法再确定test/0不正确地使用f1/1?
发布于 2017-09-17 19:35:13
作为一种类型检查器,Dialyzer有一些限制。透析器不是严格的打字机,它是一个松散的打字机。这意味着,它只会在发现某个函数的声明方式存在明显错误时才会给您一个警告,而不是在某种情况下推断调用者可能正在做一些不好的事情。
它将尝试推断有关调用站点的事情,但它不能超出基本类型can声明所能传达的范围。因此,可以将整数值定义为neg_integer()
、pos_integer()
、non_neg_integer()
或任何integer()
,但是除非您明确定义了合法值的边界,否则无法从5..infinity
定义任意范围,但是您可以定义一个范围,如5..10
,并获得预期的结果。
奇怪的是,虽然guards向Dialyzer提供了一些信息,因为它是一个允许/松散的类型,但真正的负担在于程序员来指定具有足够严格定义的函数,以便可以检测到调用点的错误。
以下是这些内容在实际代码+透析器输出中的表现(请原谅,要完整地显示所有这些内容有点长,但没有什么比代码更能说明相关问题):
原始问题
-module(dial_bug1).
-export([test/0]).
%-export([f/1]).
test() ->
f(1).
f(X) when X > 5 ->
X * 2.
透析器天数:
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)
因此,在封闭的世界中,我们可以看到透析器将回溯到调用者,因为它有一个有限的案例。
第二变体
-module(dial_bug2).
-export([test/0]).
-export([f/1]).
test() ->
f(1).
f(X) when X > 5 ->
X * 2.
透析器说:
done (passed successfully)
在一个开放的世界中,调用者可以是任何发送任何内容的人,不需要回溯和检查未声明的、无界的范围。
第三个变体
-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.
透析器说:
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)
在一个开放的世界中,我们有一个可声明的开放范围(在本例中,是一组正整数),将会找到有问题的调用站点。
第四变体
-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.
透析器说:
done (passed successfully)
在一个开放的世界中,我们有一个戒备森严但仍未声明的范围,我们发现透析器将再次找不到冒犯我们的呼叫者。在我看来,这是最重要的变体--因为我们知道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.
透析器说:
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)
在这里,我们看到,如果我们用汤匙喂食透析器,它会像预期的那样发挥作用。
我真的不确定这是否被认为是"bug“或”透析器松散的限制“。pain透析器地址的要点是失败的本机类型,而不是数字界限。
话虽如此..。
当我在实际项目中遇到这个问题时,实际项目中的工作代码在现实世界中很有用--我已经提前很好地知道我是否在处理有效数据,在极少数情况下,我不会这样写:
{ok, Value} | {error, out_of_bounds}
的包装值,让调用者决定如何处理它(这在任何情况下都能为他们提供更好的信息)。一个受保护的示例是相关的--上面最后一个具有有界保护的示例将是crashable函数的正确版本。
-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}.
https://stackoverflow.com/questions/46262859
复制相似问题