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
になります。nil
にhoge
メソッドは生えていないので、2行目はNoMethodErrorを吐きます。
一方で3行目は&.
を使ってメソッドを呼び出しているので、エラーを吐かずに単にnil
を返します。
||=
||=
を使うと「左辺が偽(nil
かfalse
)の場合だけ代入する」ということができます。
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]