From: "shioyama (Chris Salzberg) via ruby-core" Date: 2023-05-14T03:47:55+00:00 Subject: [ruby-core:113471] [Ruby master Feature#19633] Allow passing block to `Kernel#autoload` as alternative to second `filename` argument Issue #19633 has been updated by shioyama (Chris Salzberg). I wrote: > I personally do not believe this is necessary. In fact, I realized this is not quite complete. I don't want to require file-level changes for two reasons. One, it requires changes to every file to make a project (gem or other autoloaded collection of files) loadable under an anonymous module. Those changes may be small, but the result is significant IMO. Concretely speaking, look at [rails_on_im](https://github.com/shioyama/rails_on_im) for an example. Here, I made some changes to Rails setup to replace Zeitwerk with Im and autoload an application under a single root namespace (`MyApp`). It mostly works (minus views, which I haven't dug into yet). This I think is a great example of the kind of change that this approach can bring, and would potentially avoid namespace collisions between an application and its gems, without any changes to the normal Rails way of doing things. Making each file change its `class` or `module` declarations to do this would mean that experimenting this approach would require changes to N files, where N is the total of all Zeitwerk-autoloadd files. This is a barrier to entry and a barrier to change that I do not feel brings great value, particularly in this case. The second reason I don't want to do this is, honestly, that if you are ready to make file-level changes to autoload under an anonymous namespace, then to me you don't really need a gem anymore. Or maybe you need a slightly different one, but regardless the problem becomes quite different. Specifically once the constraints are loosened to the point that you require the file to do something different, you might as well just change the top-level module/class declaration and avoid the constant creation altogether: ```ruby mod = Module.new module mod::Foo class Bar end end ``` Of course this is not enough, and we'd need to fetch/return `mod` here. But to me this renders the exercise much less interesting and useful. The whole reason I am interested in piggy-backing this approach on autoloading is that it eliminates boilerplate and makes it possible to capture an entire loadable file collection under a single module (the loader in Im). This also, btw, is a reason I was hoping this request here would be accepted. I really like the idea of "object-level autoloads", i.e. you have an object (`Im::Loader` in this case) and all autoloads are defined _on it_, rather than on a global namespace. This seems well-aligned to the ideas of OOP. In practice though, Im has to still hold a registry mapping file paths to loaders. The proposed change here would eliminate that registry, since the `autoload` could pass the module to `load` through the closure, removing the need for any global registry. So the loader becomes an encapsulated package whose trigger for loading code in files is `autoload`. Implementation concerns aside, I personally find this idea quite elegant. ---------------------------------------- Feature #19633: Allow passing block to `Kernel#autoload` as alternative to second `filename` argument https://bugs.ruby-lang.org/issues/19633#change-103051 * Author: shioyama (Chris Salzberg) * Status: Open * Priority: Normal ---------------------------------------- `Kernel#autoload` takes two arguments, a symbol `module` representing the constant to be autoloaded, and a `filepath` to load the constant from (with `require`). Currently, Zeitwerk has to [monkeypatch `Kernel#require`](https://github.com/fxn/zeitwerk/blob/a7c4a983df0f4e4058f32c610dac1e8b99f687da/lib/zeitwerk/kernel.rb) to fetch the loader for the file being loaded, then run the original (aliased) `require`, then run autoload callbacks. In addition to the monkeypatch, this also requires a registry (`Zeitwerk::Registry`) to map file paths to loaders, to know which loader should be used for a given autoload-triggered `require`. In fact, Zeitwerk has to _assume_ that the monkey-patched `require` call came from an autoload trigger; there is no way to really be sure of the source. If Ruby allowed passing a block as an alternative to the explicit filepath, then I think this could be improved and would eliminate the need for a monkeypatch. So something like this: ```ruby autoload(:B) do require "lib/b" # trigger callback, etc end ``` I am implementing a gem called [Im](https://github.com/shioyama/im) which is a fork of Zeitwerk, and in the case of this gem, such a feature would be even more useful. Im implements autoloads on anonymous modules by registering an autoload and then "catching" the require and converting it into a `load`, passing the module as the second argument (see [here](https://github.com/shioyama/im/blob/44ce348639a1aae563a5be7a40602761e9698d43/lib/im/kernel.rb).) This is currently quite tricky because, again, it's hard to know _where_ a `require` came from. In addition to removing the monkeypatch (inherited from Zeitwerk), Im would further benefit from the block argument because then it could simply access the module via a closure, rather than pulling it from a registry: ```ruby mod.autoload(:Foo) do load "lib/foo.rb", mod end ``` I don't know how hard or easy this would be to implement, but if there is interest in this as a feature I'd be happy to look into implementing it. -- 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/