Creating a set of functions with the same name using macros.

Hello I have been trying to implement this function into elixir, but cannot seem to get it to work. It seems like a perfect use case for macros in my eyes.

I want a macro lets call newdef that takes a name argument, and a do block

defmodule Test do
    defmacro newdef(name, do: block) do
        ...
    end
end

this macro will then def three functions with the same name

        quote do
            defmodule Functions do
                def unquote(name)([]) do
                    :endof
                end
                def unquote(name)([char | rest]) do
                    unquote(block) #this needs to use the 'char' argument
                    unquote(name)(rest)
                end
                def unquote(name)(string) do
                    array = string_to_array(string)
                    unquote(name)(array)
                end
            end
        end

The Issues I am having:

  1. getting the part in the do: access the char
  2. creating functions that can pattern match
  3. using the newdef in the same way I can use def in elixir

Thank you if you could give some enlightenment

Desired:

    import Test
    newdef namedfunction do
      IO.puts(char)
    end
Function.namedfunction "hello world"

which would output
"
h
e
l
l
o

w
o
r
l
d
"

Hi @rieou

Welcome to the Elixir forum.

First of all, you should be very careful with macros. Most likely, you don’t need a macro in your case. See Write macros responsibly.

The problem in your code is that the char in the macro does not affects the char used in your namedfunction. See Macro hygiene. With var!/1 you can make char available outside the macro.

Your could look like this (again I would not recommend writing it this way):

defmodule Foo do
  defmacro magic(name, do: block) do
    quote do
      defmodule Functions do
        def unquote(name)([]) do
          :endof
        end

        def unquote(name)([var!(char) | rest]) do
          unquote(block)
          unquote(name)(rest)
        end

        def unquote(name)(string) do
          array = String.split(string, "")
          unquote(name)(array)
        end
      end
    end
  end
end

defmodule Bar do
  import Foo

  magic :go do
    IO.puts(char)
  end
end
iex(1)> Bar.Functions.go("hello")
h
e
l
l
o
:endof
iex(2)>
4 Likes

Welcome to this awesome forum :grin:

As @Marcus answered, you can use var!/1 to break macro hygiene, but I don’t think you should do so, or at least you should give the programmers using your macro (including your future self) some kind of hints about the magical local variables inside block.

step 1: design the intended use case

Instead of forcing the callers to use char as the local variable name, you can give the callers the freedom of choosing whatever variable name they want. For example, you can let the callers use the following syntax:

defmodule Foo do
  import NewDef

  newdef foo do
    grapheme -> IO.puts(grapheme)
  end
end

step 2: see the AST

quote do
  foo -> foo + 1
end

It shows

[
  {:->, _meta, [  # operator ->
    [{:foo, _meta, Elixir}],  # argument list of ->
    {:+, _meta, [{:foo, _meta, Elixir}, 1]}  # body of ->
  ]}
]

step 3: implement your macro

The simplest way is to assign char to whatever variable the caller chooses.

defmodule NewDef do
  defmacro newdef({name, _, _}, do: [{:->, _, [[variable], expr]}]) do
    quote do
      def unquote(name)([]) do
        :endof
      end
      
      def unquote(name)([char | rest]) do
        unquote(variable) = char
        unquote(expr)
        unquote(name)(rest)
      end
  
      def unquote(name)(string) do
        graphemes = String.graphemes(string)
        unquote(name)(graphemes)
      end
    end
  end
end
2 Likes

Nice solution. But my proposal would be to not using macros at all.

iex(1)> "hello" |> String.graphemes() |> Enum.each(fn char -> IO.inspect(char) end)
"h"
"e"
"l"
"l"
"o"
:ok
iex(2)> defmodule Foo do
...(2)>   def each_char(string, fun) do 
...(2)>     string |> String.graphemes() |> Enum.each(fn char -> fun.(char) end)
...(2)>   end
...(2)> end
{:module, Foo, ...}
iex(3)> Foo.each_char("hello", fn char -> IO.inspect(char) end)
"h"
"e"
"l"
"l"
"o"
:ok
iex(4)>
4 Likes

Agreed. Explicitness is always better than implicitness.

2 Likes

Thank you so much for the help.
Please tell me if my thinking is correct but I feel like this is a use case for macros.
It isn’t about implementing that specific function.
But I want to implement a variety of functions that will all have that similar functional structure. Which seems like what macros are for. So would defining a macro that does this be the correct implementation?
If not what would you recommend? Or, if macros would be the correct way how would you personally implement this?

Hi @rieou

It is difficult to say if you really needs macros. I can’t see from your example what you want to achieve. In doubt I always start with an implementation without macros. And most of the time when I use macros I do later on a refactoring to get rid of macros.

If you explain in more detail what you want to achieve, someone can tell you if you need macros.

2 Likes

Okay i I think I can describe it better. Should I simply make a new reply here or make a new topic? I would think making just another reply since it is still answering this question correct?

Both are fine but I’d still make a new thread with a changed title because people respond more to those. You are free to put a link to this thread in it as well.

3 Likes