簡體   English   中英

在Elixir宏中使用警衛

[英]Using guards in Elixir macros

我正在研究宏,它將采用一個函數並添加一些額外的功能。 例如。:

這個:

  defstate this_works(a, b) do
    a + b + 1
  end

應轉換為:

  def this_works(a, b) do
    IO.puts("LOGGING whatever")
    a + b + 1
  end

這就是我到目前為止所擁有的。 嘗試在iex中運行這段代碼:

  defmodule MyMacro do
    defmacro defstate(ast, do: block) do
      {fn_atom, _} = Macro.decompose_call(ast)

      quote do
        def unquote(fn_atom)(var!(a), var!(b)) do
          IO.puts("LOGGING")
          unquote(block)
        end
      end
    end
  end

  defmodule Test1 do
    import MyMacro

    defstate this_works(a, b) do
      a + b + 1
    end
  end

  Test.this_works(1, 2)

這按預期工作。

現在,這個模塊沒有編譯:

  defmodule Test2 do
    import MyMacro

    defstate this_fails(a, b) 
      when 1 < 2 
      when 2 < 3 
      when 3 < 4 do
      a + b + 1
    end
  end

唯一的變化是我添加了一個后衛,宏無法處理。

如何改進MyMacro.defstate以使其適用於具有任意數量防護的功能?

如果你defstate this_fails(a, b) when 1 < 2defstate this_fails(a, b) when 1 < 2檢查fn_atom ,你會看到它是:when而不是:this_fails 這是因為如何when表達的葯劑AST中表示:

iex(1)> quote do
...(1)>   def foo, do: 1
...(1)> end
{:def, [context: Elixir, import: Kernel],
 [{:foo, [context: Elixir], Elixir}, [do: 1]]}
iex(2)> quote do
...(2)>   def foo when 1 < 2, do: 1
...(2)> end
{:def, [context: Elixir, import: Kernel],
 [{:when, [context: Elixir],
   [{:foo, [], Elixir}, {:<, [context: Elixir, import: Kernel], [1, 2]}]},
  [do: 1]]}

您可以使用一些模式匹配來解決此問題:

defmodule MyMacro do
  defmacro defstate(ast, do: block) do
    f = case ast do
      {:when, _, [{f, _, _} | _]} -> f
      {f, _, _} -> f
    end

    quote do
      def unquote(ast) do
        IO.puts("LOGGING #{unquote(f)}")
        unquote(block)
      end
    end
  end
end

defmodule Test do
  import MyMacro

  defstate this_works(a, b) do
    a + b + 1
  end

  defstate this_works_too(a, b) when a < 2 do
    a + b + 1
  end
end

defmodule A do
  def main do
    IO.inspect Test.this_works(1, 2)
    IO.inspect Test.this_works_too(1, 2)
    IO.inspect Test.this_works_too(3, 2)
  end
end

A.main

輸出:

LOGGING this_works
4
LOGGING this_works_too
4
** (FunctionClauseError) no function clause matching in Test.this_works_too/2

    The following arguments were given to Test.this_works_too/2:

        # 1
        3

        # 2
        2

    a.exs:24: Test.this_works_too/2
    a.exs:33: A.main/0
    (elixir) lib/code.ex:376: Code.require_file/2

(我還在def之后更改了unquote,以確保保留when子句。)

defstate的調用在編譯時擴展到defstate中的quote塊中的defmacro 因此,保護​​表達式不會直接應用於宏調用,因為在編譯時,不會調用您在里面定義的函數。

所以你必須抓住:when你自己元組並自己添加守衛時:

defmodule MyMacro do
  defmacro defstate({:when, _, [ast, guards]}, do: block) do
    {fn_atom, _} = Macro.decompose_call(ast)

    quote do
      def unquote(fn_atom)(var!(a), var!(b)) when unquote(guards) do
        IO.puts("LOGGING")
        unquote(block)
      end
    end
  end
end

注意我現在如何匹配{:when, _, [ast, guards]}元組。

當你用一個守衛調用一個宏時,它會將原始的ast放在參數列表的第一項中,並將第二項中的守護表達式放在內。

請注意,如果您想使用沒有保護子句的宏,您仍然需要在此下面定義一個全能宏定義。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM