代码之家  ›  专栏  ›  技术社区  ›  Silvio Mayolo

带do块的关键字参数

  •  3
  • Silvio Mayolo  · 技术社区  · 6 年前

    我有一个类似这样的函数。

    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 回复  |  直到 6 年前
        1
  •  6
  •   bla    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
    
        2
  •  9
  •   Aleksei Matiushkin    6 年前

    而@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"
    

    你所做的 不能 用功能实现,就是生产 可执行文件 封锁。它将是静态的,如上例所示,因为函数是运行时公民。函数执行时的代码将已被编译,这意味着不能通过 密码 到这个街区也就是说,块内容将在 来电者 语境, 之前 函数本身:

    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
    

    这通常不是你希望如何使用灵丹妙药。当宏出现时:宏是编译时的公民这个 block 传递给 做: 宏的参数,将是 注射为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 .