Help us understand the problem. What is going on with this article?

差をつけるRuby

More than 1 year has passed since last update.

Rubyを楽しむために

Rubyは、奥が深い言語です。覚えゲーともいいます。
Rubyは、いろんな機能を覚えるとキレイに書けて気持ちがいいです。
キレイに書けるとRubyが楽しくなります。

Rubyを触り始めて一通りの処理が書けるようになった人が、次に書けるようになるとプロっぽいRubyの機能や文法を紹介します。

注意:本記事ではRubyのバージョンは2.6以上を仮定します

mapの小技 ①

[1, -1, 2, -3].map { |e| e.abs } # => [1,1,2,3]
[1, -1, 2, -3].map(&:abs) # => [1,1,2,3]

mapメソッドにはブロックを渡して処理をするのが普通ですが、map(&:method_name)と書くこともできます。
ブロックを渡した場合と意味は同じですが、より短く簡潔に書けます。

mapの小技 ②

h = { a: 1, b: 2, c: 3 }
[:a, :c, :b, :x].map { |k| h[k] } # => [1, 3, 2, nil]
[:a, :c, :b, :x].map(&h) # => [1, 3, 2, nil]

Arrayの中身をHashを使って変換したいことがあると思います。そういうときはmap(&:hash)と書けます(&によってHashをブロックに変換しています)。
ブロックを使った場合と意味は同じですが、より短く簡潔に書けます。

mapの小技 ③

(Thanks @pink_bangbi san)

[:a, :c, :b, :x].map { |e| p e }
[:a, :c, :b, :x].map(&method(:p))

上の2つの行は同じ意味になります。

2行目では、 method(:p) によって p メソッドをProcオブジェクトに変換し、&
によってProcオブジェクトを「展開」して、mapにブロックを渡しています。

Safe navigation operator

arr = []
arr.first.hoge # => NoMethodError
arr.first&.hoge # => nil

上の例では空配列のfirstを呼び出していて、この値はnilになります。nilhogeメソッドは生えていないので、2行目はNoMethodErrorを吐きます。
一方で3行目は&.を使ってメソッドを呼び出しているので、エラーを吐かずに単にnilを返します。

||=

||=を使うと「左辺が偽(nilfalse)の場合だけ代入する」ということができます。

a = 100
a ||= '代入されない!!!'
a # => 100

b = nil
b ||= '代入される!!!'
b # => '代入される!!!'

これを使うと、「最初に呼び出されたときに初期化する」みたいなことができます。

class Hoge
  def add_history(value)
    @c ||= [] # add_historyが最初に呼ばれたときだけ実行される
    @c.push(value)
  end
end

Hashのデフォルト値

Hash.newにブロックを渡して初期化すると、登録されていないキーが参照された場合にそのブロックの返り値が返却されます。
つまり、ブロックの返り値がHashのデフォルト値になります。

h = Hash.new { 100 }
h[:key] = h[:key] + 1
h[:key] # => 101

Hash.newに渡すブロックを工夫すると、さらにキレイに書けます(ブロックの
第一引数がハッシュ自身、第二引数がハッシュのキーになることと、代入文の返り値は右辺の値であることを利用している)。

h = Hash.new { |hash, key| hash[key] = 100 }
h[:key] += 1
h[:key] # => 101

これを使うと、計算結果のキャッシュができます。

memo = Hash.new { |hash, arg| hash[arg] = some_func(arg) }
memo['somearg'] # => some_func('somearg')が走る
memo['somearg'] # => キャッシュが使われる

メモ化もキレイに書けます。
フィボナッチ数列をメモ化を使って実装する例:

fib = Hash.new do |hash, n|
  if n == 0
    hash[n] = 0
  elsif n == 1
    hash[n] = 1
  else
    hash[n] = hash[n-1] + hash[n-2]
  end
end

fib[100] # => 354224848179261915075

tap

tapはすべてのオブジェクトに生えているメソッドです。ブロックを渡すことができ、その返り値は呼び出し元のオブジェクトになります。
メソッドチェインの途中の状態を見たいときとかに使えます。

説明するのが難しいので例を見てくれ:

[-10, 10, -20]
  .map(&:abs).map(&:to_s)
  .tap { |inner_arr| p inner_arr } # => ["10", "10", "20"]
  .map(&:chars).map(&:first)

method(:p)を使って以下のようにも書けます(Thanks @pink_bangbi san):

[-10, 10, -20]
  .map(&:abs).map(&:to_s)
  .tap(&method(:p)) # => ["10", "10", "20"]
  .map(&:chars).map(&:first)

Struct

いわゆる構造体です。クラスを作るほどではないが、Hashよりは "ちゃんとしたい" ときに使います。

Point = Struct.new(:x, :y)
point = Point.new(100, 200)
point.x # => 100
point.y # => 200
point.x = 500
point.x # => 500

構造体にはメソッドが生やせます (Thanks @horyu san)。

Point = Struct.new(:x, :y) do
  def fuga
    "somestring"
  end
end

Point.new(100,200).fuga # => "somestring"

構造体はクラスなので、継承できます。

class Point < Struct.new(:x, :y)
  def add_x(dx)
    self.x += dx
  end
end

point = Point.new(100,200)
point.add_x(5)
point.x # => 105

モンキーパッチ

Rubyでは、既存のクラスに後からメソッドを生やせます。もちろん組み込みクラスにも生やせます。これをモンキーパッチといいます。

例えばStringクラスに新しくhogeメソッドを生やしてみます:

some_string = 'aaaaa'

class String
  def hoge
    'This is hoge!!!'
  end
end

some_string.hoge # => 'This is hoge!!!'

このようにモンキーパッチは、存在するすべてのインスタンスにメソッドを生やします。

モンキーパッチを乱用すると最悪なことになるので、慎重に使いましょう(cf. refine)。

Procの合成

Proc>>によって合成できます。

f1 = proc { |e| e * 10 }
f2 = proc { |e| e + 1 }
composed = f1 >> f2

composed.call(2) # => 21
[1,2,3].map(&composed) # => [11, 21, 31]
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away