Ruby "return if nil" idiom

У меня есть вонючий метод вроде:

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

Есть ли хорошая рубиновая идиома для "результата возврата вызова до нуля"?

Или просто написать метод return_unless_nil(&blk)?

(Обратите внимание, что аргументы разные для каждого вызова, поэтому я не могу просто просто перебирать их)

Ответы

Ответ 1

Вас интересует разница между nil и false здесь? Если вам все равно, является ли возвращаемое значение каждого метода "ложным", тогда это довольно рубиновый способ сделать это:

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

Если вы не знакомы с этой идиомой, это можно объяснить так: Ruby, как и большинство языков, "short circuit" ИЛИ сравнения, это означает, что если первый операнд оценит "truey", он не потрудится оценить второй операнд (т.е. если expensive_operation_1 возвращает что-то, отличное от nil или false, оно никогда не вызовет expensive_operation_2), потому что он уже знает, что результат логической операции верен.

Еще одна полезная вещь, которую Ruby делает, вместо возврата true или false из булевых операций, просто возвращает последний операнд, который он оценивает. Поэтому в этом случае, если expensive_operation_1 возвращает nil, он вызывается expensive_operation_2, и если это возвращает значение (это не ложно), все выражение будет просто оценивать это значение.

Наконец, мы можем связать эти булевы, чтобы, по сути, вернуть результат первого операнда, который не является ложным, и никогда не оценивать последующие операнды. Если все операнды оцениваются как ложные, он вернет конечный операнд (который, как мы знаем, является фальшивым, а в вашем случае, вероятно, nil).

Ответ 2

Дополняя ответ Джордану: пусть представьте, что эти функции могут возвращать логическое (маловероятное для функции поиска, но в любом случае) и || не работает. Затем мы могли бы использовать обсуждаемую абстракцию здесь:

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