我已经根据this教程创建了下面的代码片段。最后两行(feed_squid(FeederRP)
和feed_red_panda(FeederSquid)
)显然违反了定义的约束,但Dialyzer发现它们还好。这非常令人失望,因为这正是我希望通过工具执行静态分析来捕获的错误类型。
本教程提供了一个解释:
在用错误的馈线调用函数之前,首先用正确的类型调用函数。对于R15B01,Dialyzer不会发现这个代码的错误。观察到的行为是,一旦对给定函数的调用在函数的体内成功,Dialyzer将忽略相同代码单元中的稍后错误。
这种行为的理由是什么?我理解成功输入背后的哲学是“永不哭泣狼”,但在目前的场景中,Dialyzer显然忽略了有意定义的函数规范(在它看到之前正确调用了这些函数之后)。我理解代码不会导致运行时崩溃。我能以某种方式迫使透析器始终认真对待我的功能规格吗?如果没有,是否有一个工具可以做到这一点?
-module(zoo).
-export([main/0]).
-type red_panda() :: bamboo | birds | eggs | berries.
-type squid() :: sperm_whale.
-type food(A) :: fun(() -> A).
-spec feeder(red_panda) -> food(red_panda());
(squid) -> food(squid()).
feeder(red_panda) ->
fun() ->
element(random:uniform(4), {bamboo, birds, eggs, berries})
end;
feeder(squid) ->
fun() -> sperm_whale end.
-spec feed_red_panda(food(red_panda())) -> red_panda().
feed_red_panda(Generator) ->
Food = Generator(),
io:format("feeding ~p to the red panda~n", [Food]),
Food.
-spec feed_squid(food(squid())) -> squid().
feed_squid(Generator) ->
Food = Generator(),
io:format("throwing ~p in the squid's aquarium~n", [Food]),
Food.
main() ->
%% Random seeding
<<A:32, B:32, C:32>> = crypto:rand_bytes(12),
random:seed(A, B, C),
%% The zoo buys a feeder for both the red panda and squid
FeederRP = feeder(red_panda),
FeederSquid = feeder(squid),
%% Time to feed them!
feed_squid(FeederSquid),
feed_red_panda(FeederRP),
%% This should not be right!
feed_squid(FeederRP),
feed_red_panda(FeederSquid).
发布于 2012-08-10 10:40:03
尽量减少这个示例,我有以下两个版本:
第一个透析器能捕捉到的:
-module(zoo).
-export([main/0]).
-type red_panda_food() :: bamboo.
-type squid_food() :: sperm_whale.
-spec feed_squid(fun(() -> squid_food())) -> squid_food().
feed_squid(Generator) -> Generator().
main() ->
%% The zoo buys a feeder for both the red panda and squid
FeederRP = fun() -> bamboo end,
FeederSquid = fun() -> sperm_whale end,
%% CRITICAL POINT %%
%% This should not be right!
feed_squid(FeederRP),
%% Time to feed them!
feed_squid(FeederSquid)
然后那个没有警告的:
[...]
%% CRITICAL POINT %%
%% Time to feed them!
feed_squid(FeederSquid)
%% This should not be right!
feed_squid(FeederRP).
对于它可以捕捉的版本,Dialyzer的警告如下:
zoo.erl:7: The contract zoo:feed_squid(fun(() -> squid_food())) -> squid_food() cannot be right because the inferred return for feed_squid(FeederRP::fun(() -> 'bamboo')) on line 15 is 'bamboo'
zoo.erl:10: Function main/0 has no local return
..。这是一种宁愿相信自己对用户更严格规范的判断的情况。
对于未捕获的版本,Dialyzer假设feed_squid/1
参数的类型fun() -> bamboo
是fun() -> none()
的超级类型(如果不在feed_squid/1
中调用,它仍然是一个有效的参数)。在推断出类型之后,Dialyzer无法知道是否在函数中实际调用了传递的闭包。
如果使用-Woverspecs
选项,透析器仍会发出警告:
zoo.erl:7: Type specification zoo:feed_squid(fun(() -> squid_food())) -> squid_food() is a subtype of the success typing: zoo:feed_squid(fun(() -> 'bamboo' | 'sperm_whale')) -> 'bamboo' | 'sperm_whale'
..。警告说,没有什么能阻止此函数处理其他馈线或任何给定的馈线!如果该代码检查闭包的预期输入/输出,而不是泛型的,那么我非常肯定Dialyzer会发现这种滥用。从我的观点来看,如果您的实际代码检查错误输入,而不是依赖类型规范和Dialyzer (它从来没有承诺要查找所有错误),情况会好得多。
警告:深奥的部分紧随其后!
在第一种情况下报告错误而不是第二种情况的原因与模块-局部细化的进展有关。最初,函数feed_squid/1
成功地键入了(fun() -> any()) -> any()
。在第一种情况下,函数feed_squid/1
将首先使用FeederRP
进行细化,并且肯定会返回bamboo
,立即伪造规范并停止对main/0
的进一步分析。在第二部分中,函数feed_squid/1
将首先使用FeederSquid
进行细化,肯定会返回sperm_whale
,然后使用FeederSquid
和FeederRP
进行细化,然后返回sperm_whale
或bamboo
。然后使用FeederRP
调用时,预期的返回值成功地输入为sperm_whale
或bamboo
。规格然后承诺它将是sperm_whale
和透析器接受它。另一方面,这个论点应该是fun() -> bamboo | sperm_whale
成功地输入,它是fun() -> bamboo
,所以只剩下fun() -> bamboo
了。当对照规范(fun() -> sperm_whale
)检查这一点时,Dialyzer假设参数可以是fun() -> none()
。如果您从未调用feed_squid/1
中传递的函数(Dialyzer的类型系统不作为信息保存),并且您在规范中保证始终返回sperm_whale
,那么一切都很好!
可以“修正”的是,当作为参数传递的闭包实际用于调用和警告时,类型系统将被扩展为注意,在这种情况下,“生存”类型推断的某些部分的唯一方法是fun(...) -> none()
。
发布于 2012-08-08 09:42:44
(请注意,我在这里推测一下。我还没有详细阅读透析器代码)。
“正常”的类型检查器具有类型检查可判定的优点。当类型检查器终止时,我们可以问“这个程序是否打得很好”并得到“是”或“否”。透析器的情况并非如此。它本质上是为了解决停滞不前的问题。其结果是,会有一些明显错误的程序,但仍会在透析器的夹持下滑落。
然而,这并不是其中之一:)
问题是两方面的。成功类型说:“如果此函数正常终止,它的类型是什么?”在上面,我们的feed_red_panda/1
函数终止任何匹配任意类型A
的fun (() -> A)
的参数。我们可以调用feed_red_panda(fun erlang:now/0)
,它也应该能工作。因此,我们对main/0
中函数的两个调用不会引起问题。他们都终止了。
问题的第二部分是“你是否违反了规范?”注意,规格通常不作为事实在透析器中使用。它推断类型本身,并使用推理模式而不是您的规范。每当调用函数时,都会使用参数对其进行注释。在我们的例子中,它将被注释为两个生成器类型:food(red_panda()), food(squid())
。然后根据这些注释进行函数本地分析,以确定您是否违反了规范。由于注释中存在正确的参数,所以我们必须假定在代码的某些部分正确地使用了该函数。它也是用乌贼调用的,这可能是代码的工件,而由于其他情况,代码从来没有被调用过。因为我们是函数本地的,所以我们不知道,并且给程序员带来怀疑的好处。
如果您将代码更改为只使用squid生成器进行错误的调用,那么我们就会发现规范不一致。因为我们知道唯一可能的呼叫站点违反了规范。如果将错误的调用移动到另一个函数,也不会找到它。因为注释仍然在函数上,而不是在调用站点上。
人们可以想象未来透析器的一个变体,它解释了每个呼叫站点可以单独处理的事实。由于透析器也在随着时间的推移而变化,它可能在将来能够处理这种情况。但目前,这是一个错误,将溜走。
关键是要注意,透析器不能用作“良好类型的检查器”。您不能使用它在程序上强制执行结构。你得自己去做。如果您希望进行更多的静态检查,可能可以为Erlang编写一个类型检查器,并在代码库的一部分上运行它。但是,在代码升级和分发方面会遇到麻烦,这是不容易处理的。
https://stackoverflow.com/questions/11851639
复制相似问题