From: Magnus Holm Date: 2010-08-30T00:06:27+09:00 Subject: [ruby-core:31929] Proposal: Autoload with block = A proposal for autoload w/block: == autoload(:Constant, &blk) == autoload(&blk) When Constant is accessed, the following happens: 1. The block is called with the constant name as a parameter and the scope where it's looking for the constant as self: scope.instance_exec(:Constant, &blk) 2. If the block raises an error, the error isn't rescued 3. If the block raises a specific error or throws something specific, it signals that the constant is missing unless it has been set earlier in the block: 3a. It checks if the constant is defined now 3b. If not, the constant doesn't exists and it'll continue searching for the constant in the constant chain. 4. If the block succeeds: 4a. It sets the constant to result of the block If you don't specify a constant name to autoload, it will catch all missing constants, but otherwise it works exactly the same as above. There should also be a way to specify an autoload which triggers on all scopes. So with the given code: # Not sure about how we should specify it though: autoload(true) do throw :stop end module Foo::Bar p Hello end It will run the autoload block with respectivly Foo::Bar and Object as scope (as opposed to Foo::Bar, Foo and Object which it'll search through if you hack it up with const_missing). There are basically two scenarios which this API covers: - Easily use autoload without using #const_set: autoload(:C) { generate_C } - Run code which might set the constant: autoload(:C) { set_C; throw :stop } - The two scenarios above with an unspecified constant name. None of these changes previous behaviour and you can also create a Ruby monkey-patch which sort-of works similar through #const_missing (which you can use where this proposal isn't implemented). = Example of uses == Camping In Camping (http://whywentcamping.com) we use autoload to load optional features which requires dependencies. Example: # in camping.rb module Camping autoload :Template, "camping/template" end # in camping/template.rb require 'tilt' module Camping Template = Tilt end With the proposed changes to autoload, this could be written as: # in camping.rb module Camping autoload(:Template) { require 'tilt'; Tilt } end Which is way easier to maintain and understand for everyone. === Rails Rails overwrites const_missing in order to automagically load files (from different locations) when a constant is accessed. Example of how this could be implemented with the proposed autoload: autoload do |constant| # Convert FooBar to foo_bar.rb file = constant.to_s.underscore + '.rb' # Search through the list of directores: Rails.autoload_paths.each do |dir| full_path = File.join(dir, file) # Load the file and stop searching: File.exists?(full_path) && load(full_path) && break end throw :stop end This is also more correct than Rails' version, which can't use the proper cref because it uses const_missing: module Foo::Bar p Hello end Rails will search through Foo::Bar, Foo and Object instead of Foo::Bar and Object. This is really impossible to properly implement with const_missing, but is doable with autoload with blocks. === Implementing regular autoload in Ruby Just to show that this implementation is the general case of autoloading, implementing regular autoloading is really simple with this API: autoload(:Constant) { load(file); throw :stop } = Thoughts? What are your thoughts on: - A more general autoload solution? - This proposal? - Other ways to solve this "problem"? // Magnus Holm