From: "fxn (Xavier Noria) via ruby-core" Date: 2023-05-12T21:57:25+00:00 Subject: [ruby-core:113461] [Ruby master Feature#19633] Allow passing block to `Kernel#autoload` as alternative to second `filename` argument Issue #19633 has been updated by fxn (Xavier Noria). Hey, sorry for not replying earlier, for some reason messages from Redmine often end up in my spam folder. > I know you're not in favour of what I'm doing with autoloads on anonymous modules That is not true, why do you think that? We have barely talked about your project, and the few interactions that we had in Twitter were positive and encouraging from my side, no? The reason Zeitwerk does not support anonymous modules today is practical, it is not something I am against it, or refuse to ever support. First, Zeitwerk does not have isolation as a goal. Zeitwerk is designed for regular applications and gems that want their code to be accessible as in regular Ruby. In particular, your files are normal and have to define things normally, don't get the nesting modified. So, how can a regular project have an anonymous root namespace? It cannot be in a constant, is it going to be in a global variable? Who does that? I believe it was and edge case that did not deserve to spend time. Why the validation, because and knowing that things have an ordinary constant path simplifies some internal assumptions, so I validate in the boundary to be confident downwards that is the case. > In fact, Zeitwerk has to assume that the monkey-patched require call came from an autoload trigger; This is not accurate. Zeitwerk does not assume that, really. It is weaker. What Zeitwerk knows is that if the file is registered, the corresponding loader is in charge of that file. That means it has to trace is loading, has to run callbacks, etc. That is a fact. No matter who ran the `require`. Who ran the `require` is irrelevant. Even more, Zeitwerk supports `require` calls made by 3rd party! [These lines](https://github.com/fxn/zeitwerk/blob/d14b16c90370e5c6406fe73403f158c59fc9e369/lib/zeitwerk/kernel.rb#L40-L43). If your project has some rogue `require`s, or you migrate an existing gem to Zeitwerk, and existing client code is issuing `require`s, things will work. It is not the case that you are forced to autoload and if a rogue `require` happens somewhere the project is broken. No, it is more robust than that on purpose. And that is one of the reasons I believe Zeitwerk would not use the proposed block. Because I want that robustness. Also, `require` has some concurrency control builtin which eases thinking about concurrency. That is not to say I am against the block, eh? Only saying I don't quite see it used by Zeitwerk at the moment. I would prefer to not decorate `Kernel`, indeed, and I believe what would really work for Zeitwerk would be a callback associated to `Kernel#require`: If a file is `require`d, invoke the callback with the `require` argument, and the absolute path to the file. A different topic in this proposal is backwards compatibility. Today, `Module#autoload?` returns the file name if set, a string. With this addition, the natural return value for a block argument would be the block, but a block is not a string. I use that to know if a constant associated to a file being visited has an autoload set (maybe by another project, it is an edge case of shadowed file). ---------------------------------------- Feature #19633: Allow passing block to `Kernel#autoload` as alternative to second `filename` argument https://bugs.ruby-lang.org/issues/19633#change-103040 * 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/