Deja vu all over again

Posted by Piers Cawley on Jul 7, 2007

Back when I was still programming Perl, one of the common mistakes that you’d see when people wrote lazily initialized object accessors was code like:

sub get_content {
my($self) = @_;
$self->{content} ||= ‘default content’;
}

Code written like this would trundle along quite happily, until you had an attribute where, say, the empty string or 0 were legal values for the attribute. The problems were especially painful when the default value wasn’t something that perl treated as false. The correct way of writing that code would be:

sub get_content {
my($self) = @_;
unless (exists($self->{content})) {
$self->{content} = ‘default content’
}
$self->{content}
}

Which is substantially uglier, but safe.

Safety’s important, especially in building block code like accessor methods. An accessor method that works 99.99% of the time is like a compiler that produces correct code 99.99% of the time - useless.

Why déjà vu?

Recently, the usually spot on Jay Fields wrote up the lazy initialization pattern for Ruby. I’m not entirely sure that I agree with his motivation for the pattern, but I am concerned by his suggested code transformation. He suggests writing your lazy initialization as:

def content
@content ||= []
end

Does that look familiar? This is subject to exactly the same potential bug as the perl code above. Admittedly, the number of possible ‘bad’ values is reduced to nil and false, but it only takes one. Here’s the fix:

def content
unless instance_variable_defined? :content
content = []
end
return @content
end

This code is guaranteed to work in all circumstances.

Going a little bit further…

As Mark Jason Dominus has argued persuasively elsewhere, patterns are a sign of weakness in a programming language, so how can we go about incorporating this boilerplate code into our language[1].

How about something like this (as yet untested):

class Module
class << self
alias_method :attr_reader_without_default_block, :attr_reader
def attr_reader_with_default_block(*args, &default_block)
unless block_given?
return attr_reader_without_default_block(*args)
end
unless args.size == 1
raise ArgumentError, “Expected 1 argument”
end
var_name = “@#{args.first.to_s}”
self.define_method(args.first) do
unless instance_variable_defined?(var_name)
self.set_instance_variable(var_name, default_block.call(self))
end
(class << self; self; end).attr_reader_without_default_block(args.first)
return self.send(args.first)
end
end
alias_method :attr_reader, :attr_reader_with_default_block
end
end

This code is a little more complex than the boilerplate code. When the generated method is called, possibly initializing the attribute, the (class << self; self; end).attr_reader_without_default_block(args.first) part replaces the instance’s accessor with the default attr_reader implementation and calls that instead. This is arguably premature optimization, but it’s not all that evil…

Assuming I’ve not screwed anything up, that should allow you to write.:

class Article
attr_reader :content {‘default content’}
end

and have your content lazily initialized. Extending this to let attr_writer take a block too is a reasonably obvious next step.

Extending this lazy initialization approach to work with ActiveRecord based classes is probably the next step after that. Making it work right probably involves a little bit of fossicking around in the workings of ActiveRecord, but it’s far from impossible.

[1] I tend to take the Smalltalkish view that it’s pointless to separate language from library, especially in a dynamic language. A sufficiently expressive language lets you blur the boundary between them.