Как стать автором
Обновить

Комментарии 34

Тут разумнее привести такой код:
User.new.tap{|u| u.name = "Alex" }.save!

Вместо
u = User.new
u.name = "Alex"
u.save!
Количество строк одна против 3. А также лишняя переменная.
Но, как по мне, то читать легче без tap. Тут надо смотреть, чего мы хотим: мало строк или хорошая читаемость.
Если для ActiveRecod то можно и без tap

User.new do |user|
  user.name = 'Alex'
end.save!



тоже самое на create, update

Ну, это для рельс.
Видел такой в куче библиотек относящих к рельсам чуть менее, чем никак.

Паттерн обычно выглядит следующим образом:
class Something
  def initialize(...)
    # initialization
    yield self if block_given?
  end
end
точнее для ActiveRecord
и для всех объектов принимающих блок
то, что вы привели в качестве примера, не пройдёт ни один стайлгайд.
.tap используется в основном для возвращения значения внутри метода:

def make_admin
  User.new.tap do |user|
    user.role = 'admin'
  end
end


вместо

def make_admin
  user = User.new
  user.role = 'admin'
  user
end


оно не сокращает количество строк, а улучшает читаемость
Так а где улучшение читаемости-то? То же ж самое абсолютно.
Да ни разу он не для улучшения читаемости просто. Бывает, что нужно что-то выполнить на промежуточном результате:

def my_f
  User.new.tap { |u| puts “New user: #{u}” }
end

Напечатали лог и вернули вновь созданного юзера из функции. Вместо:

def my_f
  user = User.new
  puts “New user: #{user}”
  user
end
Вот тут, согласен, tap вполне приятно смотрится.
удивительное совпадение в том, что tap как раз и был введен прежде всего для таких манипуляций: то есть, для чтения, а не для мутации. Мутация — это практически побочный эффект
нет лишней переменной.
зато есть лишний блок, лол
Возможность проставлять лямбду при помощи -> появилась сравнительно недавно
В 1.9 она уже есть. Недавно?! xD
В следующий раз лучше выбирайте пост для перевода. Если человек ведёт блог, это ещё не значит, что он профессионал языка.

Кроме выше означенных проблем, вот ещё. Назовите разницу между
a || a = b
и
a = a || b
С логической точки зрения её нет.
Во втором случае нет смысла переназначать «a» если мы уже имеем ее в распоряжении.
в ruby чистые присваивания практически ничего не стоят.
На самом деле a ||= b работает скорее как a = b unless a
А хабраюзер total не обидится на то, что его будут в разных методах использовать? Да еще и считать, пусть только и один раз. «И тебя посчитали!» (с) :)
а вот **c принимет только параметры в формате ключ/значение, после чего отдаст нам хэш.

О божечки, они изобрели **kwargs :D
И правильно сделали, этого не хватало, были всякие костыли типа extract_arguments в начале многих методов. Кстати говоря, именно поэтому 5-е рельсы активно перепиливают на использование **kwargs везде (следовательно минимальная версия Ruby там будет 2.2).
Оно же с 2.0 появилось? Например, здесь пишут, что это так: magazine.rubyist.net/?Ruby200SpecialEn-kwarg

Это не считая того, что синтаксический сахар для последнего аргумента-хэша и в 1.9 был. Менее удобный, правда, так как значения аргументов по умолчанию выставлялись в теле функции и проверка наличия избыточных аргументов делалась вручную и довольно редко.
Как следует заработало только с 2.2 (а то и с 2.2.1).
Важно понять, что этот оператор работает не так: a || a = b и не так: a = a || b:

irb(main):001:0> b = :caveat
=> :caveat
irb(main):002:0> a1 ||= b
=> :caveat
irb(main):003:0> a2 || a2 = b
NameError: undefined local variable or method `a2' for main:Object
Так все таки, как он работает?
Работает как и говорили выше

a = b unless a

Поэтому много проблем влечёт ||=, так как забывают люди постоянно про то, что с Bool данную конструкцию использовать нельзя.

irb(main):001:0> a = 42
=> 42
irb(main):002:0> a ||= 77
=> 42
irb(main):003:0> a = false
=> false
irb(main):004:0> a ||= true
=> true
irb(main):005:0> a
=> true

Вроде как и понятно почему, но когда идёт несколько идентичных методов или присваиваний и среди них забываешь про Bool становится очень обидно за убитое на дебаг время.
За bool спасибо, не задумывался об этом

Но вот
a||=b

не совсем
a = b unless a

потому что
2.2.0 :001 > a = :symb
 => :symb 
2.2.0 :002 > b ||= a
 => :symb 
2.2.0 :003 > b = a unless b
 => nil 
2.2.0 :004 > b
 => :symb 

а скорее
if defined?(b) && b then b else b = a end
Собсно я еще над этим подумал, и получается, что как раз
a = a || b

так оно и работает :) То есть так, как в посте помечено коментарием «Неверно». Поправьте, в чем неправ.
def total
  @total ||= (1..100000000).to_a.inject(:+)
end

Только когда пишите такое, помните, что это не thread-safe.
как бы вы переписали этот метод, чтобы он стал 'thread-safe'?
Синхронизировать (см.), или, если возможно, перенести в конструктор без ||
Стоит отметить, что ||= нужно применять с осторожностью для вычислений, которые могут вернуть nil, в таком случае результат не будет закеширован. Для решения этой проблемы есть гемы типа memoist.
код почему-то не подсвечивается

def products

return @cached[:result] if @cached.is_a?(Hash)

#…
# нужные операции
#…

@cached = {result: result}

end
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории