22
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Rubyの#yield_selfの良さを#tapと比較して説明する

Last updated at Posted at 2018-08-11

まえがき

Object#yield_self、使ってますか?
Object#yield_selfはRuby2.5から追加された強力なメソッドです。
今回はその使い方を、似たメソッドである Object#tapと比較しながら説明します。

中身の実装

Object#yield_self

class Object
  def yield_self
    yield self
  end
end

Object#tap

ネイティブで実装されていてRubyコードじゃなくて辛いので、
RubyのRuby実装であるRubiniusからソースを持ってきます。

module Kernel
  def tap
    yield self
    self
  end
end

#yield_selfと#tapの相違点

yield自体の挙動がちょっとむずかしいので難解かもしませんが、
どちらもレシーバを引数のブロックの引数にとって実行する点は同じです。
どちらもレシーバをブロックのパラメーターにとって実行する点は同じです。
※用語が不正確だというご指摘いただきましたので修正

違うのは返り値で
yield_selfだと実行結果(つまり受け取ったブロックの返り値)が返って、
tapはブロックを実行した後のレシーバが返ります。

つまりどういうことだってばよ

ブロックの中で String#downcase!を使う例で比較します。

String#downcase!はレシーバの文字列を小文字にして返す破壊的メソッドですが、
変更があった場合には小文字化した文字列が返り値になり、変更なしの場合はnilが返ります。

'Hoge'.downcase! # => "hoge"
'hoge'.downcase! # => nil

ただし破壊的とはいえ、そこまで暴君ではないので元の文字列までnilにするわけではありません

str = 'hoge'
str.downcase! # => nil
str           # => 'hoge'

さてこれをyield_selfとtapで使ってみましょう。

'hoge'.yield_self { |str| str.downcase! } # => nil 

'hoge'.tap { |str| str.downcase! }        # => 'hoge'

yield_selfはブロックの中の実行結果が返るので、 'hoge'.downcase!の返り値である nilが返り、
tapは実行後のレシーバが返るのでそのまま 'hoge'が返りました。

で、yield_selfの何がいいのか

たとえばこんなコードをリファクタしてみる。

before = 'hello world'
str = 'L'.downcase
p str                         # => 'l'
str = str * 2
p str                         # => 'll'
after = before.gsub(str, 'X')
p after                       # => "heXo world"

( self =とかはナシで)tapを使うとせいぜいこんなものでしょう↓

before = 'hello world'
str = 'L'.downcase.tap { |tmp| p tmp }             # => 'l'
str = (str * 2).tap { |tmp| p tmp }                # => 'll'
after = before.gsub(str, 'X').tap { |tmp| p tmp }  # => "heXo world"

yield_selfを使うとこんな感じ


before = 'hello world'
after = 'L'.downcase
           .yield_self { |str| p str; str * 2 }               # => 'l'
           .yield_self { |str| p str; before.gsub(str, 'X') } # => 'll'
           .tap { |str| p str }                                # => "heXo world"

いちいち変数にいれなくても次のメソッドで使えるので幾分かスマートになりました。

元のコードと比べて、変数(引数)のスコープが限定的になるのでコードを読むときに「この変数なんだっけ?」という状態になりづらくてとても便利です。

おわり

tapでメソッドチェーンを作るのもいいですが、時にはyield_selfのことも思い出してあげてください。
ちなみにtapbreakで同じことできます。
ruby2.6からは thenでかけます

22
7
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?