How to suppress struct name expansion?

I’m generating a module from a macro, like this:

defmodule WhereTheMacroIs do

defstruct [:same_for_all]

defmacro generate(name) do
  quote do
    defmodule unquote(name) do
      def simplified_example(%WhereTheMacroIs{} = msg), do: msg.same_for_all
    end
  end
end

end

defmodule WhereIUseIt do
  require WhereTheMacroIs
  WhereTheMacroIs.generate(SomeOtherName)

  def test, do: SomeOtherName.simplified_example(%WhereTheMacroIs{same_for_all: 42})
end

The problem I get is that the struct %WhereTheMacroIs{} will be name-expanded to %WhereIUseIt.WhereTheMacroIs{} when expanding macro WhereTheMacroIs.generate/1 - which is not what I want. I want all generated modules to use the same struct internally.

I googled for a while but I was unable to find a way to suppress this extension of the struct name.

This seems to happen even if an alias is used or not.

I’m using elixir 1.6.

You should update.

I do not understand. So you want to call generate twice and it shall generate the same module twice as well? This will cause warnings during compilation and also can cause weird races when the modules happen to have the same name, but different Implementation.

I can’t update easily. Bureaucracy reasons, unfortunately. (Also, would that solve my problem…?)

This was a simplified example. In the concrete example I have a central data structure - the data type does not change. So I want to re-use the struct for it. I still can combine the same structure with different callbacks (given as macro params) and define this under a new name.

The example already allows injecting a name to each expansion of generate/2 anyway, so name collisions are excluded. But my real problem is the struct.

Oh, I do only realize now that you use the defstruct outside of the module.

So you can either use unquote(__MODULE__) (if I remember expansion rules correctly) or use Elixir.WhereTheMacroIs to specify fully qualified module name.

It wouldn’t solve your problem, yes. But I never understood the bureaucracy behind the staying outdated philosophy… Though I have to deal with it quite often due to my day work… One of our customers is still using Java5…

1 Like

Thank you. :slight_smile:

I also figured it might be less problematic to only define function defs in the quote block instead of adding the defmodule - I’ll probably refactor to something more like the __using__/1 macro in GenServer.

Thanks for the tip with the Elixir.! I saw this somewhere but it didn’t click in my brain…

As for the bureaucracy… To unroll a new elixir version to our central infrastructure I would have to go through hoops involving one or two months time of waiting, and so far 1.6 has proven to be a quite blessed version. :wink:

Okay, but why would you want to duplicate the same struct definition in every module? Shouldn’t it stay defined only once and just be used by the generated modules?

Maybe I misunderstand but it does look like you are trying to copy the same struct definition for each module.

Hi, dimitarvp.

I actually wanted to re-use the same struct definition in each module - not the defstruct statement but simply the struct itself.

I ended up doing something like below to achieve my goals. This is not what I was working on but follows the same principles:

defmodule SimpleTree do
  @callback order(term, term) :: integer
   
  defstruct [:less, :value, :more]

  def to_list(tree), do: to_list(tree, [])
  defp to_list(tree, acc) do
     acc = to_list(tree, :more, acc)
     acc = [ tree.value | acc ]
     acc = to_list(tree, :less, acc)
     acc
  end
  defp to_list(tree, side, acc) when side in [:less, :more] do
     case tree[side] do
        %SimpleTree{} -> to_list(tree[side], acc)
        nil           -> acc
      end
  end

  defmacro __using__(_) do
    quote do
      @behaviour SimpleTree # force user to define needed function

      def init(pivot), do: %SimpleTree{value: pivot}

      def add(tree, value) do
         case order(value, tree.value) do
           -1 -> add(tree, :less, value)
            0 -> tree # value already in tree
            1 -> add(tree, :more, value)
         end
      end
  
      defp add(tree, side, value) when side in [:less, :more] do
         Map.put(tree, side,
           case tree[side] do
              %SimpleTree{} -> add(tree[side], value)
              nil           -> %SimpleTree{value: value}
           end)
      end

      defdelegate to_list(tree), to: SimpleTree
    end # quote
  end # defmacro
end # defmodule

defmodule DescendingSimpleTree do
  use SimpleTree

  # define order to be descending
  def order(x, y) do
    cond do
     x > y -> -1
     x < y -> 1
     true -> 0
    end
  end

  def example do
    init(42)
    |> add(17)
    |> add(53)
    |> add(52)
    |> to_list
  end
end

The data structure itself (SimpleTree) is given, but operations on the actual values can still be generic and overridden. One can define only the parts that change in another module - here the order of elements. How the tree is built doesn’t change, but what kinds of values are in it and how they are ordered can be defined by the user.

In my own project I aimed to re-use the equivalent of %SimpleTree{} because the complex data structure will remain the same while the types and operations on the leaves needed to be flexible. The problem I encountered was that when putting defmodule in a quote block that the struct names expanded to include the names of the enveloping module. (NobbZ’ advice about prefixing Elixir. solves that particular issue.) But in the end what I wanted to achieve was easier done by a construct like I shared above - it just took some re-thinking my approach.

For the sake of example I implemented two different kinds of operation:

  • add/2: Needs a function order/2 defined in the module where use SimpleTree is done. It must be defined inside the __using__/1 macro.
  • to_list/1: Depends solely on the structure of the data itself. It can be effectively defined outside the __using__/1 macro. (So I actually pull it in with defdelegate instead of defining it inside the macro.)

Both re-use the %SimpleTree{} struct - it is only defined once. No multiple definitions of the same struct even if used in different modules. Module SimpleTree knows nothing much about the data inside the value field - except that it is not another %SimpleTree{} struct (because that would break the algorithms). All the specifics regarding the actual values inside the tree are externalized to the one invoking use SimpleTree. This makes it a similar pattern to C++ generic programming.

(I didn’t compile my example, it was just the best I could think of when trying to show what I was trying to actually do.)