我有一个看起来像这样的函数。
def test(options \\ []) do
# Fun stuff happens here :)
end
它接受几个(可选)关键字参数,包括do:
。我希望能够这样称呼它。
test foo: 1 do
"Hello"
end
但是,这会导致错误。
** (UndefinedFunctionError) function Example.test/2 is undefined or private. Did you mean one of: * test/0 * test/1 Example.test([foo: 1], [do: "Hello"]) (elixir) lib/code.ex:376: Code.require_file/2
从错误中您可以看到,上面的语法简化为两个单独的关键字列表。现在,我可以使用以下稍微不方便的语法调用此函数
Example.test foo: 1, do: (
"Hello"
)
但是在一个函数调用中,除了其他关键字参数之外,还有什么方法可以提供do
块?
答案 0 :(得分:8)
虽然@bla提供的答案在技术上是正确的(例如macro
有效),但它几乎无法说明问题和原因。
首先,没有什么可以阻止您使用函数而不是宏使用此语法,您只需要显式地将do:
部分的关键字参数与其他任何内容分开:
defmodule Test do
# ⇓⇓⇓⇓⇓⇓⇓⇓⇓ HERE
def test(opts \\ [], do: block) do
IO.inspect(block)
end
end
Test.test foo: 1 do
"Hello"
end
#⇒ "Hello"
您无法使用功能实现的是生成可执行文件块。如上例所示,它是静态的,因为函数是运行时公民。函数执行时的代码将已经编译,这意味着无法将代码传递给该块。也就是说,块内容将在 caller 上下文中执行,而在 函数本身之前:
defmodule Test do
def test(opts \\ [], do: block) do
IO.puts "In test"
end
end
Test.test foo: 1 do
IO.puts "In do block"
end
#⇒ In do block
# In test
通常这不是您期望Elixir块起作用的方式。那就是宏出现的时候:宏是编译时的公民。传递给宏的block
参数的do:
将作为AST 注入到Test.test/1
do
块中,成为
defmodule Test do
defmacro test(opts \\ [], do: block) do
quote do
IO.puts "In test"
unquote(block)
end
end
end
defmodule TestOfTest do
require Test
def test_of_test do
Test.test foo: 1 do
IO.puts "In do block"
end
end
end
TestOfTest.test_of_test
#⇒ In test
# In do block
边注:在您的注释中说:“我毫不犹豫地将其放入宏。”这是完全错误的。函数和宏是不可互换的(尽管看起来很像),它们是完全不同的东西。宏应该用作最后的手段。宏将注入AST 到位。函数是AST 。
答案 1 :(得分:6)
如果您愿意使用宏而不是函数,这可能会帮助您:
defmodule Example do
defmacro test(args, do: block) do
quote do
IO.inspect(unquote(args))
unquote(block)
end
end
end
样品用量:
iex(2)> defmodule Caller do
...(2)> require Example
...(2)>
...(2)> def foo do
...(2)> Example.test foo: 1 do
...(2)> IO.puts "Inside block"
...(2)> end
...(2)> end
...(2)> end
{:module, Caller,
<<70, 79, 82, 49, 0, 0, 4, 108, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 147,
0, 0, 0, 16, 13, 69, 108, 105, 120, 105, 114, 46, 67, 97, 108, 108, 101, 114,
8, 95, 95, 105, 110, 102, 111, 95, 95, ...>>, {:foo, 0}}
iex(3)> Caller.foo
[foo: 1]
Inside block
:ok