Object#tapはそのブロックの評価結果を捨てるという風変わりなメソッドです。これは主としてメソッドチェーンにおける途中経過を覗き見るために使われます。

"charlie".upcase.tap{ |s| p s }   # => "CHARLIE"
         .reverse.tap{ |s| p s }  # => "EILRAHC"
         .gsub(/[aeiou]/i,'*')    # => "**LR*HC"

tapの副作用を使う

もっとも、その評価結果を捨てるというユニークな特徴をうまく使えば、もっと面白いことができます。

例えば、ある変数の値を取得した上でその変数の値をリセットしたい場合を考えます。通常は次のように実装するのでしょう。

@name = 'Charlie'

def reset_name
   name = @name
   @name = nil
   name
end

reset_name # => "Charlie"
@name # => nil

ローカル変数を用意するのが少しまどろっこしく感じられます。

しかしtapを使えばより簡潔に書くことができます。

@name = 'Charlie'
def reset_name
   @name.tap { @name = nil }
end

reset_name # => "Charlie"
@name # => nil

Rubyのtapはメソッドチェーンだけのものじゃない!」より

tapの返り値を使う

tapbreakと組み合わせると、更に面白いことができます。

配列要素の平均値を求める例を示します。通常以下のようにします。

scores = [56, 87, 49, 75, 90, 63, 65]
scores.inject(:+) / scores.size # => 69

これはtapbreakを使って一行にすることができます。

avg = [56, 87, 49, 75, 90, 63, 65].tap { |sco| break sco.inject(:+) / sco.size } # => 69

つまりtapのブロックでbreakするとその結果を返り値にできるのです。

Object#doメソッドというのはありですか?」より

tapを存在演算子として使う

さらにtapはCoffeeScriptの存在演算子のようにも使うことができます。

例を示します。ここでは各学生の数学の結果を出力したいとします。

Student = Struct.new(:name, :age, :scores)
charlie = Student['Charlie', 14]
liz = Student['Liz', 14]

charlie.scores = { math:35, english:78, music:60 }

students = [charlie, liz]

students.each do |st|
  math = st.scores[:math]
  puts "#{st.name}'s math is #{math}"
end

# >> Charlie's math is 35
# >> NoMethodError: undefined method `[]' for nil:NilClass

この例でlizはまだ試験を受けていないのでscoresの値はnilであり、その結果scores[:math]の呼び出しはエラーになってしまいます。

次にtapを使ったエラー回避策を示します。

students.each do |st|
  math = st.scores.tap{ |s| break s[:math] if s }
  puts "#{st.name}'s math is #{math}"
end

# >> Charlie's math is 35
# >> Liz's math is

scoresがあるときはbreakでブロックの結果が返され、scoresがnilのときはtapの挙動通りブロックの結果は無視されてsocoresつまりnilが返ります。

Rubyに存在演算子は存在するの?」より

tapを制御構造として使う

上記の使い方を更に一歩進めると、tapは制御構造としても機能するようになります。

まずは偶数判定をするeven?メソッドをtapで定義してみます。

def even?(n)
  n.tap { |i| break false if (i%2)!=0 }
   .tap { |i| break true if i }
end

(1..10).map { |i| even? i } # => [false, true, false, true, false, true, false, true, false, true]

tapを2つ繋いで処理を振り分けています。一見、2つ目のtapにおけるif修飾子は要らないように思われますが、これは必須です。なぜならtapにおけるbreakはフォールスルーの如くに機能するからです。つまり奇数が渡された場合も2つ目のブロックは評価されます。

次に、FizzBuzzをやってみます。

def fizzbuzz(n)
  n.tap { |s| break :Fizz if (n%3)==0 }
   .tap { |s| break :Buzz if (n%5)==0 }
   .tap { |s| break :FizzBuzz if (n%15)==0 }
end

(1..100).each { |i| printf "%s ", fizzbuzz(i) }

# >> 1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz Fizz 22 23 Fizz Buzz 26 Fizz 28 29 FizzBuzz 31 32 Fizz 34 Buzz Fizz 37 38 Fizz Buzz 41 Fizz 43 44 FizzBuzz 46 47 Fizz 49 Buzz Fizz 52 53 Fizz Buzz 56 Fizz 58 59 FizzBuzz 61 62 Fizz 64 Buzz Fizz 67 68 Fizz Buzz 71 Fizz 73 74 FizzBuzz 76 77 Fizz 79 Buzz Fizz 82 83 Fizz Buzz 86 Fizz 88 89 FizzBuzz 91 92 Fizz 94 Buzz Fizz 97 98 Fizz Buzz

ここでは各ブロックにおいてその引数を使わずに、メソッド引数を直接使っています。先のフォールスルーの理屈によりFizzBuzzのブロックが最後に来ているのが特徴的です。

ブロック引数を使った例も示しておきます。この場合、後続のブロックにはSymbolも渡るのでその判定が必要になります。

def fizzbuzz(n)
  n.tap { |i| break :FizzBuzz if (i%15)==0 }
   .tap { |i| break :Fizz if i.is_a?(Integer) && (i%3)==0 }
   .tap { |i| break :Buzz if i.is_a?(Integer) && (i%5)==0 }
end

(1..100).each { |i| printf "%s ", fizzbuzz(i) }

# >> 1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz Fizz 22 23 Fizz Buzz 26 Fizz 28 29 FizzBuzz 31 32 Fizz 34 Buzz Fizz 37 38 Fizz Buzz 41 Fizz 43 44 FizzBuzz 46 47 Fizz 49 Buzz Fizz 52 53 Fizz Buzz 56 Fizz 58 59 FizzBuzz 61 62 Fizz 64 Buzz Fizz 67 68 Fizz Buzz 71 Fizz 73 74 FizzBuzz 76 77 Fizz 79 Buzz Fizz 82 83 Fizz Buzz 86 Fizz 88 89 FizzBuzz 91 92 Fizz 94 Buzz Fizz 97 98 Fizz Buzz

ブロック内でreturnを使うことで、フォールスルーを避ける事もできます。

まあ実用性はなさそうですが、面白いので紹介してみました。

tapの新しい使い道、あなたも探してみませんか?


タップダンス入門―誰でも気軽に踏めるステップ! by 佐々木 隆子



blog comments powered by Disqus
ruby_pack8

100円〜で好評発売中!
M'ELBORNE BOOKS