Question about applying dynamic methods in macros

I have seen two ways to call a dynamically defined method atom on a context module.

We have

apply(unquote(context), unquote(get_function), [id])

Or

unquote(context).unquote(get_function)(id)

Where i find the latter method extremely wierd. Seems it like it either does some unquote chaining. Or that somehow elixir supports MyModule.:get_my_stuff(id).

Ayone help me understand ?:pray:

The apply/3 is a function that does the call exclusively at runtime, by using it you lose all compile-time guarantees. Your generated code will look like:

apply(MyModule, :my_function, [id])

The second example literally generates the function call at compile-time (assuming that code compiles), that dot is not meant for chaining, just as a dot for the generated code. Assuming the syntax is correct it should generate:

MyModule.my_function(id)

The advantage of the second apprach is that you will have all the compile-time guarantees and warnings.

4 Likes

Thanks. I think i will add some comments in my macro to shed some light on this :slight_smile: EDIT: and definitely all compile help we can get is good help :raised_hands:

1 Like

I would create a helper macro that would generate that structure, as you are right, it is kind of easy to get lost in that syntax.

1 Like

I made a helper macro

  defmacro call_dynamic(context, function_name, args) do
    quote do
      unquote(context).unquote(function_name)(unquote_splicing(args))
    end
  end

then I can call it like this

call_dynamic(unquote(context), unquote(get_function), [id])

when I now change a name on a method or make it take more parameters, the compiler will issue warnings. Then I can --warnings-as-errors :wink:

not sure about the name yet, but its a million times easier to understand and it looks like how apply works … maybe I should call it safe_apply :stuck_out_tongue:

EDIT:

I went a bit further too

  defmacro call_dynamic(context, function_name, arg1, arg2, arg3) do
    quote do
      unquote(context).unquote(function_name)(unquote(arg1), unquote(arg2), unquote(arg3))
    end
  end

  defmacro call_dynamic(context, function_name, arg1, arg2) do
    quote do
      unquote(context).unquote(function_name)(unquote(arg1), unquote(arg2))
    end
  end

  defmacro call_dynamic(context, function_name, args) when is_list(args) do
    quote do
      unquote(context).unquote(function_name)(unquote_splicing(args))
    end
  end

  defmacro call_dynamic(context, function_name, arg) do
    quote do
      unquote(context).unquote(function_name)(unquote(arg))
    end
  end

now I have more freedom of expression and don’t have to use [foo, bar] all the time