Proposal: Support for __PARENT_MODULE__ (or some other better name)

When writing my code, I always find __MODULE__ very useful to use as alias of that module “inner dependencies”, ex:

alias __MODULE__.{Impl, Helper, ...}

But one thing that I always find missing is that when I want to get some dependency for a module in the same “level” that my current module, I need to write the full module path, ex:

defmodule My.Big.Module.Path.Something do
  alias My.Big.Module.Path.SomethingElse

  ...
end

I feel that having something like __PARENT_MODULE__ would be useful in these cases. Also, the impact on the existing eco-system would be zero since it doesn’t break any existing code.

Here are more “concrete examples” of when this can come in handy:

When organizing behaviours

The way I organize them is something like this:

# The behaviour module
Some.Module.MyBehaviour

# The modules implementing it
Some.Module.MyBehaviour.Impl1
Some.Module.MyBehaviour.Impl2
Some.Module.MyBehaviour.Impl3

In these cases, I could write something like @behaviour __PARENT_MODULE__ and @impl __PARENT_MODULE__ instead of @behaviour Some.Module.MyBehaviour and @impl Some.Module.MyBehaviour

When organizing gen servers and scoped business logic

If my genservers are getting big or complex, I like to split them into smaller parts, normally what I do is have something like this:

# This module will have the genserver child_spec and public APIS
Some.Module.Path.MyGenServer

# This module will have all the genserver code implementation (handle_call, start_async, etc)
Some.Module.Path.MyGenServer.Server

# This module will have all the genserver business logic
Some.Module.Path.MyGenServer.Impl

# And this module will have the genserver state struct and how to create/update it
Some.Module.Path.MyGenServer.State

Now, for example, the Server module needs to call Impl functions, and the Impl module needs to call State functions. Making the usage of __PARENT_MODULE__ useful here.

I don’t know if that is normally an issue for other people, but the project I work is pretty big and have some pretty big module names, so having something like that would make writing some code way faster for me.

I don’t hate this and have found myself wanting it for cases like your first example. However, there is already confusion around there not actually being a module hierarchy and feel this could make it worse. It’s also something that won’t “just work” when you rename a module referenced by this.

Personally, I’m overall indifferent as I don’t find status quo to be too much of a pain.

4 Likes

Can you elaborate on that? I’m not sure I understood the point.

I would argue that you would get an error from the compiler and you would be able to easily fix the issue.

That module hierarchy in Elixir is an illusion and doesn’t exist. The . is lie! :ghost: Lots of people don’t understand this. Nested defmodules already make this confusing enough.

Maybe this one wouldn’t be as bad as I thought. I’d have think more on this one as it feels like it could possibly cause subtle bugs where a rename could result in it calling another function that happens to have the same name, but maybe not :thinking:


I should add that despite my indifference, I’d more than likely use this if it were available.

2 Likes

To expand upon this with examples…

Module A.B.C is not contained within module A.B nor A, they are completely separate modules whose names start the same.

Even when writing code such as:

defmodule A do
  defmodule B do
    def b, do: "b"
  end

  def a, do: B.b()
end

Module A.B is still not truly contained within module A (A.B is not a child of A) but the above works as a convenience.

3 Likes

Ah, ok, I got now what you meant, but that is just a technical detail IMO, in the end of the day devs (well, I just speak to myself on that) would read it as a hierarchy regardless.

But yeah, having a __PARENT_MODULE__ would give the wrong idea that the modules are a hierarchy.

Recently, I work in large repo and must to add alias too much. I wish Elixir has namespace for auto add alias (and maybe support for import/require/use).

Example:

defmodule MyApp.A do
  namespace MyApp.DataProcessing

  def a do
  # do somethings
  end

  defmacro macroA(name, opts) do
    #...
  end
end

defmodule MyApp.B do
 namespace MyApp.DataProcessing

 def b do
  # somethings
 end
end

defmodule MyApp.C do
  namespace MyApp.DataProcessing

  def do_something(data) do
    data
    |> A.a()
    |> B.b()
  end

 A.macroA :wrapper, type: :json
end

Updated: I brought this into the proposal.

I do use __MODULE__ occasionally. Often like

alias __MODULE__, as: User

Even though it saves characters when writing, I find it sometimes makes reading code harder, whereas spelling out the name or fully qualified name would have been clearer. Example situations: grep results, reviewing git diff, casually browsing a large file. Those are add the cognitive burden to decode on my head “what module is this?”

I guess I’d feel similar about a __PARENT_MODULE__, with the additional trouble of making moving modules around to organize the illusional hierarchy require consideration for any usage of that shortcut. Depending on where the module name is used, this could pass unnoticed by the compiler, e.g. if the module is referenced as an MFA tuple.

1 Like

AFAIK Erlang has a single module/atom namespace, and Elixir inherits that. This idea would be a separate proposal, different than the OP.

