使用do-block的关键字参数

Sil*_*olo 3 syntactic-sugar elixir keyword-argument

我有一个看起来像这样的功能.

def test(options \\ []) do
  # Fun stuff happens here :)
end
Run Code Online (Sandbox Code Playgroud)

它接受几个(可选的)关键字参数,包括do:.我希望能够这样称呼它.

test foo: 1 do
  "Hello"
end
Run Code Online (Sandbox Code Playgroud)

但是,这会产生错误.

** (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
Run Code Online (Sandbox Code Playgroud)

从错误中可以看出,上面的语法是两个单独的关键字列表.现在,我可以使用以下稍微不方便的语法来调用此函数

Example.test foo: 1, do: (
  "Hello"
)
Run Code Online (Sandbox Code Playgroud)

但有没有办法do在一个函数调用中提供除了其他关键字参数之外的-block?

Ale*_*kin 9

虽然@bla提供的答案在技术上是正确的(例如macro作品),但它几乎没有说明什么是什么.

首先,没有什么能阻止您使用函数而不是宏来使用此语法,您只需要将关键字参数显式地分隔为do:part和其他任何内容:

defmodule Test do
                     # ????????? HERE 
  def test(opts \\ [], do: block) do
    IO.inspect(block)
  end
end

Test.test foo: 1 do
  "Hello"
end
#? "Hello"
Run Code Online (Sandbox Code Playgroud)

你有什么不能用的功能实现,是制作一个可执行块.它将是静态的,如上例所示,因为函数是运行时公民.函数执行时的代码将被编译,这意味着无法将代码传递给该块.也就是说,函数本身之前,将使用调用者上下文执行块内容:

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
Run Code Online (Sandbox Code Playgroud)

通常情况下,您不期望Elixir块如何工作.那就是当宏来到现场时:宏是编译时的公民.在block传递到do:宏观的说法,将被注入的ASTTest.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
Run Code Online (Sandbox Code Playgroud)

旁注:在评论中你说"我毫不犹豫地把它变成一个宏."这是完全错误的.函数和宏不可互换(虽然看起来像它们一样),但它们是完全不同的东西.宏应该用作最后的手段.宏注入AST就地.功能是AST.


bla*_*bla 6

如果您愿意使用宏而不是函数,这可能对您有所帮助:

defmodule Example do
  defmacro test(args, do: block) do
    quote do
      IO.inspect(unquote(args))
      unquote(block)
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

样品用法:

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
Run Code Online (Sandbox Code Playgroud)