2

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?
3
  • 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. Commented May 10, 2016 at 10:43
  • @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 =( Commented May 10, 2016 at 10:59
  • 1
    Try gist.github.com/anonymous/e2ab20b11fe9f502f2e0c1d693e1625a Commented May 10, 2016 at 12:25

1 Answer 1

1

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
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.