16

I've got a smelly method like:

def search_record(*args)    
  record = expensive_operation_1(foo)
  return record unless record.nil?

  record = expensive_operation_2(foo, bar)
  return record unless record.nil?

  record = expensive_operation_3(baz)
  return record unless record.nil?

  record = expensive_operation_4(foo, baz)
  return record unless record.nil?
end

Is there a good ruby idiom for "return result of call unless nil"?

Or should I just write a return_unless_nil(&blk) method?

(Note that args are different for each call, so I can't simply just iterate over them)

Sisyphus
  • 4,181
  • 1
  • 22
  • 15
pithyless
  • 1,659
  • 2
  • 16
  • 31
  • Can record be `false`, or is definitely either a real record or nil? – Andrew Grimm Jun 15 '11 at 06:07
  • "Or should I just write a `return_unless_nil(&blk)` method?" Which would return from the method that invoked it? I don't think that's possible. – Joshua Cheek Jun 15 '11 at 06:10
  • I have about 10 minutes of learning ruby under my belt, but am curious how would you write a method to possibly return a value for the caller? As for a potential answer, I feel uncomfortable putting this as an actual answer given my very limited ruby exposure... can you have something like this in ruby? `return (record=expensive_op(foo)) unless record.nil?` Not sure if that's any less smelly for you either.. – at. Jun 15 '11 at 06:13
  • @at: Unfortunately that would not work, because in that type of expression Ruby will evaluate the right-hand side (the part after `unless`) before the left-hand side, so the assignment won't happen before you call `.nil?`. You could, however, do `(record = expensive_op(foo)) && (return record)`, but that's more parentheses than I prefer in my Ruby. – Jordan Running Jun 15 '11 at 06:26
  • @Jordan: you're right and I assumed that's how ruby evaluated actually, I meant to write `return record unless (record=expens_op(foo)).nil?`. Still, maybe not the most elegant code.. – at. Jun 15 '11 at 06:33
  • @Joshua-Cheek Yes, as far as I understand calling return from a block, returns from the function that yielded to the block. – pithyless Jun 15 '11 at 18:03
  • Although I prefer the Jordan answer, I'd use `return record if record` instead of `return record unless record.nil?`. – Sony Santos Oct 13 '11 at 18:33

2 Answers2

21

Do you care about the difference between nil and false here? If you only care whether the return value of each method is "falsy," then this is a pretty Rubyish way of doing it:

def search_record(*args)    
  expensive_operation_1(foo)      ||
  expensive_operation_2(foo, bar) ||
  expensive_operation_3(baz)      ||
  expensive_operation_4(foo, baz)
end

If you're unfamiliar with this idiom, it can be explained thusly: Ruby, like most languages, "short circuits" OR comparisons, meaning that if the first operand evaluates to "truey" it won't bother to evaluate the second operand (i.e. if expensive_operation_1 returns something other than nil or false, it won't ever call expensive_operation_2), because it already knows that the result of the boolean operation is true.

Another useful thing that Ruby does is, instead of returning true or false from boolean operations it just returns the last operand it evaluates. So in this case if expensive_operation_1 returns nil, it will then call expensive_operation_2, and if that returns a value (that isn't falsy), the whole expression will just evaluate to that value.

Finally, we can chain these booleans so, in effect, it will return the result of the first operand that isn't falsy and never evaluate the subsequent operands. If all of the operands evaluate to falsy, it will return the final operand (which we know is falsy and, in your case, probably nil).

Jordan Running
  • 102,619
  • 17
  • 182
  • 182
  • To expand on Jordan's rather terse answer: The boolean operators short-circuit (evaluate only the left side if the right side isn't needed) and return the value that satisfied the operator. – Chuck Jun 15 '11 at 06:13
  • Thanks Chuck. I was busy expanding on my answer when you commented. :) – Jordan Running Jun 15 '11 at 06:14
  • The caveat "Do you care about the difference between nil and false" is indeed important. If any of these functions return a boolean, this code won't do. Unfortunately the OP has gone AWOL. – tokland Jun 15 '11 at 11:04
  • Sorry about going AWOL. You're right @Jordan; in this specific case I don't necessarily care about false vs nil, so I can just use standard short circuiting. I'm not quite sure why I fixated on making sure it's nil and missing the obvious path... Thanks! – pithyless Jun 15 '11 at 17:59
2

Complementing Jordan's answer: let's imagine these functions could return a boolean (unlikely for a search function, but anyway) and || does not work. We could then use the abstraction discussed here:

expensive_operation_1(foo).or_if(:nil?) do
  expensive_operation_2(foo).or_if(:nil?) do
    expensive_operation_3(foo).or_if(:nil?) do
      expensive_operation_4(foo)
    end
  end
end
Community
  • 1
  • 1
tokland
  • 66,169
  • 13
  • 144
  • 170