walkr

walkr

Hello,

I need some help with the following problem (Bascially I want to store at compile time all modules using some other module, along with their opts).

defmodule Root do
  @modules %{} # Mapping from __MODULE__ to opts

  def __using__(opts) do
    quote do
        @opts unquote(opts)

        def hello(), do: "Hello from #{__MODULE__}"

        # How do I store __MODULE__ and its @opts
        # inside Root.@modules?
        # ...
    end
  end

  def modules_using_me: @modules
end

defmodule A do
  use Root, id: A1
end

defmodule B do
  use Root, id: B1
end

Then to get the modules __using__ Root I would call

iex> Root.modules_using_me()
%{A: [id: A1], B: [id: B1]}

OR

iex> SomeOtherModule.modules_using_root()
%{A: [id: A1], B: [id: B1]}

Could anyone give me some ideas? I already tried to accumulate into a Root attribute all other modules, but obviously it fails since Root module is already compiled.

Thank you!

Showing Posts 1 to 7

christhekeele

christhekeele

As you observe, Root cannot know what modules use it at compile time, because it must first be compiled itself. Indeed, at compile time only module A could know if it used Root.

You can leverage module attributes to ask A which modules it has used—example code for that here.

Alternatively, since A knows what it’s used, you could inject that information into other modules that use A if you take control of its __using__ macro. This allows a module that uses both A and B to know what modules they respectively have used. This is pretty abstract and hard to explain—I have example code demoing the concept here.

walkr

walkr OP

Thank you for your reply @christhekeele. Unfortunately it doesn’t address my challenge. I need a way to record all the modules that are __using__ the Root module. Also, these modules are not to be used any further like in your second demo.

christhekeele

christhekeele

I’m not sure that’s possible. When Root.__using__ is expanded, the only context it has access to is the module using it (A in our example). So any tracking you want to do must occur within A.

There are only two ways I’m aware of to try to pass information A knows about to modules at compile time: using a macro defined in A (like giving it its own __using__ macro and using it elsewhere); or a @before_compile macro callback (which must come from an already compiled module as well).

I understand your challenge pretty well—since I developed both of those examples a couple of years ago grappling with the same problem. Despite a lot of metaprogramming since then, I haven’t been able to find a way to accomplish exactly what you want in pure Elixir, and those techniques sufficed.

Ultimately I’m pretty sure the problem is intractable, because of the core problems:

  • Root must be compiled to be used
  • only __using__ modules know when Root is used

So all you can really do when Root is used is to push that information around. You can put it in A, or use other techniques to put it in other modules, but by definition and the need for an acyclic module graph, you cannot ever put that information into Root itself.

christhekeele

christhekeele

That isn’t to say you couldn’t use techniques outside of pure compile-time Elixir macros to store this information somewhere. The obvious example that comes to mind is a file somewhere. Alternatively, the closest data store adjacent to pure Elixir that is available when the Elixir compiler is executing is :ets. However, no matter how you handle it you run in to the issue where whenever during compilation you need the compiler to access that information, you can’t be fully sure that all modules that would contribute to it have finished compiling.

walkr

walkr OP

I don’t need the list of modules at compile time (although that would be a bonus) but rather at runtime. I have a GenServer that needs this information. Let’s call it GS. I tried to inspect all loaded modules at GS’s init via :code.all_loaded, call some special function I inserted inside Root’s __using__, however it looks like not all modules are loaded at the time GS is initialized, thus I don’t get a complete picture. Note: GS is started in one app, while A and B in another, probably that’s why GS doesn’t see all loaded modules.

mbuhot

mbuhot

Your problem sounds similar to protocol consolidation. You might find some inspiration in GitHub - OvermindDL1/protocol_ex: Elixir Extended Protocol · GitHub which has a custom mix compiler task that scans the beam files for modules that declare implemtations of a protocol.

OvermindDL1

OvermindDL1

I can easily add a function that returns all implementations of the given protocol too (I think I have it in a descriptor but it’s not really ‘public’ stuff?), but if that is the only feature you are needing then the normal elixir protocols already has that feature too.

But yeah, the scanner part itself could easily do that, copy it into your project and just have it be a compiler that generates the information necessary after everything else is already compiled. :slight_smile:

— 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
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
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
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
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
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
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews