I wrote a bit about function objects here. However, if you don’t buy that the persistent state of function objects provides something that anonymous functions cannot, how about this: readability. In some cases.
Anonymous functions are boss and cool, and extremely common in idiomatic Ruby. However, in some cases they can get a little… esoteric. Consider:
people.sort do |lhs, rhs|
lhs, rhs = rhs, lhs if ascending?
result = lhs.name <=> rhs.name
if result == 0
result = lhs.date_of_birth <=> rhs.date_of_birth
end
# etc...
end
Sometimes, anonymity isn’t the answer. Consider:
class ByNameAscending
def self.to_proc
Proc.new { |lhs, rhs| rhs.name <=> lhs.name }
end
end
This allows you to write this:
people.sort(&ByNameAscending)
Or, to push the example to the extreme:
class SortOrder
def initialize(direction = :descending)
@direction = direction
end
def by(attribute)
attributes << attribute
self
end
alias_method :and, :by
def to_proc
Proc.new do |lhs, rhs|
lhs, rhs = rhs, lhs if ascending?
return lhs <=> rhs if attributes.empty?
attributes.each do |attribute|
result = lhs.send(attribute) <=> rhs.send(attribute)
return result if result != 0
end
0
end
end
private
def attributes
@attributes ||= []
end
def ascending?
@direction == :ascending
end
end
def ascending; SortOrder.new(:ascending); end
Which gives us:
people.sort(&ascending.by(:name).and(:date_of_birth))
A DSL for generating sort order function objects. It could be useful.
i really like the syntax of people.sort(&ascending.by(:name).and(:date_of_birth))
Did you consider using sort_by? It would be faster and easier to write.
November 12, 2008 at 9:28 am
Very cool
November 12, 2008 at 3:46 pm
You’re absolutely right that #sort_by would simplify this function. To be honest, I tried to come up with an example with a fair bit of complexity in the proc in order to illustrate the readability improvements of using a function object, and it ended up being a bit contrived. A function that does something to circumvent #< => would have been better, although I can’t think of anything even vaguely reasonable at the moment.
And, thinking about it just for a moment, I can’t think of a good way to make the syntax read as nicely with #sort_by.
I’m bad at examples, but hopefully the general idea makes sense. I’m always interested to know of any real-world instances where this sort of thing turned up useful.
November 12, 2008 at 3:53 pm
Or
attributes.collect { |attribute| lhs.send(attribute) } < => attributes.collect { |attribute| rhs.send(attribute) }
instead of
attributes.each do |attribute|
result = lhs.send(attribute) < => rhs.send(attribute)
return result if result != 0
end
It demonstrates that you can sort on multiple attributes by slapping them in an array.
It’s probably worse than the original, but it *is* one line of code. :)
November 12, 2008 at 6:25 pm