Metaprogramming code reuse

I am looking at the following code:

How can I refactor this not to have to define the same thing twice for def and defp? I want to add more code here also.

What’s important to note here is that def and defp are macros - they are not keywords. So when we define functions using def/defp we use the normal syntax for calling macros (or functions).

That means we can do this:

method = :def

unquote(method)(unquote(origname)(unquote_splicing(args))) do
  ...
end
3 Likes

I get this error

** (CompileError) iex:118: invalid call unquote(method)(unquote(origname)(unquote_splicing(args))) do

It works on my machine:

iex(1)> method = :def
:def
iex(2)> origname = :my_func
:my_func
iex(3)> args = [:arg1, :arg2]
[:arg1, :arg2]
iex(4)> quote(do: unquote(method)(unquote(origname)(unquote_splicing(args))))
{:def, [], [{:my_func, [], [:arg1, :arg2]}]}
iex(5)> quote(do: unquote(method)(unquote(origname)(unquote_splicing(args))) do ... end)
{:def, [], [{:my_func, [], [:arg1, :arg2]}, [do: {:..., [], Elixir}]]}

Can you show the code that causes the compilation error?

EDIT: A better example of it working:

iex(4)> defmodule Foo do
...(4)> defmacro foo(method, origname, args) do
...(4)> quote do
...(4)> unquote(method)(unquote(origname)(unquote_splicing(args))) do :bar end
...(4)> end
...(4)> end
...(4)> end
iex(5)> require Foo
Foo
iex(6)> defmodule Bar do
...(6)> Foo.foo(:def, :bar, [:arg1])
...(6)> end
{:module, Bar,
 <<70, 79, 82, 49, 0, 0, 4, 20, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 126,
   0, 0, 0, 14, 10, 69, 108, 105, 120, 105, 114, 46, 66, 97, 114, 8, 95, 95,
   105, 110, 102, 111, 95, 95, 7, 99, 111, ...>>, {:bar, 1}}
iex(7)> Bar.bar(:arg1)
:bar
2 Likes

You can’t do unquote(method) if method is in the list of bind_quoted variables. Remove it from bind_quote and it may work.

3 Likes

I just tried but I still get the same issue.

I am trying to reproduce the error using your repository but I can’t. Are there tests covering this code?

Not yet that is my next step.

iex -S mix

Cachex.start(:mycache)
defmodule M do
	use Cachex.Memoize
	
	defmemo f(x), cache: :mycache do
		x + 1
	end
end

Okay I added a test the code is on top of the master branch https://github.com/edescourtis/cachex .

You can run the test directly with mix test test/cachex/memoize_test.exs