elves / elvish

Powerful scripting language & versatile interactive shell
https://elv.sh/
BSD 2-Clause "Simplified" License
5.69k stars 300 forks source link

Breaking big modules into smaller modules: shared namespaces #1166

Open krader1961 opened 4 years ago

krader1961 commented 4 years ago

I dislike huge interactive shell init scripts; whether that's ~/.bashrc or ~/.elvish/rc.elv. So I typically break the initialization up into smaller modules. That, however, is problematic in Elvish because every use statement creates a new namespace. So if I do use ./prompt from inside my ~/.elvish/rc.elv the former does not have access to the vars defined in the latter, and vice-versa. In the past I would have used the experimental -source command but that no longer exists. It would be extremely useful if it was possible to use a module in manner that utilized the current namespace; e.g., use &same-namespace ./prompt.

I appreciate that the above proposal runs counter to best practices. Specifically, it is generally recommended that importing should always do so in a distinct namespace independent of the current namespace. Too, I can workaround the lack of this feature by separating the use (import) from the initialization the module performs such that the initialization requires an explicit invocation of a command in the module. But that is a bit of a PITA for this common use case.

Of course this is applicable to any module that might live in ~/.elvish/lib/. So another solution is to introduce something analogous to Go's package statement. That would require exposing the interactive (REPL) namespace so that it can be used as an explicit namespace; e.g., package _repl at the top of my ~/.elvish/prompt.elv module.

xiaq commented 3 years ago

Is this essentially proposing a way to import all symbols from another module directly into the current namespace - like Python's import * from foo.bar, or Go's import . "foo/bar"?

krader1961 commented 3 years ago

Is this essentially proposing a way to import all symbols from another module directly into the current namespace ...

Yes, conceptually it is similar; although the Python and Go equivalent didn't occur to me since $dayjob outlaws those import forms. Which I why I said "I appreciate that the above proposal runs counter to best practices." So I'm not wed to that proposal but would like to have some way to split an ungainly script (such as ~/.elvish/rc.elv) into smaller files. I've already done that with the statements that manipulate the $E: namespace since I can put those in a module named ~/.elvish/lib/env.lib and use env.

krader1961 commented 3 years ago

Also, resolving this might make it possible to eliminate $-exports- -- depending on the specifics of how this is resolved. For example, I want the utility functions, such as ff (find files), that I place in my ~/.elvish/lib/util.elv module to be usable in an interactive shell without the util: namespace prefix while still being able to use them in non-interactive scripts. I do this today by doing the following after use util in my rc.elv:

-exports- = [
   &coff~=$util:coff~
   &con~=$util:con~
   &ff~=$util:ff~
   &gitfiles~=$util:gitfiles~
   &nx~=$util:nx~
]
krader1961 commented 3 years ago

Related to my previous comment see also issue #1116 which illustrates why being able to import the public identifiers in a module into the current namespace is useful.

xiaq commented 3 years ago

So far the use case seems to be restricted to splitting rc.elv, which, assuming #1138 and #668 are implemented, can be done like this:

keys $local: | each [key]{ edit:add-var $key $local:[key] }

If this is a bit wordy, as part of #1138 we can add a utility edit:import-ns, and make this just edit:import-ns $local:.

Is there a compelling use case that is not about splitting rc.elv, i.e. about splitting a library namespace into multiple files?

krader1961 commented 3 years ago

Is there a compelling use case that is not about splitting rc.elv, i.e. about splitting a library namespace into multiple files?

In theory, yes. The obvious analog is how Go uses the package statement to allow splitting a package implementation across multiple files. I've only recently started writing standalone programs in Elvish; i.e., Elvish code not primarily meant to be used interactively. Those have been small enough that splitting them into multiple files hasn't been needed but I can certainly envision a time when I've written a program in Elvish that would benefit from being able to split into multiple files.