Как передать функцию захвата макросу в Elixir? - PullRequest
0 голосов
/ 05 апреля 2020

Это здесь не работает. Использование Macro.expand/2 также не помогает.

    defmodule ExBs.Component do
      defmacro defcomp(name, opts) do
        func = Keyword.get(opts, :func)
        IO.inspect(func)
        func.("foo")
      end
    end

    defmodule ExBs.Foo do
      import ExBs.Component

      defcomp(:foo,
        func: &Phoenix.HTML.Link.link/2
      )
    end

Это вывод IEx:

    [{:__aliases__, [line: 24], [:Phoenix, :HTML, :Link]}, :link]

    ** (BadFunctionError) expected a function, got: nil
        expanding macro: ExBs.Component.defcomp/2

Возможно ли это?

1 Ответ

1 голос
/ 05 апреля 2020

Макросы получают AST и возвращают AST . Возвращенный AST вводится в место, откуда был вызван этот макрос, вместо вызова макроса. Для возврата AST используется Kernel.quote/2. Тем не менее, ниже будет работать:

defmodule ExBs.Component do
  defmacro defcomp(name, opts) do
    quote do
      func = Keyword.get(unquote(opts), :func, fn _ -> "" end)
      IO.inspect(func)
      func.("foo")
    end
  end
end

defmodule ExBs.Foo do
  import ExBs.Component

  defcomp(:foo,
    func: &IO.inspect/1
  )
end

#⇒ #Function<0.48519526 in file:iex>
"foo"

Обратите внимание, что функция была выполнена на этапе компиляции , а именно во время раскрытия макроса. Тем не менее, полученный луч не имеет следов этой функции.

Я сомневаюсь, что это именно то, что вы на самом деле хотели, но не зная, чего вы пытаетесь достичь, невозможно предложить что-то более ценное. В любом случае, теперь код копируется и работает так, как задумано.


Дикая догадка: если вы захотите объявить функцию :foo wrapping func, то будет сделано следующее:

defmodule ExBs.Component do
  defmacro defcomp(name, opts) do
    func = Keyword.get(opts, :func)
    quote do
      def unquote(name)(), do: unquote(func).("foo")
    end
  end
end

defmodule ExBs.Foo do
  import ExBs.Component

  defcomp(:foo,
    func: &IO.inspect/1
  )
end

ExBs.Foo.foo
#⇒ "foo"
...