Close
Glad You're Ready. Let's Get Started!

Let us know how we can contact you.

Thank you!

We'll respond shortly.

LABS
Functional witness protection

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.

Comments
  1. grosser says:

    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.

  2. Steve Conover says:

    Very cool

  3. Adam Milligan says:

    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.

  4. Mark Wilden says:

    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. :)

Post a Comment

Your Information (Name required. Email address will not be displayed with comment.)

* Copy This Password *

* Type Or Paste Password Here *