Ruby

差をつけるRuby


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]