「プロを目指す人のためのRuby入門」を再読した
Railsを使っているとしばしばRuby自体を学びなおした方が良いのではと思うことがあり、Ruby初心者の頃に読んだチェリー本を読み返してみた。
Rubyに初めて触れてから3年くらいが経つものの、業務で使わない構文や機能に関しては頭から飛んでいたことがよくわかった。長期記憶に焼き付けておく目的で、重要度が高く記憶の定着度が低いものを書き残しておく。
どんな本か
プログラミング言語『Ruby』の入門書。プログラミングそのものの入門者は対象としておらず、ある程度プログラミング経験がある人を対象読者としている。注意点として、RubyのWebアプリケーションフレームワーク『Ruby on Rails』は本書の範囲外。
読者の疑問を先読みした非常にわかりやすい説明がなされる一方で、入門書としては比較的深めの領域にも触れている。全体を通してサンプルコードがテスト駆動開発を模していたり、デバッグ技法について説明する章が設けられているなど、実務に有益な情報が多く盛り込まれているのも特徴的。
2017年12月8日発行。技術評論社。著者は株式会社ソニックガーデンのプログラマ 伊藤淳一氏。
覚えてなかったところ
splat展開
こういうやつ
(1..5).to_a #=> [1, 2, 3, 4, 5]
[*1..5] #=> [1, 2, 3, 4, 5]
Rangeオブジェクトの前に*
をつけることでRange範囲内の値を展開してくれる。
他にも配列展開して複数の引数として渡すときにも使える。
a = [] #=> []
a.push(1,2) #=> [1, 2]
a.push([3,4]) #=> [1, 2, [3, 4]]
a.push(*[5,6]) #=> [1, 2, [3, 4], 5, 6]
配列を入れ子にしたくない時はflatten
を使っていたけど、splat展開で済む場合も多そう。
でもこれ、前者はRangeに対してで、後者は配列の引数渡しで使っているので実際別の動きをしてそうだな、と思って検索したら同じことを考えていた方がまとめてくださっていた。
変数代入するときに、右辺に*
を付けると配列変換として機能するということらしい。Integerのリテラルにも適用されるということは、初心者が自己代入構文の順番間違えて誤爆するなんてこともありそうだなと思った。
a = 100 #=> 100
a *= 2 #=> 200
a =* 3 #=> [3]
可変長引数を受けるメソッドを定義する時にも*
を使うそう。たぶん上で使ったpush
メソッドの定義でも使われてるのだろう。Javaだと(String... args)
で書いてたのを思い出した。
throw, catchでの大域脱出
ネストされたループを一気に抜ける構文。break
はひとつ外にしか出られないので、多重ループでたくさんbreak
を書いて可読性が下がりそうな場合に使うと良さそう。catch
の分だけネストが1増えるので2重ループ程度で使うべきかは考えものかも。
count = 0
catch :done do
(1..10).each do |i|
('a'..'z').each do |j|
('A'..'Z').each do |k|
count += 1
throw :done if i == 5 && j == 'g' && k == 'G'
end
end
end
end
p count #=> 2867
ぼっち演算子、nilガード
Ruby2.3から導入のぼっち演算子。レシーバがnil
の場合にメソッドがnil
を返すようになり、NoMethodErrorが起きない。知らなかったわけではないけど、仕事で古めのRubyを扱うことが多くそもそも使えてないので備忘録として。
a = nil
a&.upcase #=> nil
b = 'ruby'
b&.upcase #=> RUBY
変数代入の時、代入先がnil
の場合に代入したい時に使うイディオム。論理和の演算子と自己代入を組み合わせたもので、左辺がfalsyの時に代入される。
a = nil
a ||= 10
a #=> 10
b = 20
b ||= 10
b #=> 20
rubyはnil
とfalse
だけがfalsyなのでいいけど、PerlやJavaScriptで使うとバグ製造機になりそう。
正規表現のキャプチャ名付け
正規表現でキャプチャした値に名前をつけておける機能。
text = '今日は2021年12月12日です'
m = /(?<year>\d+)年(?<month>\d+)月(?<day>\d+)日/.match(text)
p m[:year] #=> "2021"
p m[:month] #=> "12"
p m[:day] #=> "12"
p m #=> #<MatchData "2021年12月12日" year:"2021" month:"12" day:"12">
左辺に正規表現リテラルを、右辺にターゲット文字列を置いて=~
演算子で繋ぐとローカル変数が作られるらしい。すごい。
text = '今日は2021年12月12日です'
if /(?<year>\d+)年(?<month>\d+)月(?<day>\d+)日/ =~ text
puts "#{year}/#{month}/#{day}"
end
#=> 2021/12/12
正規表現のパターンが使いまわしづらくなったり、Web上のチェッカーに入れづらくなりそうだなーと思ったけど、Rubularでチェックすると変数名付けも含めてチェックしてくれるみたい。
Rubularのキャプチャ
メソッドの有無を調べる
respond_to?
を使うと、レシーバに対してメソッド呼び出しが可能かが確認できる。メタプログラミングには欠かせない。ただWebアプリケーションの開発で使うかというとそんなに出番は多くないのではと思う。
ちなみにRailsのActionController
によく似たメソッドがあるけど別人。こっちはObject
クラス(Kernelモジュール)が持っているメソッド。
num = 100
num.respond_to?(:to_s) #=> true
num.respond_to?(:push) #=> false
num.method(:respond_to?).owner #=> Kernel
モジュール全般
モジュールについてはこちらの記事で振り返った。
例外の継承関係
これが頭に入ってなかったのは結構まずいのでは、と自分で思ったやつ。
すべての例外系クラスの継承元がException
クラス。そのException
を継承したStandardError
が、通常のプログラムで発生する可能性の高い例外を表すスーパークラス。例えばNameError
やArgumentError
などが該当する。
StandardError
は、rescue
節に例外クラスを指定しなかった場合のデフォルトの捕捉対象。つまりは開発者が捕捉してなんとかできる可能性がある例外、と言える。JavaでいうところのExceptionクラス。
一方、NoMemoryError
やSystemExit
など、StandardError
を継承ルートに持たないクラスが存在する。これらはどうしようもない系の例外なので一般的にコード上で捕捉しない。JavaでいうところのErrorクラスに相当する。うっかりrescue
でException
を指定しないように気をつけたい。
ensureの代わりにブロック
File.openはブロックを渡して呼び出すと、ブロック内で例外が発生した際にもclose
処理を実行してくれる。Javaでいうところのtry-with-resource文。
try-with-resourceの場合はjava.lang.AutoCloseable
が実装されているかを確認すればよかったが、rubyの場合はメソッド仕様を知らないと自動クローズしてくれることに気付かないかも。
map(&:upcase)の意味
map
とかinject
でよく使うこれ。なぜこれでブロック渡したときと同じ結果になるのか、「そういうもの」的な理解しかしていなかった。
['ruby', 'java', 'perl'].map { |s| s.upcase } #=> ["RUBY", "JAVA", "PERL"]
['ruby', 'java', 'perl'].map(&:upcase) #=> ["RUBY", "JAVA", "PERL"]
&
は右辺のオブジェクト(この場合は:upcase
のシンボル)に対してto_proc
メソッドを呼び出し、戻り値のProc
オブジェクトをmap
メソッドに引数として渡している。シンボルから作ったProc
は、第一引数をレシーバにしてシンボルで指定したメソッドを呼び出す。らしい。
結局「そういうもの」の理解からはあまり脱しない気もするけど、単なるおまじないではなく裏に理由があるのだということを認識しているのが大事だと思う。
tap
メソッドチェーンの途中の値をprintデバッグしたい時に便利なメソッド。tap
はレシーバをそのままブロックに渡し、ブロックの戻り値はレシーバになるので、Proxy的な動きをしてくれる。
'ruby'.upcase.delete('Y').concat('O') #=> "RUBO"
#delete時点の値を見たい
'ruby'.upcase.delete('Y').tap { |s| puts s }.concat('O') #=> RUB
source_location
そのメソッドが定義されているライブラリのコードがどこにあるかを確認するメソッド。便利だけど多分GitHub見に行ってしまう自分がいる。
標準ライブラリなど、ソースが手元に存在しないものはnil
を返す。
'string'.method(:underscore).source_location
#=> ["/usr/local/bundle/gems/activesupport-6.1.4.1/lib/active_support/core_ext/string/inflections.rb", 143]
感想
改めて入門書を読んでみると、実務で使う範囲の構文しか身に付いていないことや、「なんとなくそういうもの」の理解で済ませている要素が色々あることに気付かされた。
言語仕様を深く理解しないままでもフレームワークを使えてしまうけど、フレームワーク自体のコードを読もうと思ったら言語仕様の理解は不可欠で、中級者以上は避けて通れない道なんだろう。
そのうち「メタプログラミングRuby」を読み直そうと思う。