5 ms·
There's at least a few? E.g. * Lexical scoped imports a-la python * Pass state to modules explicitly, which I think is what newspeak was doing (i.e there's no
by riffraff 3y ago
There's at least a few? E.g.
* Lexical scoped imports a-la python
* Pass state to modules explicitly, which I think is what newspeak was doing (i.e there's no global state)
Either way you'd lose ruby's "let's add methods to Kernel and have them pop up everywhere" so it's a trade off.
Worth nothing: there are a few proposals around to have some sort of namespaces in Ruby, but it's unclear they'll ever happen.
E.g.
https://bugs.ruby-lang.org/issues/19744 https://bugs.ruby-lang.org/issues/19744
- thaumasiotes 3y ago> Lexical scoped imports a-la python How is this an alternative to modifying the global interpreter state? Before I do any imports, I can type this: os.getcwd() and the interpreter will crash, telling me that "os" doesn't mean anything. But if I do this: import os then, later, I can type exactly the same thing: os.getcwd() and the interpreter will call the function and return a string. In order for the interpreter's behavior to be different when presented with exactly the same input, it's necessary that the state of the interpreter has changed. What's the alternative? And since this is the entire purpose of a module system -- causing the global interpreter to recognize names that it otherwise wouldn't have -- it follows immediately that all module systems will operate by making changes to a global interpreter state. The reason Python's module system is better than C's has nothing to do with whether or not either system modifies a global interpreter state, since that doesn't differ between the systems. The reason is that Python allows you to specify what you want those modifications to be. If I have two Python libraries named os, I can import them like so: from lib1 import os from lib2 import os as os2 and now they don't conflict. As far as I know, C has no such facility.
- mirekrusin 3y agoIf you qualify imports transitive dependencies won’t pollute your scope. It also naturally deals better with name conflicts, allows cherry picking exports etc.
- thaumasiotes 3y agoOK, where's the part of this where we're not modifying a global interpreter state?
- tomstuart 3y agoYou’re taking the use of the word “state” too literally. The original comment was complaining about how Ruby `require` adds names (constants) to a global namespace; Python `import` only binds names in the local scope.
- jeltz 3y agoBy that definition I would not say any language has modules. Even compiled ones
- movpasd 3y agoI expect _global_ here refers to the language-level semantics, not internal interpreter semantics. Python's module system results in no implicit modifications to Python-level state between modules — at least, for a well-developed module. Since Python is so dynamic, you can of course jerry-rig all sorts of global consequences if you really want to. But without dynamic magic, all state is namespaced to the imported module, and must be explicitly given names within the importer, with no global consequences for parallel modules. In your os.getcwd() example, the state is scoped to the importer module, so it's not an example of global interpreter state. I don't know what Ruby's module system looks like, so I don't know for sure if that is the point the GP is making. It seems to me that you are in agreement, just disagree with the meaning of "global" in this context.
- thaumasiotes 3y agoBut by this interpretation, the description of #include is incorrect, and `require` wouldn't be analogous to `#include`. That #included code appears at the highest scope in a file is a coding convention, not a fact about the preprocessor. That's just the place where people like to put their #include directives. The reason they do that, of course, is that they want to be able to access #included code from the highest scope in their file. But once that's the requirement, we're back to saying that this is the goal of all module systems, and all of them will modify the global namespace in this way. > all state is namespaced to the imported module, and must be explicitly given names within the importer, with no global consequences for parallel modules. This is just false; state appears within the interpreter in the form of registering the imported module under the provided name. You are free to register a name that's already in use, in which case you will lose the ability to refer to the module that was registered with the same name earlier. Becoming unreachable is ordinarily considered a "consequence"; you can find people complaining that this has happened to them. Why does the problem arise? Because importing means making changes to the state of the interpreter.
- dragonwriter 3y agoRuby's modules are namespaces (well objects that are deeply similar to class objects, but not identical), but Ruby files aren't modules, they are executed in the global environment, and may (or may not) define (or redefine, modules are mutable) modules, and any such modules may or may not have any relationship to the file name and relative path location.
- Joker_vD 3y ago> it follows immediately that all module systems will operate by making changes to a global interpreter state. No, it doesn't? def print_sysinfo(): import sys, os print (f'{sys.platform} {sys.version}, current dir is {os.getcwd()}' os.getcwd() # throws NameError, because os is not a thing there. Now, you may argue that the 'os' module is still loaded after a call to print_sysinfo() and while it's true, it didn't have to be so: Python could as well had have semantics of unloading modules that have zero references to them, and re-loading them anew every time they are imported again. A language can have locally-scoped module semantics, it just that most of the time the static/global scope is much more convenient.
- thaumasiotes 3y agoWhat happens here? ---main.c---- void f(void); int main(void) { int a = 0; f(); return a; } void f(void) { #include aux.c } ---aux.c----- int a = 1; By your argument, #include doesn't make changes to global interpreter state either. Is there a sense in which the #include preprocessor directive works by making changes to the state of the global interpreter, but Python's import mechanics don't work by doing the same thing? There's only one interpreter and your goal is to make a change to its state. Hence, "global interpreter state". The change you make can affect behavior in, in principle, any context in which the interpreter is used. Could be inside the definition of `f()`. Could be outside the definition of `f()`. There's no difference in the mechanics.
- dvdkon 3y agoAt that point everything is "a change to global interpreter state", even a locally created object allocates memory from a global allocator. So clearly this distinction as you present it doesn't matter. What matters is language (not interpreter) semantics, here it's about variable scoping. In C, if I include "a.h" and then "b.h", "b.h" will have access to definitions from "a.h", even though lexically they were never brought into scope in that file. This is an actual problem that C/C++ developers have to deal with, since it makes it hard to create isolated headers. In Python, the code in each module is evaluated in an isolated context, and returned as an object, so this isn't a problem. Edge cases like monkey patching don't really matter for >99% of usecases.
- psychoslave 3y ago>In order for the interpreter's behavior to be different when presented with exactly the same input, it's necessary that the state of the interpreter has changed. What's the alternative? I guess that scoped changes is an option, that is you ask to alter the interpreter behavior only in a lexical scoped. Ruby has refinement in this spirit, though it’s not exactly operating at the same level as a require. Here is a related discussion that arised in Crystal community a few years ago: https://github.com/crystal-lang/crystal/issues/4439 https://github.com/crystal-lang/crystal/issues/4439
- Izkata 3y agoThere's another big difference between Python and C (and I don't know how Ruby does it): When a module is imported, the file is evaluated once, then stored in memory as a stateful object. Further imports of the same module, even from other files, reference the object in memory instead of evaluating the module again. The dict used for this is accessible in sys.modules
- matheusmoreira 3y agoI think I expressed myself poorly. You're correct that the ultimate result of all useful code is changes to interpreter state. Even a simple variable assignment creates an object in memory and a mapping from the variable to that object. I meant to allude to the fact Ruby has a single global namespace that all Ruby code adds bindings to. In Ruby we have to manually namespace stuff just like we add prefixes to functions in C. Python on the other hand creates isolated namespaces for each module which makes it impossible for them to clobber each other. Import creates links between those isolated namespaces.