Thursday, October 22, 2009

Recipe 9.6. Automatically Loading Libraries as Needed










Recipe 9.6. Automatically Loading Libraries as Needed





Problem


You've written a big library with multiple components. You'd like to split it up so that users don't have to load the entire library into memory just to use part of it. But you don't want to make your users explicitly require each part of the library they plan to use.




Solution


Split the big library into multiple files, and set up
autoloading for the individual files by calling
Kernel#autoload
. The individual files will be loaded as they're referenced.


Suppose you have a library, functions.rb, that provides two very large modules:



# functions.rb
module Decidable
# … Many, many methods go here.
end

module Semidecidable
# … Many, many methods go here.
end



You can provide the same interface, but possibly save your users some memory, by splitting functions.rb into three files. The functions.rb file itself becomes a stub full of
autoload
calls:



# functions.rb
autoload :Decidable, "decidable.rb"
autoload :Semidecidable, "semidecidable.rb"



The modules themselves go into the files mentioned in the new functions.rb:



# decidable.rb
module Decidable
# … Many, many methods go here.
end
# semidecidable.rb
module Semidecidable
# … Many, many methods go here.
end



The following code will work if all the modules are in functions.rb, but it will also work if functions.rb only contains calls to autoload:



require 'functions'
Decidable.class # => Module
# More use of the Decidable module follows…



When Decidable and Semidecidable have been split into autoloaded modules, that code only loads the Decidable module. Memory is saved that would otherwise be used to contain the unsed Semidecidable module.




Discussion


Refactoring a library to consist of autoloadable components takes a little extra planning, but it's often worth it to improve performance for the people who use your library.


Each call to Kernel#autoload binds a symbol to the path of the Ruby file that's supposed to define that symbol. If the symbol is referenced, that file is loaded exactly as though it had been passed as an argument into require. If the symbol is never referenced, the user saves some memory.


Since you can use autoload wherever you might use require, you can autoload builtin
libraries when the user triggers some code that needs them. For instance, here's some code that loads Ruby's built-in set library as needed:



autoload :Set, "set.rb"

def random_set(size)
max = size * 10
set = Set.new
set << rand(max) until set.size == size
return set
end

# More code goes here…



If random_set is never called, the set library will never be loaded, and memory will be saved. As soon as random_set gets called, the set library is autoloaded, and the code works even though we never explicitly require 'set':



random_set(10)
# => #<Set: {39, 83, 73, 40, 90, 25, 91, 31, 76, 54}>

require 'set' # => false














No comments: