有两种使用erlang和elixir的开发人员:那些为Dialyzer编写规范的开发人员,以及尚未编写规范的开发人员。 乍一看来,这似乎是在浪费时间,特别是对于那些使用松散打字语言的人。 但是,他们帮助我在CI阶段之前发现了多个错误,并且-迟早任何开发人员都知道需要它们。 不仅可以用作半严格键入的指导工具,而且还可以很好地帮助您编写代码。
但是,就像我们这个残酷的世界一样,无论桶装什么,都离不开汤匙。 本质上, @spec
指令复制了函数声明代码。 下面我将告诉您二十行代码将如何帮助在表单设计中结合功能的说明和声明
defs is_forty_two(n: integer) :: boolean do n == 42 end
如您所知,在长生不老药中,只有宏。 甚至Kernel.defmacro/2
都是
。 因此,我们要做的就是定义自己的宏,通过上面的构造,它会同时创建spec和函数声明。
好吧,让我们开始吧。
步骤1.研究情况。
首先,我们将了解我们的宏将以哪种类型的AST作为参数接收。
defmodule CustomSpec do defmacro defs(args, do: block) do IO.inspect(args) :ok end end defmodule CustomSpec.Test do import CustomSpec defs is_forty_two(n: integer) :: boolean do n == 42 end end
在这里,格式化程序将反叛 ,添加括号并格式化其中的代码,以使眼泪从眼中流出来。 从此断奶。 更改.formatter.exs
配置.formatter.exs
如下所示:
[ inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"], export: [locals_without_parens: [defs: 2]] ]
让我们回到羊身上,看看defs/2
到达那里了。 应该注意的是,我们的IO.inspect/2
在编译阶段正常工作(如果您不了解为什么,还不需要使用宏,请阅读Chris McCord出色的Metaprogramming Elixir书)。 为了使编译器不会发誓,我们返回:ok
(宏必须返回正确的AST)。 因此:
{:"::", [line: 7], [ {:is_forty_two, [line: 7], [[n: {:integer, [line: 7], nil}]]}, {:boolean, [line: 7], nil} ]}
是的 解析器认为这里的主要对象是::
运算符,该运算符粘合了函数定义和返回类型。 函数定义还包含Keyword
形式的参数列表,即“参数名称→类型”。
步骤2.快速失败。
由于到目前为止我们已经决定仅支持这种调用语法,因此我们需要重写defs
宏的定义,以便例如在未指定返回类型的情况下,编译器立即宣誓。
defmacro defs({:"::", _, [{fun, _, [args_spec]}, {ret_spec, _, nil}]}, do: block) do
好了,该开始实施了。
步骤3.生成规格和函数声明。
defmodule CustomSpec do defmacro defs({:"::", _, [{fun, _, [args_spec]}, {ret_spec, _, nil}]}, do: block) do # args = for {arg, _spec} <- args_spec, do: Macro.var(arg, nil) # args_spec = for {_arg, spec} <- args_spec, do: Macro.var(spec, nil) quote do @spec unquote(fun)(unquote_splicing(args_spec)) :: unquote(ret_spec) def unquote(fun)(unquote_splicing(args)) do unquote(block) end end end end
这里的所有内容都非常透明,甚至没有什么可评论的。
现在该看看对CustomSpec.Test.is_forty_two(42)
的调用将导致什么:
iex> CustomSpec.Test.is_forty_two 42 #⇒ true iex> CustomSpec.Test.is_forty_two 43 #⇒ false
好吧,它有效。
步骤4.就是全部吗?
不,当然。 在现实生活中,您将必须正确处理无效调用,具有几个不同默认参数的函数的头定义,仔细收集规范,变量名,确保所有参数名均不同以及更多其他内容。 但是作为性能证明就可以了。
原则上,您仍然可以通过以下方式使同事惊讶:
defmodule CustomSpec do defmacro __using__(_) do import Kernel, except: [def: 2] import CustomSpec defmacro def(args, do: block) do defs(args, do: block) end end ... end
(仍然需要纠正defs/2
,生成Kernel.def
而不是def
),但是我强烈建议您不要这样做。
谢谢您的关注,宏健康!