带有do-block的关键字参数

时间:2018-07-20 21:59:59

标签: elixir syntactic-sugar keyword-argument

我有一个看起来像这样的函数。

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块?

2 个答案:

答案 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