强制转换为 any() 是让 Dialyzer 接受 ETS 匹配模式的好解决方案吗?
Is casting to `any()` a good solution for having Dialyzer accept ETS match patterns?
投射到 any()
是让 Dialyzer 接受 ETS 匹配模式的好解决方案吗?
Dialyzer 和 match specifications 不能很好地结合在一起,似乎没有标准的解决方案:
- http://erlang.org/pipermail/erlang-questions/2012-June/067581.html
- https://bugs.erlang.org/browse/ERL-892
这是我正在考虑的解决方案的完整示例。如果最后一行的 matcher('_')
更改为“_”,那么 Dialyzer 会抱怨错误的记录构造,但是使用 matcher/1
函数似乎一切正常:
-module(sample).
-record(rec, {field :: number}).
-export([main/1]).
-type matchvar() :: '' | '' | '' | '' | '' | '' | '' | '' | '' | '' | '' | '' | '' | '' | '' | '' | '' | '' | '' | '' | '' | ''.
-spec matcher('_' | matchvar()) -> any().
matcher(X) ->
case node() of
'$ will never match' -> binary_to_term(<<>>);
_ -> X
end.
main(_Args) ->
ets:match('my_table', #rec{field = matcher('')}, 1).
这是可行的,因为 Dialyzer 无法静态地判断 matcher/1
的无法访问的第一个子句是无法访问的。由于 binary_to_term/1
returns any()
,Dialyzer 将 matcher/1
的 return 类型推断为 any()
.
这个技巧是否是让 Dialyzer 在使用匹配规格时保持快乐的好方法?我所说的“好”是指:
- 低运行成本
- 很少有脚枪
- 没有更好(更安全、更快、更符合人体工程学)的方法
我偷看了node()
的实现,认为它只是一个指针解引用,所以成本应该很低。而“$ 永远不会匹配”实际上永远不会匹配,因为 node()
always returns an atom with an @
in it。但一定有更好的方法。
这里确实有两个问题,我将它们结合起来以避免 X Y Problem:
- 上述技术是否是让 Dialyzer 将某些内容视为
any()
的好方法?
- 让 Dialyzer 将
matcher('_')
视为 any()
是处理匹配规范的良好解决方案吗?
我认为这不是一个好的解决方案,因为您正在做无用的工作(无论多么小)来满足编译时的需求,并且您这样做是在欺骗透析器。
出现这种情况时,我通常会扩展记录以包含匹配变量并接受它(通常我的记录是 -opaque
因此字段类型在构造函数中控制)。
您始终可以只导出实际类型的子类型,而不是使用 -opaque
(详述 Pierre Krafft's comment from ERL-892):
-module(sample).
-record(rec, {field :: number() | '_'}).
-type rec() :: #rec{field :: number()}.
-export_type([rec/0]).
-export([main/1]).
-spec main(rec()) -> {[[rec()]], ets:continuation()} | '$end_of_table'.
main(Rec) ->
ets:match('my_table', Rec#rec{field = '_'}, 1).
-module(sample_user).
-export([main_ok/0, main_error/0]).
main_ok() ->
sample:main({rec, 1}).
main_error() ->
sample:main({rec, '_'}).
sample_user.erl
7: Function main_error/0 has no local return
8: The call sample:main({'rec', '_'}) breaks the contract (rec()) -> {[[rec()]],ets:continuation()} | '$end_of_table'
投射到 any()
是让 Dialyzer 接受 ETS 匹配模式的好解决方案吗?
Dialyzer 和 match specifications 不能很好地结合在一起,似乎没有标准的解决方案:
- http://erlang.org/pipermail/erlang-questions/2012-June/067581.html
- https://bugs.erlang.org/browse/ERL-892
这是我正在考虑的解决方案的完整示例。如果最后一行的 matcher('_')
更改为“_”,那么 Dialyzer 会抱怨错误的记录构造,但是使用 matcher/1
函数似乎一切正常:
-module(sample).
-record(rec, {field :: number}).
-export([main/1]).
-type matchvar() :: '' | '' | '' | '' | '' | '' | '' | '' | '' | '' | '' | '' | '' | '' | '' | '' | '' | '' | '' | '' | '' | ''.
-spec matcher('_' | matchvar()) -> any().
matcher(X) ->
case node() of
'$ will never match' -> binary_to_term(<<>>);
_ -> X
end.
main(_Args) ->
ets:match('my_table', #rec{field = matcher('')}, 1).
这是可行的,因为 Dialyzer 无法静态地判断 matcher/1
的无法访问的第一个子句是无法访问的。由于 binary_to_term/1
returns any()
,Dialyzer 将 matcher/1
的 return 类型推断为 any()
.
这个技巧是否是让 Dialyzer 在使用匹配规格时保持快乐的好方法?我所说的“好”是指:
- 低运行成本
- 很少有脚枪
- 没有更好(更安全、更快、更符合人体工程学)的方法
我偷看了node()
的实现,认为它只是一个指针解引用,所以成本应该很低。而“$ 永远不会匹配”实际上永远不会匹配,因为 node()
always returns an atom with an @
in it。但一定有更好的方法。
这里确实有两个问题,我将它们结合起来以避免 X Y Problem:
- 上述技术是否是让 Dialyzer 将某些内容视为
any()
的好方法? - 让 Dialyzer 将
matcher('_')
视为any()
是处理匹配规范的良好解决方案吗?
我认为这不是一个好的解决方案,因为您正在做无用的工作(无论多么小)来满足编译时的需求,并且您这样做是在欺骗透析器。
出现这种情况时,我通常会扩展记录以包含匹配变量并接受它(通常我的记录是 -opaque
因此字段类型在构造函数中控制)。
您始终可以只导出实际类型的子类型,而不是使用 -opaque
(详述 Pierre Krafft's comment from ERL-892):
-module(sample).
-record(rec, {field :: number() | '_'}).
-type rec() :: #rec{field :: number()}.
-export_type([rec/0]).
-export([main/1]).
-spec main(rec()) -> {[[rec()]], ets:continuation()} | '$end_of_table'.
main(Rec) ->
ets:match('my_table', Rec#rec{field = '_'}, 1).
-module(sample_user).
-export([main_ok/0, main_error/0]).
main_ok() ->
sample:main({rec, 1}).
main_error() ->
sample:main({rec, '_'}).
sample_user.erl
7: Function main_error/0 has no local return
8: The call sample:main({'rec', '_'}) breaks the contract (rec()) -> {[[rec()]],ets:continuation()} | '$end_of_table'