Pivotal Labs

Main menu

Skip to primary content
Skip to secondary content
  • About
  • Case Studies
  • Team
    • Executives
    • Locations
      • San Francisco (HQ)
      • Boston
      • Boulder
      • Denver
      • London
      • Los Angeles
      • New York
  • Community
    • Blogs
    • Tech Talks
    • Events
  • Careers
    • Lifestyle
    • Principles & Practices
    • Benefits
    • FAQ
    • Apply
  • Contact
    • Press Room
    • Press Releases
    • In The News
    • Press Kit
  • All
  • Labs
  • Standup
  • Tracker

Functional witness protection

Adam Milligan
Tuesday, November 11, 2008

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.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter

4 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.

    November 12, 2008 at 9:28 am

  2. Steve Conover says:

    Very cool

    November 12, 2008 at 3:46 pm

  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.

    November 12, 2008 at 3:53 pm

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

    November 12, 2008 at 6:25 pm

Add New Comment Cancel reply

Your email address will not be published.

Adam Milligan

Adam Milligan
New York

Recent Posts

  • Why not to use ARC
  • Cedar Expectations
  • The Trouble With Expectations in Objective C
Subscribe to Adam's Feed

Author Topics

blocks (2)
cplusplus (4)
ios (10)
objective-c (13)
cedar (11)
testing (16)
opensource (5)
xcode4 (1)
rake (4)
access control (3)
ci (2)
jasmine (1)
javascript (3)
ie6 (1)
addiction (1)
activerecord (7)
nested_attributes (1)
rails (12)
actionpack (1)
refactoring (1)
agile (4)
ruby (8)
actionview (1)
threads (1)
functors (2)
brownbags (2)
solr (1)
  • About
  • Case Studies
  • Team
  • Community
  • Careers
  • Contact
  • Labs
  • Events

Contact Us

contact@pivotallabs.com
+1 415-77-PIVOT
TwitterLinkedInFacebook

Pivotal Tracker

Tracker is the award-winning agile project management tool that enables real-time collaboration around a shared, prioritized backlog.
Visit pivotaltracker.com >