I’ve made some progress on this, but there are a few things that I wanted to get feedback on. First, a bit of context:
Namespacing currently works by starting with a few user-specified modules like [Code, Macro, ...]
and then recursively namespacing those modules and any modules they reference. You’ll get MyNamespace.Code
, but you’ll also get MyNamespace.Code.Fragment
because Code.Fragment
is referenced by Code
, and you’ll also get anything that Code.Fragment
references, etc.
This can be problematic. As a concrete example, when you parse access syntax like foo[:bar]
, you get:
{{:., [line: 1], [Access, :get]}, [line: 1], [{:foo, [line: 1], nil}, :bar]}
and various tools like Sourceror will look for that [Access, :get]
to differentiate between that and Access.get(foo, :bar)
, which parses differently.
Without intervention, however, lib_elixir will implicitly namespace Access
, so you’d get something like:
{{:., [line: 1], [MyNamespace.Access, :get]}, [line: 1], [{:foo, [line: 1], nil}, :bar]}
This is problematic because this AST will be misunderstood by other libraries. To address this, lib_elixir has a hard-coded list of excluded modules, including things like Kernel
, Access
, etc.
Given the above, there are certain modules that must be excluded from namespacing, either because module names must be preserved, or for other reasons: String
, for instance, is excluded because later versions fail to compile on earlier Elixirs/OTPs.
So I have some thoughts and questions on what and what not to include:
-
While this idea was introduced as lib_elixir, all of the current intended use-cases make use of only three root modules and their descendants: Code
, Macro
, and Module
. (Though @dorgan has a use-case for including Mix.Tasks.Format
as well.)
As a simplification, should this project explicitly target those modules and be called something like lib_code instead? Are there other modules that people may want to use?
-
Alternatively, should it be the library author’s responsibility to explicitly exclude everything they want excluded? E.g. Access
would be namespaced unless you excluded it. Perhaps the only modules that are excluded automatically are those that cannot be included for compatibility reasons, like String
?
-
Should transitive dependencies by accessible directly within the namespace? For instance, if you include Code
, Code.Fragment
could be namespaced as MyNamespace.DEP.Code.Fragment
to prevent its accidental use. This also means the Access
example above could result in something like {:., [], [MyNamespace.DEP.Access, :get]}
depending on the decisions from the above two questions.
-
My personal opinion is that the library should take a strong stance on what’s included and excluded, perhaps with no configuration at all:
- Rename to lib_code.
- Only include modules related to metaprogramming, like
Code
, Macro
, Module
, :elixir_tokenizer
as it’s used by a number of projects, maybe Mix.Tasks.Format
, and their descendants.
- Exclude everything not strictly necessary, like
Kernel
, Access
, String
, etc.
- Munge the names of transitive dependencies that must be included but shouldn’t be used, like
Mix.Task
(if Mix.Tasks.Format
is included), namespacing it as MyNamespace.DEP.Mix.Task
or similar.
I’d love to hear on the above from folks!