Exactly my point. I am little uncomfortable with us having to compose the exact atom module name in prod iex settings. Having a convenience escape-hatch function/macro loses us nothing and, again, clearly communicates your intent. It can also help when testing such modules (although testing implementation details should be a no-go by default).
As I said above though, I would not insist on it. Just a small quality-of-life improvement, nothing major.
In my first proposal :visible_to was a required attribute but the proof of concept has evolved and made it optional. So I believe it can be made an attribute to. We can discuss this with more details in a later step when (or if) we pick one of those proposals.
So, these solutions are primarily targeted at people who are using private APIs and are unaware that they are doing so. Is that common? If the predominant case is that people are using private APIs and they are aware, does this solve anything?
I donāt have a good gauge on that.
EDIT: I guess one benefit I can think of is explicitness over convention
According to Jose in both threads so far, people are left with the impression that Elixir itself breaks backwards compatibility ā when clearly people try to force their way in to quickly solving a problem and using an API that is intended to be private in the process. This is undesirable for marketing reasons and it doesnāt matter if people were doing things wrong if they step away from Elixir with the wrong impressions and never give it another chance. This is made worse if they advise friends and colleagues.
As for legitimate uses, Iād use this mechanism to communicate intent that certain modules are implementation details and that only their direct user modules ā who provide better public API ā must be able to access them. Having to deal with ugly and malformed external data (3rd party APIs give you those all the time) is a very good candidate for a private module. Ecto schema modules when dealing with a big and convoluted legacy database ā another.
Introducing such a feature is a clear win in both cases: when people unwittingly break contracts, and when they want to introduce semantic boundaries in their projects.
I guess I donāt see how any of these solve that problem. My guess is most people indirectly use private APIs indirectly via a library. So, when they upgrade Elixir and that library breaks, the assumption is Elixir broke it. If people are directly using private APIs and not realizing that theyāre doing so, I could see what youāre saying being true.
Many developers donāt think about boundaries because the language has no constructs to say so (besides @moduledoc false and @doc false, which are minimal). This means people donāt write annotations, which means that even if a developer would respect those boundaries, the boundaries are not there. And similarly, because those boundaries are not easy to check, you may accidentally cross them, even when they are defined.
For example, imagine you want to provide a reduce_with_super_power. You may do so by copying Enum.reduce source and then changing it a bit. The problem is that Enum.reduce called a private module, but you didnāt bother to check everything the code was calling when you copied that small snippet, and now your code may break in the next Elixir release.
This is just one example. Working on Ecto, Plug and Phoenix I saw it happening in other situations too. However, if someone knows that something is private and they decide to call it anyway, then there is nothing we can do. But at least they have been told so.
In general, it is about intent and communication and it is really hard to show intent and communicate when you have limited constructs to do so.
I have some complicated formulas (ex: related to daily items average pricing calculation, weekly automatic discount placement, etc) in some modules that are only used by 1 or 2 modules. One These complicated formulas need to be splitted in different modules (for readability and testability), probably 3 to 5, one of them has 8 modules. Do I need to make them private?
Would hiding them be beneficial to my code? I havenāt tried it. But for sure, if I hide those modules, I only have to provide the public modules to be visible for other components to use. So this is much like the context principle in Phoenix? Access some functionalities only from its boundary? I kinda think that this private modules (if implemented) will push us to write smaller modules in the future.
Would this become confusion to my other components as well? Because I already have some public and private functions in some modules. But surely, this will change how I test my Elixir code if want to utilize private modules.
Yup! I am interested in exploring the impact this feature may have in the language in terms of code organization and documentation. As @imetallica said, defmodulep really isnāt a good name, so maybe we can find a name that is more intention revealing and will guide us in the matters of code organization.
One of my friend said he has 18 modules that have only one top function to call in order to call 18 of them all. It was a huge giant module that was later refactored into 18 (soon to be 20) small modules. He said that each of those 18 modules have its own test file and live inside our CI/CD pipeline.
We still donāt know if those 18 modules should be put under private (if module private is going to be implemented), and we still have no idea on how to test them once they gone private.
The README also talks about testing but it should be mostly transparent. You can also bypass visibility for testing by using the qualified name. It is all in the README. Please do report your findings!
I can not tell about much, but the last time I checked the issue in haskell (which has proper namespaced modules), it was common to have an INTERNAL-namespace directly beneath the main namespace of the library.
There was only this convention, but much more obvious and discoverable than to have @moduledoc false. Especially when reading foreign code its more obvious that a certain piece is meant to be private.
I have no clue how it is done in other functional languages.
Another system Iām aware of, not in a functional language though, is gos. There you name a package internal (IIRC) and all its childs will only be accessible by childs of its parent. Due to the way how gos packages are namespaced (the source repository is part of the name) you canāt even mimick to be in that package. (Well, one could with abusing dependency vendoring, and then youāll know when you update anyway).
In Java and C++ namespaces/packages can not be hidden, only their content. And its easy to open up even foreign namespaces and āimpersonateā to get access.
So to be honest, I have not yet seen a system that realy makes privte stuff private, but Iād prefer an explicit āI am privat, you shall not use meā over a āIām having no documentation, so please pretend you havenāt seen meā.
One thing that is conspicuously lacking from this entire thread is any discussion of how private modules might be tested.
As @dimitarvp alluded to, there is often a reflex to say ādonāt test implementation detailsā, but when decomposing complicated functionality among several smaller private functions Iāve often regretted not being able to test them. I can easily imagine that Iād want to test the code in a private module as much if not more.
How would testing relate to the four proposals?
Edit: Or at least conspicuously lacking until the last few minutes while I was writing this! Still, I would like to know how testing would work in each of the four proposals.
You still can test them when they are private, just make sure that your tests have the correct alias-prefix.
Also similar to the proposed iex helper, some ex_unit helper would be thinkable as well, but Iām not sure if that is really necessary.
Last but not least, is this a library or a standalone application? If its the latter, hiding the modules might not be necessary at all. If it is a library though, Iād hide the modules by either @moduledoc false or the proposed defmodulep.
I do not think so. But perhaps a :"Elixirp.Module.Private".get_private/1 which is declared in a way that iex and ex_unit only need to prepare wrappers? Then it wonāt get out of sync if mangling or other details change.
If we expose that function directly, nothing (or not much at least) would change from where we are now, as it is still to easy to circumvent.
But of course, in ex_unit it should be never necessary, as you can easily create the test-module in a namespace that has access to the hidden/private module.