3

选择any()一个让 Dialyzer 接受 ETS 匹配模式的好解决方案吗?

Dialyzer 和 match 规格不能很好地配合使用,并且似乎没有标准的解决方案:

这是我正在考虑的解决方案的完整示例。如果matcher('_')在最后一行更改为“_”,则 Dialyzer 会抱怨记录构造错误,但该matcher/1功能似乎一切正常:


-module(sample).
-record(rec, {field :: number}).
-export([main/1]).

-type matchvar() :: '$1' | '$2' | '$3' | '$4' | '$5' | '$6' | '$7' | '$8' | '$9' | '$10' | '$11' | '$12' | '$13' | '$14' | '$15' | '$16' | '$17' | '$18' | '$19' | '$20' | '$21' | '$22'.


-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')}, 1).

这是有效的,因为 Dialyzer 不能静态地判断出不可达的第一个子句matcher/1是不可达的。由于binary_to_term/1返回any(),Dialyzer 推断返回类型matcher/1为 be any()

在使用匹配规范时,这个技巧是让 Dialyzer 开心的好方法吗?“好”是指:

  • 运行时成本低
  • 几支长枪
  • 没有更好(更安全、更快、更符合人体工程学)的方式

我看了看它的实现node()并认为它只是一个指针取消引用,所以成本应该很低。并且 '$ will never match' 真的永远不会匹配,因为node() 总是返回一个带有 an @in it的原子。但必须有更好的方法。

这里确实有两个问题,我结合起来避免了XY 问题

  1. 上面的技术是让 Dialyzer 将某些东西视为 的好方法any()吗?
  2. 将 Dialyzer 视为处理matcher('_')匹配any()规范的良好解决方案吗?
4

1 回答 1

3

我认为这不是一个好的解决方案,因为您正在做无用的工作(无论多么小)来满足编译时的某些问题,并且这样做是在欺骗透析器。

当出现这种情况时,我通常会扩展记录以包含匹配变量并使用它(通常我的记录是-opaque这样的,因此字段类型在构造函数中进行控制)。

您始终可以只导出实际类型的子类型,而不是使用-opaque(详细说明Pierre Krafft 来自 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'
于 2020-12-29T18:11:40.090 回答