Комбинаторный метод, как тап, но может вернуть другое значение? - PullRequest
29 голосов
/ 24 октября 2011

Я прохожу фазу попыток избежать временных переменных и чрезмерного использования условных выражений, где я могу использовать более гибкий стиль кодирования. Мне очень понравилось использовать #tap в тех местах, где я хочу получить значение, которое мне нужно вернуть, но сделайте что-нибудь с ним, прежде чем я его верну.

def fluid_method
  something_complicated(a, b, c).tap do |obj|
    obj.update(:x => y)
  end
end

Vs. процедурный:

def non_fluid_method
  obj = something_complicated(a, b, c)
  obj.update(:x => y)
  obj # <= I don't like this, if it's avoidable
end

Очевидно, что приведенные выше примеры просты, но, тем не менее, это довольно распространенный стиль кодирования в сообществе ruby. Иногда я буду использовать #inject для прохождения объекта через серию фильтров:

things.inject(whatever) do |obj, thing|
  thing.filter(obj)
end

Vs. процедурный:

obj = whatever
things.each do |thing|
  obj = thing.filter(obj)
end
obj

Теперь я сталкиваюсь с повторным использованием условия, подобного следующему, и ищу более гибкий подход к нему:

def not_nice_method
  obj = something_complex(a, b, c)
  if a_predicate_check?
    obj.one_more_method_call
  else
    obj
  end
end

(немного) более чистое решение состоит в том, чтобы избежать временной переменной за счет дублирования:

def not_nice_method
  if a_predicate_check?
    something_complex(a, b, c).one_more_method_call
  else
    something_complex(a, b, c)
  end
end

Я не могу не чувствовать желание использовать что-то почти , как, например, #tap здесь.

Каким другим шаблонам я мог бы следовать здесь. Я понимаю, что это просто бессмысленный сахар для некоторых людей, и я должен просто перейти к более интересным проблемам, но я пытаюсь научиться писать в более функциональном стиле, поэтому мне просто любопытно, что определили долгосрочные рубинисты быть хорошим способом решения подобных ситуаций. Эти примеры очень упрощены.

Ответы [ 5 ]

15 голосов
/ 24 октября 2011

Определить Object#as:

class Object
  def as
    yield self
  end
end

А теперь вы можете написать:

def not_sure_this_is_nice_enough_method1
  something_complex(a, b, c).as do |obj| 
    a_predicate_check? ? obj.one_more_method_call : obj
  end
end
7 голосов
/ 01 ноября 2017
def best_nice_method
  something_complex(a, b, c).tap |obj|
    break obj.one_more_method_call if a_predicate_check?
  end
end

Магия break в tap возвращает другое значение.

new

ruby ​​2.5 имеет yield_self, что именно вам нужно.https://stackoverflow.com/a/47890832/683157

3 голосов
/ 17 января 2013

Я нашел метод в геме Facets, который может быть тем, что вы искали: Kernel # ergo

Итак, ваш оригинальный метод:

def not_nice_method
  obj = something_complex(a, b, c)
  if a_predicate_check?
    obj.one_more_method_call
  else
    obj
  end
end

может выглядеть примерно так:

require 'facets/kernel/ergo'

def nice_method
  something_complex(a, b, c).ergo do |_| 
    a_predicate_check? ? _.one_more_method_call : _
  end
end
1 голос
/ 05 ноября 2015

Мне нужно было сделать что-то подобное, и мне нравится ответ Токланда, но я не хотел загрязнять Object из-за небольшого сценария, который я писал.Вместо этого я использовал tap в массиве:

[something_complicated].tap { |s| s[0] = new_cool_thing)}.first
0 голосов
/ 07 сентября 2018

instance_eval может быть использовано не по назначению

"this".instance_eval { |test| test + " works" }

начиная с 2.5 можно использовать yield_self

"easy".yield_self{ |a| a + " peasy" }

Подробнее:

https://ruby -doc.org / ядро-1.9.3 / BasicObject.html # метод-я-instance_eval

https://ruby -doc.org / ядро-2.5.0 / object.html # метод-я-yield_self * * тысячу двадцать пять

...