Still more macro weirdness

I think I am close to a solution, but there is one big obstacle. Consider the definition (2) below. It calls on functions defined in (3). But when I try to compile, I get (1) below. I don’t see anything wrong in (3) unless on3 can’t use Enum in a macro.

(1) Errors

== Compilation error on file lib/koko/doc_manager/search.ex ==
  (Protocol.UndefinedError) protocol Enumerable not implemented for {:command_list, [line: 15], nil}
    (elixir) lib/enum.ex:1: Enumerable.impl_for!/1
    (elixir) lib/enum.ex:146: Enumerable.count/1
    (elixir) lib/enum.ex:744: Enum.fetch/2
    (elixir) lib/enum.ex:313: Enum.at/3
    expanding macro: Koko.DocManager.QP.query1/1
    lib/koko/doc_manager/search.ex:15: Koko.DocManager.Search.by_command_list/1

(2) Dispatcher

#file: Koko.DocManager.Search

... 

require Koko.DocManager.QP
alias Koko.DocManager.QP

... 
def by_command_list(command_list) do
    n = length command_list
    case n do
      1 ->
        QP.query1(command_list)
      2 ->
        QP.query2(command_list)
      3 ->
        QP.query3(command_list)
      4 ->
        QP.query4(command_list)
      _ ->
        QP.query5(command_list)
    end

  end

(3) Inelegant Macro Code – there are query1 through query5

  defmacro query2(command_list) do
     [cmd0, arg0] = Enum.at command_list, 0
     [cmd1, arg1] = Enum.at command_list, 1
     quote do
        Document
          |> Query.by(unquote(cmd0),unquote(arg0))
          |> Query.by(unquote(cmd1),unquote(arg1))
          |> Repo.all
     end
  end

Of course, I’d like to have just one macro … but for now I’ve got my hands full! :smile:

What are you passing to by_command_list/1?

I’m sending it a list of 2-lists, e.g, [["title", "Magick"]] which is dispatched to query1, [["title", "Magick"], ["sort", "title"]], which is dispatched to query2, etc. I’ve tested query1, query2, etc., but Search.by_command_list(command_list) , which dispatches to these, won’t compile.

A macro is executed at compile-time and receives the code representation of the arguments. Not the runtime values.

I don’t think macro is the appropriate tool here. All can be achieved with a plain function. Looking at the snippets you pasted, it looks to me, like an Enum.reduce/3 should be enough:

command_list
|> Enum.reduce(Document, fn [cmd, arg], query ->
  Query.by(query, cmd, arg)
end)
|> Repo.all
3 Likes

Excellent ponts – thanks!!

Check out @michalmuskala’s answer :thumbsup:

But if you’re just playing with macros:

defmacro query2(command_list) do
  quote do
    [cmd0, arg0] = Enum.at unquote(command_list), 0
    [cmd1, arg1] = Enum.at unquote(command_list), 1

    Document
        |> Query.by(cmd0, arg0)
        |> Query.by(cmd1, arg1)
        |> Repo.all()
  end
end

Elixir macros are just like lisp macros. Thats how I remember it anyway. Thats why I love Elixir – its my new lisp :smile: I haven’t tested the above, but I think that should work – if you’re messing with macros.

2 Likes

Thanks! – that helps me understand macros better – turns out that @michalmuskala’s answer does the trick for me – but I will keep this in mind for the future.

I wanted to send a second note of appreciation. With your snippet, the code not only does the job, but is beautiful

2 Likes

Hi @michalmuskala, I wrote a little post on Medium in which I credited you. Enjoyed your article on contexts.

1 Like

The post looks great!

Thankyou!