Monday, May 21, 2007

Ruby Require Idiom

Ruby's "require" command can accidentally load the same file twice.

The following example shows this problem (2 files)...

#----------
# file: problem.rb
puts "#{__FILE__} loaded"

#----------
# file: run_me.rb
require '././problem' #=> .\problem.rb loaded
require 'problem' #=> ./problem.rb loaded



When run_me requires problem.rb using slightly different paths, Kernel#require loads it twice. This happens because Kernel#require tracks the files it has loaded in the $" array. It only does a String compare, so if the file names differ it doesn't find the match.

There are several ways to avoid loading the same file twice. The first technique is to call require using the absolute path of the file. Since every file only has one absolute path, Kernal#require will not get confused. To get the absolute path, we can use File.expand_path as shown in the following example.
#----------
# file: ok.rb
puts "#{__FILE__} loaded"

#----------
# file: run_me.rb
require File.expand_path('././ok') #=> C:/ruby/require/expand_path/ok.rb loaded
require File.expand_path('ok') #=>



Another effective technique is using indirection. That is, require a file that requires other files. This technique works really well for loading libraries and gems. The first file may get loaded multiple times because of arguments to Kernel#require. But once inside this file, all calls to Kernel#require are the same. So the sub-files are not loaded multiple times. The following example illustrates this solution.

#----------
# file: run_me.rb
require 'base' #=> ./required_by_base.rb loaded
require '././base'

#----------
# file: base.rb
$:.unshift(File.expand_path(File.dirname(__FILE__))) unless
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))

require 'required_by_base'

#----------
# file: required_by_base.rb
puts "#{__FILE__} loaded"



See how "base.rb" is required twice using different file paths. Kernel#require actually loaded "base.rb" twice. But "base.rb" didn't do anything except call Kernel#require to load other files. Since the parameter 'required_by_base' was the same in both calls, "required_by_base.rb" was only loaded once.

The first two lines of "base.rb" add the current directory to the search path. This wasn't really necessary for this example since the "required_by_base.rb" file is in the same directory. However, if the require used a relative path to another directory, it would have failed. By adding the current directory to the front of the search path this ensures the correct files are loaded.

If you look closely, you will see that the File.expand_path is used to put the absolute path into the search path. This avoids adding the same path twice.

Wednesday, May 02, 2007

DLR is here! Ruby soon to follow!

IronPython (2.0 Alpha) is now available with the Dynamic Language Runtime (DLR).

Microsoft also announced support for JScript on the DLR. They say releases of Ruby and VB will soon follow. (source: Jim Hugunin's Thinking Dynamic: A Dynamic Language Runtime)

This is great news to me.