Oliver

Oliver

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.

Showing Posts 1 to 6

NobbZ

NobbZ

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.

Oliver

Oliver OP

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.

NobbZ

NobbZ

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…

Oliver

Oliver OP

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:

dimitarvp

dimitarvp

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.

Oliver

Oliver OP

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.)

— All posts loaded —

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews