You might be able to stop trying to call methods on nil

Correct me if I’m mistaken. Please.

The people behind andand, try, do_or_do_not, NilClass#method_missing, SafeNil, maybe, etc. seem to be trying to beautifully solve the problem of calling methods on objects that might be nil and not having Ruby complain about it. They might be chaining methods a lot or, when they check if an object is nil, they use the ternary conditional and (rightly) think it’s ugly.

As for these ternary conditional users, you can do this: @person.name if @person instead of @person ? @person.name : nil. It does what you need, looks great, and is highly readable.

Of course, that doesn’t help you if you’re chaining methods.

But (and this is what’s been on my mind), I suspect that all of these solutions value terseness over readability and flexibility. It’s a trade off I’m willing to make sometimes, but I think it’s unnecessary here.

For example, Reg Braithwaite’s example:

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

We’re trying to get the phone number of a location, but maybe we won’t find the location. Seems normal on the surface. But these are the questions that run through my mind looking at this:

  1. All you need is the phone number?
  2. You’re sure you don’t need anything else from the Location object? (address, url, hours, etc.)
  3. It doesn’t mean anything if the location isn’t found?

I find it hard to believe that the answer to all these questions is “Yes”. I know this is example code, but it must be inspired from something, right?

I’d bet that instead of @phone = Location.find(:first, ...elided... ).andand.phone, later on you’d really wish you had this:

@location = Location.find(:first, ...elided... )

Maybe you’d discover your view (Reg’s example is ActiveResource-related, I’m running with that) needs more than just the phone number (who are you calling?). Or maybe you’d discover that if the location isn’t found, that that means something. Maybe you need to alert the user (unless @location ...).

It could be that I’m more hung up on this idea of meaning. The example for Ben’s maybe is:

if(customer && customer.order && customer.order.id==newest_customer_id)

I look at that and think: if there’s no customer, that means one thing; if there’s no order for the customer, that means something else; if the customer’s order’s id isn’t newest_customer_id, that means something else entirely; and if all these things are different, maybe they should be handled separately.

If you think about it that way, there’s no need to fiddle with nil or add methods to Object.

(I completely understand that this might just be an aesthetic difference, or that sometimes this apparent loss of flexibility is desirable, or that I might be wrong. Please narrow it down for me in the comments. Thanks.)

