Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
82 lines (58 sloc) 3.88 KB

Andand

Andand lets us write:

@phone = Location.find(:first, ...elided... ).andand.phone

And it is rewritten as:

@phone = (__TEMP721347__ = Location.find(:first, ...elided... ) and __TEMP721347__.phone)

This gives us a guarded method invocation or safe navigation method. This snippet performs a .find on the Location class, then sends .phone to the result if the result is not nil. If the result is nil, then the expression returns nil without throwing a NoMethodError.

As Dejan Simic put it:

Why would you want to write this:

entry.at('description') && entry.at('description').inner_text

when you can write this:

entry.at('description').andand.inner_text

Why indeed!

The idea behind Andand

Ruby programmers are familiar with the two guarded assignment operators &&= and ||=. The typical use for them is when you have a variable that might be nil. For example:

first_name &&= @first_name.trim
@phone ||= '612-777-9311'

You are trimming the first name provided it isn’t nil, and you are assigning ‘612-777-9311’ to the phone if it is nil (or false, but that isn’t important right now). One day a colleague and I were discussing the guards and we agreed that we wished there was a guarded method invocation operator. Here’s an example of when you would use it:

@phone = Location.find(:first, ...elided... )&&.phone

Meaning, search the location table for the first record matching some criteria, and if you find a location, get its phone. If you don’t, get nil. (Groovy provides this exact functionality, although Groovy uses ?. instead of &&.) However, &&. won’t work because &&. is not a real Ruby operator.

Andand let’s us write:

@phone = Location.find(:first, ...elided... ).andand.phone

And it does the same thing by rewriting the code into ordinary Ruby:

@phone = (__TEMP721347__ = Location.find(:first, ...elided... ) and __TEMP721347__.phone)

Because you accept any method using Ruby’s method invocation syntax, Andand also lets us write methods with parameters and/or blocks:

list_of_lists.detect { ...elided... }.andand.inject(42) { ...elided ... }

And it is again rewritten into ordinary Ruby for you:

(__TEMP721347__ = list_of_lists.detect { ...elided... } and __TEMP721347__.inject(42) { ...elided ... })

Andand emphasizes syntactic regularity: the goal was to make an &&. operation that worked like &&=. &&= looks just like normal assignment, you can use any expression on the RHS, only the semantics are different. The andand method also works just like a normal method invocation, only the semantics are modified.

And there’s a third form, a little like the and keyword and the return method’s illegitimate love child:

Location.find(:first, ...elided... ).andand do |phone|
  phone.call
  directory << phone
  phone.area_code
end

Is rewritten as:

(phone = Location.find(:first, ...elided... ) and begin
  phone.call
  directory << phone
  phone.area_code
end)

A little more background

Object#andand & Object#me in Ruby explains the original motivations, as well as providing links to similar implementations you may want to consider. A few people have pointed out that Andand is similar to Haskell’s Maybe monad. The Maybe Monad in Ruby is a good introduction for Ruby programmers.

Andand even more is an interesting read.

Shout Out

Mobile Commons. Huge.