我如何在erlang中使用-spec
字?
请给我一个有效使用这个词的想法。这仅代表文档目的吗?
我尝试使用-spec
按功能类型规范在模块中应用约束,但我失败了 - 没有应用任何限制。
答案 0 :(得分:18)
-spec
属性确实被编译器和运行时系统视为文档。您无法使用它们向代码中添加任何“可执行功能”,这同样适用于-type
和-opaque
属性。
然而,它们非常有用:
文档:EDoc使用它们为您的代码生成所有不同形式的文档。 -spec
属性是函数签名,根据您投入的精力,可以使您的代码更易于理解和维护。假设本月您最喜欢的数据结构是dict()
。请考虑以下代码:
my_function(SomeArg, SomeOtherArg, Dict) ->
...
dict:find(SomeKey, Dict)
...
用作dict的变量已被命名为。但是,假设您有以下代码段:
my_other_function(NamesDict, PlacesDict) ->
...
R1 = my_function(A, B, NamesDict),
...
R2 = my_function(C, D, PlacesDict),
...
尝试跟上这一点可能很快会导致代码重复此Dict
后缀。更甚事,您甚至可能不想在my_other_function
的上下文中记住这两个参数是dict()
。所以你可能想要这样做:
-spec my_other_function(dict(), dict()) -> atom().
my_other_function(Names, Places) ->
...
R1 = my_function(A, B, Names),
...
R2 = my_function(C, D, Places),
...
现在很明显,这些参数应该是dict()才能使函数正常工作,并希望每个人都能够在不深入代码的情况下解决这个问题。但是假设您在其他地方使用此Name
dict()
并且它存储了使用不同API公开的某些特定信息。然后它是-type
声明的完美候选者:
-type names() :: dict().
-spec my_other_function(names(), places()) -> atom().
my_other_function(Names, Places) ->
...
R1 = my_function(A, B, Names),
...
R2 = my_function(C, D, Places),
...
如果其他人频繁使用此特定数据结构,您可能也想要将其导出:
-module(my_module).
-export_type([names/0]).
-type names() :: dict().
其他模块现在可以引用这个特定的数据结构:
-module(my_other_module).
-record(my_state, {names :: my_module:names(),
...}).
最后,如果您希望其他开发人员不在其模块中以任何方式检查此数据结构,则可以将其声明为-opaque
。同样,这是一个“友好的建议”,就像到目前为止所有其他的东西一样。或者是......?
差异检测:如果您花时间使用-specs
和-types
,您会非常希望这些内容保持最新。众所周知,如果没有观看,没有人保持文档最新!幸运的是, Dialyzer 正在观看。透析器可以 check在所有调用my_function()
的参数都是dict()
(即使没有-spec
注释,它也可以执行此操作,但它更容易如果你也有这些话,并且如果你用其他东西称呼它就会尖叫血腥谋杀。此外,它还可以跟踪这些导出的类型,甚至可以报告不透明度违规。所以它不仅仅是“文档”。
测试用法生成:支持者可以使用-spec
和-type
定义通过随机测试用例自动检查您的功能。它甚至可以从像这样的声明中创建随机测试用例:
-type int_tree() :: {node, integer(), tree(), tree()} | nil.
为行为指定一组回调的全新方式是使用熟悉的-spec
语法。编译器,Dialyzer和其他可能的工具可以使用此信息来检查行为实现。请参阅OTP行为code和here
了解更多here。
答案 1 :(得分:8)
-spec
的功能是规范,它们有几个地方可以提供帮助:
-callback
关键字可用于为行为API执行此操作。-type
和-opaque
一起,您可以强制某些类型对代码段不透明。这意味着您不能在静态验证级别上查看内部表示。这可以反过来帮助驱动模块化代码,因为您不允许紧密地耦合代码片段。