Useful uses for instance-specific methods

I ran into two specific cases this weekend where singleton methods
made my code feel a lot cleaner. For your enjoyment, I’ll show them
both here.

Here’s the first version of the code:

class WordTreeNode
attr_accessor :parent
attr_accessor :text_value
attr_accessor :source_value
def initialize( text_value=nil )
@children = []
@text_value = text_value
end
def <<( kid )
@children << kid
kid.parent = self
end
def text
@text_value || @children.map{ |kid| kid.text }.join
end
def source
@source_value || (@parent && @parent.source)
end
end

root = WordTreeNode.new
root.source_value = “Bob”
root << (greeting = WordTreeNode.new)
root << (query = WordTreeNode.new)
greeting << WordTreeNode.new( "Hi there! " )
query << WordTreeNode.new( "What are you " )
query << WordTreeNode.new( “doing tonight?” )

p root.text
#=> “Hi there! What are you doing tonight?”
p query.source
#=> “Bob”

Now, we clean it up a little bit to simplify the implementation of
#source and #text:
class WordTreeNode
attr_accessor :parent
def initialize
@children = []
end
def <<( kid )
@children << kid
kid.parent = self
end
def text
@children.map{ |kid| kid.text }.join
end
def source
@parent && @parent.source
end
end
class RootNode < WordTreeNode
attr_accessor :source
def initialize( source )
super()
@source = source
end
end
class LeafNode < WordTreeNode
attr_accessor :text
def initialize( text )
super()
@text = text
end
end

root = RootNode.new( “Bob” )
root << (greeting = WordTreeNode.new)
root << (query = WordTreeNode.new)
greeting << LeafNode.new( "Hi there! " )
query << LeafNode.new( "What are you " )
query << LeafNode.new( “doing tonight?” )

But we can also choose not to have those as separate classes:
class WordTreeNode
attr_accessor :parent
def initialize
@children = []
end
def <<( kid )
@children << kid
kid.parent = self
end
def text
@children.map{ |kid| kid.text }.join
end
def source
@parent && @parent.source
end
def self.leaf( text )
node = self.new
node.instance_eval{
@text = text
def text
@text
end
}
node
end
end

root = WordTreeNode.new
def root.source
“Bob”
end
root << (greeting = WordTreeNode.new)
root << (query = WordTreeNode.new)
greeting << WordTreeNode.leaf( "Hi there! " )
query << WordTreeNode.leaf( "What are you " )
query << WordTreeNode.leaf( “doing tonight?” )

p root.text
#=> “Hi there! What are you doing tonight?”
p query.source
#=> “Bob”

The above code is less convoluted in Io, where I actually performed
this. (In Io you don’t have to set a string value and then write a
method that returns that value…you simply set the “text” or “source”
message on the instance to be the string.) But hopefully it shows
you an interesting case where duck typing and per-instance-methods can
overlap to easily handle the edge cases.