P
Phrogz
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 arent
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 arent
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 arent
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.
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 arent
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 arent
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 arent
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.