raganwald
Thursday, January 24, 2008
  Object#andand & Object#me in Ruby
At Mobile Commons, we have to write a lot of code under time constraints. We also work on a fairly distributed basis, which means that writing readable code is not a luxury, it’s a necessity. We can’t afford to be clever for clever’s sake, but if a new idiom helps make our code easier for our colleagues to grasp in a hurry, we embrace it. Here’re some idioms we’ve recently created:

Object#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). The other day we 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. So what do we write instead?
A feature is “powerful” when at least one of the following holds:
  1. It can be used to implement something trivial in an pointlessly complicated way.
  2. It can cause a lot of damage.
Seriously, it seems like 85% percents of the contexts where something is called “powerful,” it really means “useless and dangerous.”
—Yossi Kreinin, Fun at the Turing Tar Pit

How about:

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

Note that we need a local variable to make it work without extending the Object class. Note further that although we only need the local variable for one line of code, it is in method scope and we must be careful that we don’t clobber an existing loc variable. And when reading this code, we now have to look and see whether loc is used again to understand what this line does.

This is a scoping problem. if you are going to use variables, you want to restrict their scope as much as possible. It feels like half of what we do when programming is manage side effects like overwriting a variable you are using elsewhere, and the more you can restrict those side effects, the easier programming becomes. Java supports block scoping natively, and languages like Scheme solve this by providing a block scope macro, let. And you can roll your own in languages like Javascript that provide anonymous functions. But this seems to get the natural left-to-right order wrong:

@phone = lambda { |loc| loc && loc.phone }.call(Location.find(:first, ...elided... ))

Well, we are tinkerers and toolsmiths. So let’s roll our own. We can’t use &&. without rewriting the Ruby parser, and that seems drastic. So let’s use “andand” in the hope that our non-English-speaking colleagues will forgive us. We want to write:

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

And get the same effect as @phone = ->(loc){ loc && loc.phone }.call(Location.find(:first, ...elided... )). This would be bloody trivial if we have a macro facility, but “If wishes were horses then beggars would ride.” Instead, we need to write a method that does some conditional evaluation for us. If the receiver (in this case the result of Location.find(:first, ...elided... )) is truthy, like an ActiveRecord model, we want andand to return it so we can use it. But if it is falsy, we want andand to somehow return something that takes any message you send it and returns itself without doing anything. In other words, we want a mock object of sorts. So that’s what we’ll do:

class Object
def andand (p = nil)
self or MockReturningMe.new(self)
end
end

class MockReturningMe
instance_methods.reject { |m| m =~ /^__/ }.each { |m| undef_method m }
def initialize(me)
@me = me
end
def method_missing(*args)
@me
end
end

Actually, there is more to it than this, for example the MockReturningMe class is enclosed in a module to minimize namespace pollution. Consult the source and RSpec (links below) for the latest particulars.

Note that because you accept any method using Ruby’s method invocation syntax, you can accept methods with parameters and/or blocks:

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

Some of the other solutions to this problem that use send change Ruby’s syntax. This solution 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.

Enhanced Object#tap

