I think privateing modules will be a cure worse than the disease.
It breaks the semantics of the language. defp (an alias for defprivate) doesn’t allow access to functions outside the module, but defmodulep allows explicit visibility to some modules. Another naming is required.
I think that we should use an annotation, such as: @visible_to: [Foobar] or @scoped_to: [Bar] instead (maybe other names), to tag that this module is not for public usage. This should solve (1).
Although already a rejected idea, what we want to do is to guarantee that private modules are not accessible outside the application/package/library/younameit they are included, which is exactly what we are trying to do here.
Given my ideas, commenting on the proposals:
A. I like this idea, but with another name: @visible_to [MyApp, Foo, ...]. And if any module not in that list tries to use, import or whatever it will be a compilation error, much like what happens when you try to call a defp function outside the module you are defining it.
B, C & D. No because of breaking semantics: explained in (1).
Defining private modules and functions is like defining blacklists. The other approach would be to define whitelists. So instead of defining private modules / functions. Only those which are whitelisted (public) would be exposed to outer world.
I think this would be the way to go. Maybe its something for elixir 2.0, but I think it would make the language more stronger for the future.
We can’t have compilation errors with A because it is best-effort. It would be jarring to have compilation fail only from to time or in a later optional pass. If you want errors, then you need the require mechanism and a separate namespace.
That’s a good point and we can discuss other names. However, that would be too specific for now. So instead of worrying about the names themselves (such as scope/defmodulep), can you please share your thoughts on the proposal in considering the merits of the mechanisms instead, regardless of names? Thanks!
My preference would be A, D, C, then B. B is out because of the reasons already stated.
I’m concerned about this being an error, because it’s effectively a backwards incompatible change. This changes the meaning of private from, it’s not documented to I’ve explicitly marked it as private. Which means that everything that was considered private before isn’t. I may have been calling something that was private before and now because B or C were chosen, my code fails to compile. This is quite similar to using a private API and Elixir changing it and my code breaking.
I do not think, that introducing defmodulep would invalidate the former meaning of @moduledoc false.
The latter is “You use this at your own risk” and will ever be, the former is “You are not allowed to do this, and I will try the best I can to not let you pass beyond this point”.
And if a @moduledoc false module becomes defmodulep, well, you have been warned before… There never was a promise or contract to not remove that module…
That would be a backwards incompatible change in the library in question, not in the language itself. It would be the same as the library author renaming a function, or removing a module.
So you want to deprecate the use of things that shouldn’t have been used from the start, because the developer tried their best to hide the function or module from you?
Again, we are only talking about making it harder to access things you shouldn’t have accessed at all, we are not talking about magic that breaks everything in existence…
If someone is using a private API (which is @doc false today), they are in no place to put a burden of the maintainers of that library. The author of said libraries are free to move the private code around, rename private functions, or remove private modules as they wish. This is a non-discussion for me.
However, I’d also prefer if next Elixir version adds a function like Module.get_private/1 that hardcodes the implementation detail logic of :"Elixirp.Foo.Bar". Better for the core team not to bind themselves to a hardcoded logic which might not endure the test of time. But maybe I am overthinking it. So far they always knew very well what they were doing.
In any case, having the option to do iex debugging in prod environments has been, and must continue to be, one of the best selling points of Elixir.
No need to, as you would require Foo.Bar, as: Private.
:"Elixirp.Foo.Bar" is a fallback hatch, a back door if you want, to be able to access the module during development or debugging sessions in prod through iex.