14 Comments »

  1. Felipe Giotto said,

    March 4, 2008 @ 11:50 am

    Well, maybe I will need more attributes many times, but there’s times when you just want to show something if it exists, like . I think “try()” and other methods like this are just examples of ruby’s philosophy, “there’s more way to do it”.
    Best regards,
    Felipe Giotto ;-)

  2. Thom Parkin said,

    March 4, 2008 @ 12:27 pm

    Well said. When does it make more sense to be terse versus clear?

    I oft struggle with the choice of “SELECT”ing a single attribute or, more simply, grabbing the entire record and managing the object in memory.

    More to ponder…

  3. Reg Braithwaite said,

    March 4, 2008 @ 1:30 pm

    Well, we can have a VERY long exchange when discussing the phrase “valuing terseness over readability.” It may surprise you to hear this, but many of our Java-centric colleagues think that everything in Ruby values terseness at the expense of readability, and our response to them is often that when we eliminate the unnecessary, the more succinct code is the more readable code.

    So, it may come down to whether adding an extra variable strikes you as unnecessary. I use #andand when I do not need the variable elsewhere. When I do, I use a variable like this:

    person = Person.find(:first, …)
    foo = person && person.name

    And that is where the name of the #andand method comes from :-)

    But sometimes I don’t need person elsewhere. I could use it and think I may need it later. I could also legitimately say “YAGNI” and write:

    foo = Person.find(:first, …).andand.name

    As you point out, if you need person elsewhere, or you need to do something else if there is no such person, #andand is not an appropriate method to use.

    Which leads me to why I like having more than one way to do it:

    http://weblog.raganwald.com/2007/11/programming-conventions-as-signals.html

    If you are comfortable with having BOTH

    person = Person.find(:first, …)
    foo = person && person.name

    AND

    foo = Person.find(:first, …).andand.name

    In your code, then when reading it you know immediately that in the first case that we will see person again, there is other code that depends on it. The second case makes it clear that we don’t need person again.

    If I always write:

    person = Person.find(:first, …)
    foo = person && person.name

    Then I will always have to check to see whether I actually need “person” elsewhere.

    If you are hard core about not opening up the Object class and you (like me) dislike propagating locals you don’t need, here is an idiom you to consider:

    foo = Person.find(:first, …).instance_eval { self.name unless self.nil? }

    It also works for try’s semantics:

    foo = Person.find(:first, …).instance_eval { self.name if self.respond_to?(:name) }

    Anyhoo, sorry to ramble, but I do like the questions you are raising. It is refreshing to know that people are trying to work out great ways to write Ruby programs by asking each other questions.

  4. Fred said,

    March 4, 2008 @ 2:26 pm

    I agree with Chris.
    andand, try, do_or_do_not, NilClass#method_missing, SafeNil,
    somehow they are candy on the surface, but deeply in the sea it wont help.

    as Chris explain it just hides the real deal here.
    If a simple “@person.name if @person?” won’t do the deal, you are supposed to write a better code to handle what you need.

    and what’s wrong with “@person.name if @person?”

    isn’t this so ugly? “@person ? @person.name : nil”

    :)

  5. Reg Braithwaite said,

    March 4, 2008 @ 2:43 pm

    “what’s wrong with “@person.name if @person?”

    isn’t this so ugly? “@person ? @person.name : nil””

    Absolutely nothing, provided you first write:

    @person = …

    If you don’t have an @person member variable (or person local), then you must choose between creating one just for this one use or finding another way:

    Person.find(:first, … ) && Person.find(:first, … ).name

    This cannot be right. After that, it’s a question of what you find readable. If I don’t need @person elsewhere, adding an extra assignment feels a little like the boilerplate “design patterns” I loathe in Java. But that’s just me :-)

  6. Rails Podcast Brasil - Episdio 8 said,

    March 6, 2008 @ 2:57 pm

    [...] You might be able to stop trying to call methods on nil [...]

  7. she said,

    March 7, 2008 @ 2:54 pm

    “This cannot be right. After that, it’s a question of what you find readable.”

    Personally I find using @var is more readable than the RAILSish solution to throw in Foobar.new(:blabla) all over the place.

    The problem is, RoR is built with this from the ground up:
    all_people = Person.find(:all)

    This is almost native english language. The thing however you miss here is that in ESSENCE, it is still SHORT and TERSE, and this is GOOD.

    The less code you write to achieve a goal, all the better.
    It allows you to use any abstraction you want.

  8. Reg Braithwaite said,

    March 7, 2008 @ 3:26 pm

    She (who must be obeyed?):

    Well, languages like Haskell enforce referential transparency, so 1. if you write Person.find(…) twice in the same expression without using Monads, they will always return the same thing, and 2. teh compiler knows this and will optimize things so that you don’t actually call the function twice.

    That being said, it is not particularly DRY. In Scheme, I would use a let to introduce a variable limited in scope to the expression.

  9. MayoButter said,

    March 9, 2008 @ 9:49 am

    I find myself doing this a lot:

    object.child_object.other_object.attribute rescue nil

    just returns nil if anything in the chain is nil.

  10. Reg Braithwaite said,

    March 9, 2008 @ 10:39 am

    “object.child_object.other_object.attribute rescue nil”

    Ummm… you know this does a LOT MORE than just handle the case where one of the object is nil, correct? This rescues any exception thrown for any reason and silently swallows it.

    For example, what if other_object returns an object that is not null but does not have #attribute? NoMethodError, but it has nothing to do with nil. Or what if one of those methods actually performs a query, but your database is not available? Another exception, silently swallowed again.

    There’s nothing wrong with writing that when you mean “I want to rescue any exception thrown for any reason and return nil without logging it or letting me know what the problem is, even if it’s my own programming error.”

    But if what you mean is to handle cases where one of the receivers in the chain is nil, you might want to consider something a little more specific.

    JM2C, I’m obviously biased :-)

  11. MayoButter said,

    March 9, 2008 @ 3:10 pm

    Good point. Now that I think of it… I really only use that in views. Especially when calling strftime on dates that might be nil. Like so:

  12. MayoButter said,

    March 9, 2008 @ 3:10 pm

    @butter.expires.strftime = ‘%m/%d/%Y’

  13. MayoButter said,

    March 9, 2008 @ 3:12 pm

    Whoops, mistyped. This is what I meant:

    @butter.expires.strftime ‘%m/%d/%Y’ rescue nil

  14. Rails Podcast Brasil - Episódio 8 said,

    April 1, 2008 @ 8:08 am

    [...] You might be able to stop trying to call methods on nil [...]

RSS feed for comments on this post · TrackBack URI

Leave a Comment