(Formerly called Object#me)

Ruby 1.9 includes Object#tap:

Location.find(:first, ...elided... ) do |location|
puts location.inspect
end
=> location

Ruby On Rails includes a similar feature, the returning idiom:

returning(Location.find(:first, ...elided... )) do |location|
...elided...
end
=> location

Both return the location, not whatever happens inside the block. This is very useful, especially when chaining methods in a pipeline. Sometimes you want to do something for side effects, but continue on with the chain. For example:

returning([1, 2, 3, 4, 5]) do |arr|
arr.pop
end.map { |n| n * 2 }
=> [2, 4, 6, 8]

There are going to be times that using a method instead of a function makes the code more consistent. So let’s imagine what that would look like:

Location.find(:first, ...elided... ).tap do |location|
...elided...
end…

This is much nicer when you are “injecting” some extra code into the middle of an expression, such as when adding some poor-man’s-debugging puts statements.

Would we ever want to go beyond a block and write Location.find(:first, ...elided... ).tap.some_method instead of Location.find(:first, ...elided... ).tap do ...elided... end? Of course we would! Quite often, we want to send several methods to the same receiver. In Smalltalk, the semicolon does this explicitly. In Ruby, we have to be very careful to make sure our methods return self to enable chaining. For example:

[1, 2, 3] << 4 << 5
=> [1, 2, 3, 4, 5]

But if a method doesn’t return the receiver, chaining doesn’t work. That’s why we had to use “returning” in the array example above:

[1, 2, 3, 4, 5].pop.map { |n| n * 2 }
=> NoMethodError: undefined method `map' for 5:Fixnum

That’s because [1, 2, 3, 4, 5].pop => 5, not [1, 2, 3, 4]. So instead, we write:

[1, 2, 3, 4, 5].tap.pop.map { |n| n * 2 }
=> [2, 4, 6, 8] # or we write...
[1, 2, 3, 4, 5].tap { |arr| arr.pop }.map { |n| n * 2 }
=> [2, 4, 6, 8] # both work

When you examine the source, you’ll see that tap looks a lot like andand. No surprise, we are doing similar things with slightly different semantics.

This implementation of Object#tap has two advantages over the implementation in Ruby 1.9: first, it works in Ruby 1.8. Second, it adds the ability to call another method (like pop) and not have to include a block.

Summary: andand & enhanced tap

andand and tap both provide the same two benefits: First, they either eliminate (in the case of a method call) or limit in scope (in the case of a block) a local variable. Second, they let you maintain a natural left-to-right order when writing pipelined expressions. They are simple and obvious enough that I feel any unfamiliarity for the reader will be more than outweighed by the cleaner, easier-to-digest code that results from their liberal use.

My suggestion is that these two benefits make it worth your while to add these methods to the Object class and use them regularly in your code.

Installation

sudo gem install andand. For the source and more information, http://andand.rubyforge.org.


Similar solutions:
  1. Kernel#ergo in Ruby Facets
  2. send_with_default
  3. maybe
  4. _?
  5. if_not_nil (via Fran├žois Beausoleil)
  6. Groovy’s Safe Navigation Operators via Call by Name: "Yo Elvis"
  7. try() and A better try



postscript: Inline Rescues

I have now seen two people asking about using an inline rescue instead of andand:

Location.find( …elided… ).phone rescue nil

Also, one of the alternate solutions seems to use this technique. Obviously, it works for many of the cases you want to try, and you don’t need to add a new method to Object. However, it is—to paraphrase Chalain—“Sneaking up on the Interpreter.”

It achieves what we want almost by accident, namely that it rescues NoMethodError, which by coïncidence gives us the result we want. However, it does not communicate our intent. It says that we want to transmute all exceptions into a value of nil. When someone glances at the code, will they think of NoMethodError? Or will they assume that we are trying to handle exceptions that the .phone method might throw?

And what if .phone actually throws something serious? In Rails, what happens if a migration is screwed up and there is no phone column? I think we actually want a NoMethodError in that case. The rescue clause will accidentally swallow the exceptions we are trying to catch.

I think if you are absolutely certain that you do not care about other exceptions and also if your team uses this idiom extensively so that it does not miscommunicate its intent, you can live a long and happy life using it. However, I would be hesitant to recommend it as a general-purpose solution. andand and some of the other solutions obviously require the code reader to learn a new method name, but after that they offer the exact functionality we need and clearly communicate their intent.

Labels:

 

Comments on “Object#andand & Object#me in Ruby:
We use Object#send_with_default to do a similar thing.

Location.find(:first).send_with_default(:phone, nil)
 
I came up with a similar solution a week or so ago:

Location.find(:first).maybe(:phone)

The advantage of this approach is that it's relatively simpler to implement:

class NilClass
def maybe(*a); self; end
end

class Object
alias :maybe :send
end

This approach also works with blocks, so it can be used with the new Object.tap method in Ruby 1.9:

Location.find(:first).maybe(:tap) do |location|
...elided...
end

Object.tap is equivalent to the returning method in ActiveSupport, and your Object.me method.

I guess it's mostly personal preference which syntax you prefer, of course:

Location.find(:first).andand.phone
Location.find(:first).maybe(:phone)
 
Maybe crops up again.
 
Unless I severely misunderstand the goal behind Object#andand, I was able to produce the desired effect simply with the following code:

class Object
def andand(p)
return self.send(p) if self.respond_to? p
self
end
end

It's very simple, in my opinion, and does not require creating a new proxy class.
 
I was able to produce the desired effect simply with the following code...

Are you able to write (expression...).andand.phone?

Are you able to write (expression).detect {...}.andand.inject(42) {...}?

There are a lot of very simple things you can do if your needs are limited to sending a message that has neither parameters nor a block. If you wish to be able to use andand anywhere you would use a normal method invocation, with as little deviation from the normal syntax as possible, you may want to consider andand.
 
andand reminds me of _?

http://hobocentral.net/blog/2007/08/25/quiz/

It was an attempt to get the .? operator from Groovy.

You can't quit get the a.?b syntax, but you can get a._?.b which is pretty close
 
Tom:

_? is similar to andand, thanks. I think the SafeNil class needs to be "BlankSlated" to avoid certain corner case problems.
 
This is a great idea for making Ruby better! However, this is merely a special case of a monad, which can be used for many more powerful applications, especially in purely functional languages. It would probably be better to implement a true monad system in Ruby rather than something with only a few uses.
 
Justin:

Ruby is a multi-paradigm language, borrowing ideas from Perl, Lisp, and Smalltalk. By all means write a general-purpose Monad feature for Ruby, it can go alongside of things like unfold and lazy evaluation.

However, I would stop a good ways short of saying that a full Monad implementation would be better. Language features embody trade-offs, and implementing Monads (with andand being the Maybe monad) involve a different set of trade-offs.

One of the things andand achieves is conformity with an existing mechanism for guarded assignment, &&=. If you implement Monads, shouldn't &&= also be implemented as a Monad?

Which leads to implementing = as a Monad as well. There is nothing wrong with going down that road, however by making different trade-offs you will be creating something that appeals to a different audience.
 
sorry, but what is the advantage of #andand over an inline rescue?
@phone = Person.find(foo).phone rescue nil
 
Riffraff:

Good to see you... After the Rocky Horror Picture Show, work was pretty thin for you... until Dark City. But I digress:

I personally would never use an inline rescue like this, it is kind of "sneaking up on the interpreter." It achieves what we want almost by accident, namely that it rescues NoMethodError, which by coïncidence gives us the result we want.

However, it does not communicate our intent. It says that we want to transmute all exceptions into a value of nil. When someone glances at the code, will they think of NoMethodError? Or will they assume that you are trying to handle exceptions that the .phone method might throw?

Worse, what if .phone actually throws something serious? In Rails, what happens if a migration is screwed up and there is no phone column? I think we actually want a NoMethodError in that case.

FWIW, one of the other solutions I linked used a similar scheme, so I may be the only one who rejected this approach.
 
nah, still, got a biw of work, between that ac/dc thingy and ken loach movie on me..

Anyway, yeah I would not use inline rescue for the general case, but since it's a kind of guarantee that phone is juts ana accessor method, I don't expect it to throw an exception.
As for handling serious errors: it's not catching Exception, but StandardError, so a missing migration would not be caught.

Yet, you're right, it doesn't communicate the intent strongly enough, thanks for taking time to answer.
 
Hi, why not use if_nil or if_not_nil ? I implemented this a while back for XLsuite.org: A little Smalltalk in Ruby: #if_nil and #if_not_nil

Your first example then becomes:

@phone = Location.find(:first, ...).if_not_nil {|l| l.phone}
 
why not use if_nil or if_not_nil ?

I like if_not_nil?, and if you look at the source you will see that andand implements that functionality as well, so if want to do several things with the value, you can do that:

BlahBlah.if_not_nil do |blah| ... end

or

BlahBlah.andand do |blah| ... end

However, for sending a single message we were trying to make something that resembles guarded assignment, so you could say BlahBlah.andand.do_domething without a block.

But I also like if_not_nil.
 
François:

So, the longer answer in my mind is that if_not_nil sounds like an if expression, so I would view it as an if expression that happens to perform a binding in the bargain.

That is very useful. But it still looks a lot like an "if," it emphasizes the conditional.

Should I be writing some code where the nil case is really important, I might use if_not_nil as you suggest. However, if I wanted to invoke a single message and I wanted to emphasize the message and not the conditional nature, I would prefer andand. I think these two statements say slightly different things even though the both produce the same result:

Location.find(:first, ...).if_not_nil { |loc| loc.phone }

Location.find(:first, ...).andand.phone

The "if_not_nil," the block, and the block parameter all add weight to the condition, suggesting we ought to think about it more than we think about getting the phone.

Whereas the andand expression de-emphaiszes the condition, emphasizing the phone method.

I would happily use both in one code base, but I would try to use them to communicate what is important about that particular expression.
 
This reminds me of the dwim idiom in elisp, which stands for "do what I mean". It's used when a function should usually, but not always, do what it says it does. For example, rejustify always rejustifies your buffer but dwim-rejustify would fail silently if you ran it on a binary file.

I think "andand" as a term is a little bit opaque, but I can't think of anything less opaque that isn't significantly more bulky. I would have been tempted to call it Object#just_work_dammit or Object#with_your_shield_or_on_it or perhaps Object#pleaseohplease, neither of which has that same zippy and quite pronounceable feel.
 
My first thought was to recommend Superators, but alas they do not work with logical operators. However, you could maybe craft up some interesting operator like "~~", but Superators may be bordering on write only.
 
Do put me right if this is an incredibly stupid thing to do, but what about...

class NilClass
def method_missing(method, *args)
nil
end
end

You're already extending the Object class so I'm assuming core classes in general is fair game here.

There is one problem that I can see, which is that Location.find(:first).phone will throw a NoMethodError if a Location is found, but will return nil if no location is found (for the case where Location#phone is not defined). Still, I've found it useful for some things.
 
Someone on ruby-talk posted this a while ago and it's part of facets, the ergo() method:

http://facets.rubyforge.org/quick/rdoc/core/classes/Kernel.html#M000357
 
This post has been removed by the author.
 
I came up with something like if_not_nil independently, but I just call it "if":

find(...).if {|x| x.bugle()}

I find that to be much shorter and clearer than using an "if" statement. Plus, it keeps x scoped, instead of exposing it to the whole method.
 
return Location.find(:first).nil_or.phone
 
twould be nice to alias andand with _? for ease of use :)
-R
 
Roger:

Help yourself, simply open the Object class and alias andand for _?
 
now if we could somehow use it thus:
old_cost = dad_variation.old_cost if dad_variation.andand.old_cost.is_text?

old_cost = dad_variation.andand.old_cost.only_assign_if_is_text_then_I_wouldnt_have_to_type_dad_variation_twice

Oh well :)
Oh wait maybe I could just rewrite the AST to yield to my bidding :P

If only it worked in 1.9...

-R
 
So in andand.rb:

def andand
...
end

alias _? andand

should create the alias method _+ for andand I guess ;)
 




<< Home
Reg Braithwaite


Recent Writing
Homoiconic Technical Writing / raganwald.posterous.com

Books
What I‘ve Learned From Failure / Kestrels, Quirky Birds, and Hopeless Egocentricity

Share
rewrite_rails / andand / unfold.rb / string_to_proc.rb / dsl_and_let.rb / comprehension.rb / lazy_lists.rb

Beauty
IS-STRICTLY-EQUIVALENT-TO-A / Spaghetti-Western Coding / Golf is a good program spoiled / Programming conventions as signals / Not all functions should be object methods

The Not So Big Software Design / Writing programs for people to read / Why Why Functional Programming Matters Matters / But Y would I want to do a thing like this?

Work
The single most important thing you must do to improve your programming career / The Naïve Approach to Hiring People / No Disrespect / Take control of your interview / Three tips for getting a job through a recruiter / My favourite interview question

Management
Exception Handling in Software Development / What if powerful languages and idioms only work for small teams? / Bricks / Which theory fits the evidence? / Still failing, still learning / What I’ve learned from failure

Notation
The unary ampersand in Ruby / (1..100).inject(&:+) / The challenge of teaching yourself a programming language / The significance of the meta-circular interpreter / Block-Structured Javascript / Haskell, Ruby and Infinity / Closures and Higher-Order Functions

Opinion
Why Apple is more expensive than Amazon / Why we are the biggest obstacles to our own growth / Is software the documentation of business process mistakes? / We have lost control of the apparatus / What I’ve Learned From Sales I, II, III

Whimsey
The Narcissism of Small Code Differences / Billy Martin’s Technique for Managing his Manager / Three stories about The Tao / Programming Language Stories / Why You Need a Degree to Work For BigCo

History
06/04 / 07/04 / 08/04 / 09/04 / 10/04 / 11/04 / 12/04 / 01/05 / 02/05 / 03/05 / 04/05 / 06/05 / 07/05 / 08/05 / 09/05 / 10/05 / 11/05 / 01/06 / 02/06 / 03/06 / 04/06 / 05/06 / 06/06 / 07/06 / 08/06 / 09/06 / 10/06 / 11/06 / 12/06 / 01/07 / 02/07 / 03/07 / 04/07 / 05/07 / 06/07 / 07/07 / 08/07 / 09/07 / 10/07 / 11/07 / 12/07 / 01/08 / 02/08 / 03/08 / 04/08 / 05/08 / 06/08 / 07/08 /