You can use the use macro to refactor your many requires, imports and aliases and implement something similar to what you are asking in “user land”. In fact, that’s what Phoenix projects are setup like when you scaffold them with phx.new.

For example, see: https://github.com/phoenixframework/phoenix/blob/V1.8.1/installer%2Ftemplates%2Fphx_single%2Flib%2Fapp_name_web.ex#L80-L97

In Controllers, Components, LiveViews, HEEx files, etc, the module Phoenix.LiveView.JS is available to use as JS without you having to alias it explicitly in every file.

While possible, this “magical” behavior is generally frowned upon, in preference of explicit code.

1 Like

Thank you! My original idea namespace is a macro but need to work in compiler for sharing state or add specific thing.

I wrote to a separated OP.

1 Like

Your example of alias __MODULE__, as: User would definitely make grepping harder, though that is really only because of the :as. Personally I think :as should really only ever be used for getting around naming conflicts.

Otherwise for me I find the cognitive burden goes the other way. when I see __MODULE__ I immediately think “current module.” On the other hand:

defmodule Foo.Bar do
  # ... many line
  def something(%Foo.Bar{} = bar) do
    # ...
  end
end

Seeing %Foo.Bar{} here immediately makes me think “a remote module” and it often takes me many moments to figure it out, which sucks when you’re scanning.

3 Likes

I don’t like the way Elixir work inside current module, better has an auto alias like alias __MODULE__ than must add it manually (I usually add this to my module) or type __MODULE__.

For me, when look to __MODULE__ I need a little of bit to think about what is the current module.

Do you mean you type this literally, or like alias __MODULE__.Foo? If the former, why would you do this? Seems like any confusion you’re having may be caused by over-aliasing, though hard to tell.

While no solution is perfect, I find that far more often than not I know what module I’m in. When unsure, most modern editors have features like hovering and, even better, scroll context which will pin the defmodule line to the top as you scroll. Barring any of that, usually a quick glance at the filename does the trick.

1 Like

For me, I usually code like:

defmodule MyApp.Foo.Bar do
   defstruct 
   [
     :task,
     :opts
   ]

  alias __MODULE__

  def do_task(%Bar{} = task) do
    # do somethings
  end
end

This way help me easy to read & maintain than using __MODULE__ in everywhere.

Oh… ya I mean, different stokes, I guess. This would be crazy confusing to anyone coming in blind to this. I would first assume that Bar has been aliased in from another module but since it’s not explicitly present in the alias list, it then looks like a top level module. You have to “be in on it” to get it and even then, you can’t always be 100% alias __MODULE__ is present. This is terrible for scanning. But again, to each their own!

3 Likes

Yes, that is just a personal feeling of mine. Read a meaning word help me easy to understand.

1 Like

I agree with you there. For me __MODULE__ now read like this in OOP languages. It automatically forces me to think “module I am currently in”. It is a little bit verbose, but at the same time it makes it distinct enough.

So I 100% prefer __MODULE__ over alias __MODULE__ (it also help with refactoring).

2 Likes

Ya that’s a great way to put it!


@manhvu I would suggest at least doing alias Fully.Qualified.Current.Module which would totally eliminate __MODULE__ altogether!

1 Like

Do wonder why. Modules are usually referenced in other files, so how come the file the module is defined in is so special it ‘adds’ work during refactoring?

The only extra info of __MODULE__ is “struct can be found in this module too”, at the expense of a braincycle “what module am I in?” and not being able to ‘grep’ all occurrences of struct.

Most of @sodapopcan his arguments are only valid for those not into the conventions of adding the alias and then using it consistently.

Ps. I do use the module notation a lot. Still challenging assumptions and statements.

It’s not like it’s a slam dunk, but it’s a nice little perk when renaming, reducing noise.

I honestly don’t even think I’ve grepped for a stuct, though if you are aliasing how are you going to reliably grep for it? Of course you can do wildcards, but that will return matches of possible other aliases with the same name in different files.

Otherwise, and this is getting very bike-sheddy, pretty much all the places I use __MODULE__ has a variable or spec right there with the name of the module I’m in but ya, I won’t repeat my arguments over but I will say again: to each their own! The only thing I think is super janky (and will be adding to my grumpy credo rules of all the confusing things I see people do with this language) is alias __MODULE__. Just spell the dang thing out!

Though I am interested:

Are you saying that you add aliases and then sometimes don’t use them or you just generally don’t alias at all? If the latter, I’m generally all for that! I used to not alias at all though recently started when someone pointed out that it’s a great way to create a natural list of depenencies but part of me is thinking of going back. (and if this is the case then your grepping argument makes sense).

EDIT: Also, my argument (for me) still stands that if I see a module reference, my brain thinks “remote,” which is true with or without aliases, so no saved brain cycles for this guy!

Ok, I think I think I’ve sufficiently painted my side of the shed :upside_down_face:

2 Likes