こんにちは、エンジニアの神山です。
Rubyを書いてる人であれば一度は聞いたことあるチェリー本こと プロを目指す人のためのRuby入門 。その新版が昨年12月に出ました。
先日読んでたところ、今まで知らなかったことや忘れていたことがいくつか発掘されたので、記事にしてみました。ちなみに僕はRubyを触って6年になります。
先にチェリー本のリンクを貼っておきます。とても素晴らしい本なので、ぜひ読んでみてください。
プロを目指す人のためのRuby入門[改訂2版] 言語仕様からテスト駆動開発・デバッグ技法まで (Software Design plus)
map(&:upcase) の仕組み
これを知れた喜びからこの記事を書いたと言っても過言ではありません。(わりとみんな知っているのだろうか。。)
皆さんは、①の書き方が②のようにも書ける理由をご存知でしょうか。
# ① ['abc', 'def'].map { |string| string.upcase } # ② ['abc', 'def'].map(&:upcase)
これには Procオブジェクトが関わっています。ただ単純な話ではないので細かく説明していきます。 ※ブロックやProcオブジェクトに関してはWEB上にたくさん情報があるので簡単な解説にとどめます。
ブロックとは
ブロックとは処理(のかたまり)です。先程のコードで言うと、 { |string| string.upcase }
です。言語化すると 文字列を大文字にする処理
になります。
Rubyを書いてる人であれば each
や map
などのメソッドでよく使いますね。
ちなみにブロックを扱うメソッドを定義するにはこのように書きます。(いろんな書き方があるので下は一例です。)
# 文字列に何らかの処理をするメソッド def string_ex(string, &block) puts '文字列に処理を施します' block.call(string) end > string_ex('hoge') { |s| s.upcase } 文字列に処理を施します => "HOGE" > string_ex('hoge') { |s| "!!!#{s}!!!" } 文字列に処理を施します => "!!!hoge!!!" # 組み合わせて使うこともできます。 > string_ex(string_ex('hoge') { |s| "!!!#{s}!!!" }) { |s| s.upcase } 文字列に処理を施します 文字列に処理を施します => "!!!HOGE!!!"
このようにブロックを扱うメソッドを定義すると、状況に応じて好きな処理を渡すことできます。
※ブロックを引数として受け取るには &
が必要です。
Procオブジェクト とは
Procオブジェクトとはブロックのような処理をオブジェクト化したものです。先程の例をProcオブジェクトを使って書き直してみます。
# 小文字を大文字にする処理 upcase_proc = Proc.new { |s| s.upcase } # 文字列を強調させる処理 emphasis_proc = Proc.new { |s| "!!!#{s}!!!" } > string_ex('hoge', &upcase_proc) 文字列に処理を施します => "HOGE" > string_ex('hoge', &emphasis_proc) 文字列に処理を施します => "!!!hoge!!!" > string_ex(string_ex('hoge', &upcase_proc), &emphasis_proc) 文字列に処理を施します 文字列に処理を施します => "!!!HOGE!!!"
ブロックで渡したときとの違いとして、Procオブジェクトを先程のメソッドに渡すには &
をつける必要があります。
mapにProcオブジェクトを渡す
今までの流れからわかるように、 &proc_object
のように書けば map
にProcオブジェクトを渡すことができます。
upcase_proc = Proc.new { |s| s.upcase } > ['abc', 'def'].map(&upcase_proc) => ["ABC", "DEF"]
ここで補足ですが &
はメソッドにProcオブジェクトを渡す前に to_proc
メソッドを利用してProcオブジェクトに変換を行っております。
今回で言うと、 map
に渡す前に実は upcase_proc.to_proc
を行っております。ただProcオブジェクトに to_proc
しても変わらないので今回は処理に変化はありません。
シンボルをProcオブジェクトに変換
「そんなことできるの?」って思う人もいるかも知れません。僕も初めて知りました。
たとえば今まで例に使っていた upcase_proc
は以下のように定義できます。
> upcase_proc = :upcase.to_proc => #<Proc:0x000055b7ae696728(&:upcase) (lambda)> > string_ex('hoge', &upcase_proc) 文字列に処理を施します => "HOGE"
まとめ
改めて大事なことを列挙します。
- ブロックはProcオブジェクトとしてオブジェクト化できる
- Procオブジェクトはブロックを扱うメソッドに対して、
&
を使うことでブロックのように渡せる &
は引数をメソッドに渡す前に引数にto_proc
を行う- シンボルは
to_proc
でProcオブジェクト化される
つまり map(&:upcase)
が map { |string| string.upcase }
と同義になるのは、以下のような説明になります。
:upcase
が&
によってProcオブジェクトに変換(内部で.to_proc
される)- また
&
によってProcオブジェクトをブロックと同じようにmap
に渡される
正規表現のキャプチャに名前をつけられる
正規表現のキャプチャは度々使ってましたが、名前をつけられることは初めて知りました。
正規表現のキャプチャとは
()
を使うことでマッチした値を利用することができます。
> text = '2022年5月10日' => "2022年5月10日" > result = /(\d+)年(\d+)月(\d+)日/.match(text) => #<MatchData "2022年5月10日" 1:"2022" 2:"5" 3:"10"> # キャプチャできる > result[1] => "2022" > result[2] => "5" > result[3] => "10" # ちなみに組み込み変数を使うこともできる > $1 => "2022" > $2 => "5" > $3 > $~ => #<MatchData "2022年5月10日" 1:"2022" 2:"5" 3:"10">
ただ result[1]
だと実際何を表す値かわからないですよね。
そこでキャプチャに名前をつけることができます。
> text = '2022年5月10日' => "2022年5月10日" > result = /(?<year>\d+)年(?<month>\d+)月(?<day>\d+)日/.match(text) => #<MatchData "2022年5月10日" 1:"2022" 2:"5" 3:"10"> # キャプチャできる > result[:year] => "2022" > result[:month] => "5" > result[:day] => "10"
ちなみに =~
で書くとキャプチャ名をそのままローカル変数として使えます。今まで組み込み変数を使ってたので良いことを知れました。
> /(?<year>\d+)年(?<month>\d+)月(?<day>\d+)日/ =~ text => 0 > year => "2022" > month => "5" > day => "10"
1行パターンマッチ
パターンマッチはRuby3.0で正式に導入されました。ただまだ実務で使ったことがなく知らないことばかりです。 その中でも1行で書くことができる1行パターンマッチはとても便利そうです。
> full_name = { first: 'taro', last: 'kitabeppu' } > full_name in { first: String, last: String } => true > full_name = { hoge: 'taro', last: 'jiro' } > full_name in { first: String, last: String } => false > full_name = { first: 1, last: 'hoge' } > full_name in { first: String, last: String } => false
番号指定パラメータ
ブロックパラメータを明示的に指定せずに、番号指定パラメータを使って書くことができます。ただ分かりづらいのでそこまで利用頻度はなさそうです。
> [1, 2, 3].map { _1.to_s } => ["1", "2", "3"]
全範囲オブジェクト
下のように書くと全範囲を表す範囲オブジェクトになるそうです。
nil..nil ..nil nil.. > (nil..nil).cover?(1000000000000000000000000000000000000000000000) => true
まとめ
社内で共有したところ「これは知ってた」「これは知らなかった」などの声があり、意外に自分が当たり前だと思っていることも他の人は知らなかったりすることに気づきました。こういう共有はとても有効だと思うので、今後も続けていきたいです。
改めて再度、本の紹介をして終わります。意外な気付きがあったりするのでぜひ読んでみてください。
プロを目指す人のためのRuby入門[改訂2版] 言語仕様からテスト駆動開発・デバッグ技法まで (Software Design plus)
クラウドワークスに興味のある方へ
株式会社クラウドワークスでは、共によりよい社会を築いてくれる仲間を募集しています! ぜひご応募ください!