From: "shioyama (Chris Salzberg) via ruby-core" <ruby-core@...>
Date: 2023-02-12T08:45:11+00:00
Subject: [ruby-core:112377] [Ruby master Feature#14982] Improve namespace system in ruby to avoiding top-level  names chaos

Issue #14982 has been updated by shioyama (Chris Salzberg).





For those following this issue, it is now possible in Ruby 3.2. to write autoloaded code that does not touch the global namespace, with Im, a fork of Zeitwerk I have been working on over the past couple months:



https://github.com/shioyama/im



Im relies on two new features in 3.2: `Kernel#load` with a module as second argument, and `Module#const_added`. Further details are provided in the readme, but the tl;dr is that it provides an interface almost identical to Zeitwerk, but rather than autoloading to `Object` it autoloads to an anonymous module (an instance of `Im::Loader`, which is a subclass of `Module`.)



I have also developed a demo Rails app which uses Im to load the entire application onto a single namespace, while allowing you to actually write the code as you normally would, at toplevel.



https://github.com/shioyama/rails_on_im



----------------------------------------

Feature #14982: Improve namespace system in ruby to avoiding top-level  names chaos

https://bugs.ruby-lang.org/issues/14982#change-101819



* Author: jjyr (Jinyang Jiang)

* Status: Open

* Priority: Normal

* Assignee: matz (Yukihiro Matsumoto)

----------------------------------------

Updated: https://bugs.ruby-lang.org/issues/14982#note-5







## Why



Ruby has evaluation all class/module names in top-level context(aka TOPLEVEL_BINDING). 

As a user we basically hard to know how many names in the current context, is causing chaos in some cases. For example:



case 1: 



Put common used errors class in a single file, like below



``` ruby

# utils/errors.rb



class FooError

end



class BarError

end

```



In other files under 'utils' we want to use those errors, so the best practice is to use `require_relative 'errors'` in each file we need.



``` ruby

# utils/binary_helper.rb



# we forget require errors



module BinaryHelper

# ...

  raise BarError

# ...

end



```



But sometime we may forget to require dependencies in a file, it's hard to notice because

 if RubyVM already execute the requires we still can access the name BarError,



but if user directly to require 'utils/binary_helper', he/she will got an NameError.





case 2: 



Two gems use same top-level module name, so we can't use them together



## The Reason of The Problem



The reason is we let module author to decision which module user can use. ('require' is basically evaluation, highly dependent on the module author's design)



But we should let users control which names to use and available in context. As many other popular languages dose(Rust, Python..)



I think the solution is basically the same philosophy compares to refinement feature.





## The Design



I propose an improved namespace to Ruby, to solve the problems and still compatible with the current Ruby module system.



``` ruby

class Foo

end



# introduce Kernel#namespace

namespace :Hello do

  # avoiding namespace chaos

  # Foo -> NameError, can't access TOPLEVEL_BINDING directly

  

  # Kernel#import method, introduce Foo name from TOPLEVEL_BINDING

  import :Foo



  # in a namespace user can only access imported name

  Foo



  # import constant to another alias name

  # can avoid writing nested module/class names

  import :"A::B::C::D", as: :E



  # require then import, for convenient 

  import :"A::B::C::D", as: :E, from: 'some_rb_file'



  # import same name from two gems

  import :"Foo", as: :Foo_A, from: 'foo_a'

  import :"Foo", as: :Foo_B, from: 'foo_b'



  # import names in batch

  import %i{"A::B::C::D", "AnotherClass"}, from: 'some_rb_file'



  # import and alias in batch

  import {:"A::B::C::D" => :E, :Foo => Foo2}, from: 'some_rb_file'



  class Bar

    def xxx

      # can access all names in namespace scope

      [Foo, Foo_A, Foo_B]

    end

  end

end



Hello.class #  -> module. namespace is just a module

Hello::Bar # so we do not broken current ruby module design



# namespace system is intent to let user to control names in context

# So user can choose use the old require way



require 'hello'



Hello::Bar





# Or user can use namespace system as we do in hello.rb



namespace :Example do

  import :"Hello::Bar", as: :Bar

  Bar # ok

  Foo # name error, cause we do not import Foo in :Example namespace

end



Foo # ok, cause Foo is loaded in TOPLEVEL_BINDING



# define nested namespace



# more clear syntax than ���module Example::NestedExample���

namespace :NestedExample, under: Example do

end



namespace :Example2 do

  namespace :NestedExample do

  end

end



```



Pros:



* Completely compatible with the current module system, a gem user can completely ignore whether a gem is write in Namespace or not.

* User can completely control which names in current context/scope.

* May solve the top module name conflict issue(depends on VM implementation).

* Avoid introducing new keyword and syntax.

* Type hint or name hint can be more accuracy under namespace(not sure).



Cons:



* Need to modify Ruby VM to support the feature.









-- 

https://bugs.ruby-lang.org/

 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/