From: "shioyama (Chris Salzberg)" Date: 2022-09-15T03:19:23+00:00 Subject: [ruby-core:109898] [Ruby master Feature#10320] require into module Issue #10320 has been updated by shioyama (Chris Salzberg). The `wrap` option to `load` has recently been expanded to allow passing a module instead of a boolean (https://bugs.ruby-lang.org/issues/6210). This opens the doors to new approaches to this problem. I think there is a huge opportunity to improve Ruby, and particularly to improve its "namespace hygiene", in the way JS module systems work in Javascript. Unlike others I've seen implemented in Ruby (including the [modules](https://github.com/lambdabaa/modules) gem, mentioned above), I'd like this to work not only for code that explicitly enables it (with `export`, etc.) but also (and mainly) for code (particularly gem code) that does not. I've created a gem called [Im](https://github.com/shioyama/im), which depends on a few patches in [this Ruby branch](https://github.com/shioyama/ruby/tree/import_modules) ([c43870](https://github.com/shioyama/ruby/commit/c4387043437b0851306ef199f9417be609c98827) is the main one, plus the bug fix in https://bugs.ruby-lang.org/issues/18960). My goal here is to load _existing gems_, including some of the ones we use often. I want to focus on something concrete which would potentially immediately impact how gems use the global namespace. With this gem and the patches provided, you can e.g. load `activemodel` and use it in this way: ```ruby require "im" extend Im # adds `import` method mod = import "activemodel" ``` At this point, `ActiveModel` has been loaded but it is not in the global namespace, and can only be found in `mod`: ```ruby ActiveModel #=> uninitialized constant ActiveModel (NameError) mod::ActiveModel #=> ActiveModel ``` What is very interesting to me is that, _using Ruby's built-in temporary/permanent naming functionality_, we can alias either all of the gem or parts of it. So for example, naming the module immediately names everything inside of it: ```ruby MyRails = mod ``` Now we have `MyRails::ActiveModel` (but no `::ActiveModel`). You can confirm with the example in the gem's readme that this namespaced ActiveModel works (at least for simple stuff, haven't tested more thoroughly yet). There are a few things I do to make this work: 1. In the Ruby patch, I make the `wrap` argument to `load` apply to further `require`s inside the loaded script. This is actually as simple as removing (actually commenting out) a line. 2. Apply the `top_wrapper` created in `load` to native extensions (specifically, `rb_define_class` & `rb_define_global_const`) 3. Make named modules/classes under an anonymous namespace return their name minus the namespace as their temporary name when called with `name`. This is necessary to use gems like Rails which use `name` to load files, etc. 4. Resolve top-level references (`::Foo`) when loaded with `wrap` to the top of the module namespace, rather than the "absolute" top. For now I've done this in the gem using `const_missing`, but I intend on moving this to the Ruby patch. With the changes above, the gem is able to setup a registry, patch `Kernel#require`, `Modul#autoload` etc and make this all work (sort of). To make this all work, I use a lot of aliasing constants from one module namespace to another. This actually seems to work pretty well! So for example, code like this (similar to ActiveSupport): ```ruby class NilClass def to_s # ... end end ``` would define a _new_ class under the module namespace. So I alias `mod::NilClass` and other predefined constants to `NilClass`, which then makes this work. What is amazing to me is _how little is required to make this work_. Obviously this is very much a proof of concept at this point (and I want to emphasize that). But if you look at the changes to Ruby, they only impact code that uses the `wrap` option to `load`, and there are only a few of them. Also, to me at least, they feel quite natural, since they are simply extending a concept that already exists (`top_wrapper`) to other places where it is not yet applied. ---------------------------------------- Feature #10320: require into module https://bugs.ruby-lang.org/issues/10320#change-99141 * Author: sowieso (So Wieso) * Status: Open * Priority: Normal ---------------------------------------- When requiring a library, global namespace always gets polluted, at least with one module name. So when requiring a gem with many dependencies, at least one constant enters global namespace per dependency, which can easily get out of hand (especially when gems are not enclosed in a module). Would it be possible to extend require (and load, require_relative) to put all content into a custom module and not into global namespace? Syntax ideas: ~~~ruby require 'libfile', into: :Lib # keyword-argument require 'libfile' in Lib # with keyword, also defining a module Lib at current binding (unless defined? Lib) require_qualified 'libfile', :Lib ~~~ This would also make including code into libraries much easier, as it is well scoped. ~~~ruby module MyGem ����require 'needed' in Need ����def do_something ��������Need::important.process! ����end end # library user is never concerned over needed's content ~~~ Some problems to discuss: * requiring into two different modules means loading the file twice? * monkeypatching libraries should only affect the module ����� auto refinements? * maybe also allow a binding as argument, not only a module? * privately require, so that required constants and methods are not accessible from the outside of a module (seems to difficult) * what about $global constants, read them from global scope but copy-write them only to local scope? Similar issue: https://bugs.ruby-lang.org/issues/5643 -- https://bugs.ruby-lang.org/ Unsubscribe: