2
votes

Disclaimer: I know that it's possible to write code in a simpler manner, but you should understand that I post simplified code to SO.

I have a module Simple which uses Included:

defmodule Simple do
  use Included,
    events: [
      [ 
        name: :start,
        callback: fn(x) -> x * 2 end
      ], [
        name: :finish,,
        callback: fn(x) -> x * 3 ; x end
      ]
    ]
end

I want Included module to define a function which takes one param for each item in the list above and return a value. So, I'm doing this:

defmodule Included do
  defmacro __using__(opts)
    events = Keyword.get(opts, :events)

    quote bind_quoted: [events: events] do
      events
      |> Enum.each(fn(event) ->
        def unquote(event[:name])(x) do
          x
          |> unquote(event[:callback]).()
          |> IO.puts
      end)
    end    
  end
end

The problem here is that I receive invalid quoted expression: #Function<0.105634730. I tried to implement it another way:

defmodule Included do
  defmacro __using__(opts)
    events = Keyword.get(opts, :events)

    events
    |> Enum.each(fn(event) ->
      quote bind_quoted: [event: event] do
        def unquote(event[:name])(x) do
          x
          |> event[:callback].()
          |> IO.puts
         end
      end
    end)
  end
end

But in this case I haven't seen functions defined. (No errors, there is no functions Simple.start/1 and Simple.finish/1 here).

My questions are:

  1. How can I define desired functions?
  2. Why did functions not defined in 2nd approach?
1
A workaround that works is to escape the :callback key of all events before quote: events = for event <- opts[:events] do; Keyword.update!(event, :callback, &Macro.escape/1); end.Dogbert
@Dogbert Unfortunately I got cannot escape #Function<2.45959719/1 in Included.MACRO>. The supported values are: lists, tuples, maps, atoms, numbers, bitstrings, pids and remote functions in the format &Mod.fun/arity I tried to implement something similar in iex, same result =(asiniy

1 Answers

1
votes

I'm not 100% sure why, but inside the quote in Included.__using__/1, the AST of the function is being converted into an actual function. If you add IO.inspect(events) at the start of the quote, you'll get:

[[name: :start, callback: #Function<0.18558591 in file:c.exs>],
 [name: :finish, callback: #Function<1.18558591 in file:c.exs>]]

A workaround I found for this is to escape the :callback in the events.

defmacro __using__(opts) do
  events = for event <- opts[:events] do
    Keyword.update!(event, :callback, &Macro.escape/1)
  end
  quote bind_quoted: [events: events] do
  ...
end

Final code:

defmodule Included do
  defmacro __using__(opts) do
    events = for event <- opts[:events] do
      Keyword.update!(event, :callback, &Macro.escape/1)
    end
    quote bind_quoted: [events: events] do
      events
      |> Enum.each(fn(event) ->
        def unquote(event[:name])(x) do
          x
          |> unquote(event[:callback]).()
          |> IO.puts
        end
      end)
    end    
  end
end

defmodule Simple do
  use Included,
    events: [
      [ 
        name: :start,
        callback: fn(x) -> x * 2 end
      ], [
        name: :finish,
        callback: fn(x) -> x * 3 ; x end
      ]
    ]
end

Simple.start 10
Simple.finish 10

Output:

20
10