クラウドワークス エンジニアブログ

日本最大級のクラウドソーシング「クラウドワークス」の開発の裏側をお届けするエンジニアブログ

新版チェリー本を読んで今更ながら知ったこと〜主にmap(&:upcase)の話〜

新版チェリー本を読んで今更ながら知ったこと〜主にmap(&:upcase)の話〜

こんにちは、エンジニアの神山です。

Rubyを書いてる人であれば一度は聞いたことあるチェリー本こと プロを目指す人のためのRuby入門 。その新版が昨年12月に出ました。

先日読んでたところ、今まで知らなかったことや忘れていたことがいくつか発掘されたので、記事にしてみました。ちなみに僕はRubyを触って6年になります。

先にチェリー本のリンクを貼っておきます。とても素晴らしい本なので、ぜひ読んでみてください。

プロを目指す人のためのRuby入門[改訂2版] 言語仕様からテスト駆動開発デバッグ技法まで (Software Design plus)

www.amazon.co.jp

map(&:upcase) の仕組み

これを知れた喜びからこの記事を書いたと言っても過言ではありません。(わりとみんな知っているのだろうか。。)

皆さんは、①の書き方が②のようにも書ける理由をご存知でしょうか。

# ①
['abc', 'def'].map { |string| string.upcase }

# ②
['abc', 'def'].map(&:upcase)

これには Procオブジェクトが関わっています。ただ単純な話ではないので細かく説明していきます。 ※ブロックやProcオブジェクトに関してはWEB上にたくさん情報があるので簡単な解説にとどめます。

ブロックとは

ブロックとは処理(のかたまり)です。先程のコードで言うと、 { |string| string.upcase } です。言語化すると 文字列を大文字にする処理 になります。 Rubyを書いてる人であれば eachmap などのメソッドでよく使いますね。

ちなみにブロックを扱うメソッドを定義するにはこのように書きます。(いろんな書き方があるので下は一例です。)

# 文字列に何らかの処理をするメソッド
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)

www.amazon.co.jp

クラウドワークスに興味のある方へ

株式会社クラウドワークスでは、共によりよい社会を築いてくれる仲間を募集しています! ぜひご応募ください!

crowdworks.co.jp

© 2016 CrowdWorks, Inc., All rights reserved.