最初に
Ruby Advent Calendar 2021 Part2の18日目の記事です。
昨日は著名なigaigaさんの「Ruby3.0キーワード引数仕様変更に伴う書き換えをしたので調べたことのまとめAdd Star」でした!
RubyのアドベントカレンダーのPart2は枠が余ってたし、備忘用に書いたものがあったこともあり、編集して公開してみることにしました。
StringやHashのeach系のメソッドが体系化されて説明された記事を読んだことがないし、他の人も読んだことがなさそうだと思ったので、そういった内容です。
ちょっと表にしたり、ちょっと歴史的な流れを説明したりって感じですけど。
Stringのeach系メソッドについて
配列(Array)やハッシュ(Hash)にはeach
というインスタンスメソッドがあります。
対して、今の文字列(String)にはeach
メソッドは存在しません。
昔のRuby 1.8時代までは文字列にもeach
メソッドが存在しており、
今でいうeach_line
の機能で行ごとに回していたのですが、
文字列のeach
は何の単位で回すのか自明ではないためRuby 1.9で削除されました。
今はRuby 3.0が最新ですが、
実際のところ文字列にはeach_
で始まるメソッドが5つ存在します。
さらに、これと対応して、(ブロックがなければ)配列を返すメソッドが5つ存在します。
配列を返すメソッド | each系メソッド | 要素のクラス | 要素の単位 |
---|---|---|---|
chars | each_char | String | (旧式単位の)文字 |
lines | each_line | String | 行 |
bytes | each_byte | Integer | バイト |
codepoints | each_codepoint | Integer | コードポイント |
grapheme_clusters | each_grapheme_cluster | String | 書記素単位の文字 |
特にRuby/Railsを書く際に「配列が入る変数名は複数形にする(要素名はその単数形)」という慣習が存在しますが、Ruby本体のメソッド名にも「配列を返すメソッド名は複数形にする」というケースがあることがわかります。
対してeach_
始まりのメソッドは単数形になっていて、全体的に英語の文法にちゃんと則っている感じがしますね。
追記:
ところで、ruby-jpでscivolaさんから、この表の要素の単位について、charsもcodepointsも同じ単位のはずなのに違うように見える旨の指摘がありました。確かにそうですね。ここに追記する形で済ませておきます。
簡単な実行例
それぞれのメソッドの実行例を簡単に見てみましょう。
s = "aあ\n💪🏼"
p s.chars # ["a", "あ", "\n", "💪", "🏼"]
p s.bytes # [97, 227, 129, 130, 10, 240, 159, 146, 170, 240, 159, 143, 188]
p s.lines # ["aあ\n", "💪🏼"]
p s.codepoints # [97, 12354, 10, 128170, 127996]
p s.grapheme_clusters # ["a", "あ", "\n", "💪🏼"]
この中で、grapheme_clusters
は比較的新しいメソッドで、
簡単にいえばchars
の新しい版みたいなもんです。
もともとあったchars
の形式だと、新しく導入された絵文字で不合理なことが起きるケースがあります。
例えば、実行例にある通り、chars
だと「うすだいだい色の腕の絵文字💪🏼
」が、「腕の絵文字(黄色)」と「うすだいだい色」に分離されてしまうみたいなことが起きるため、そうならないように書記素単位で文字を分離するgrapheme_clusters
メソッドがRuby 2.5から導入されています。
lines
は、名前のとおり行ごとに区切るメソッドですね。
"\n"
のような改行で区切るメソッドです。
bytes
は、バイト単位に区切るメソッドです。要素は、Integerです。
"a"
は97で、"あ"
は3バイトで構成されるマルチバイト文字であり227, 129, 130をに分割されます。
codepoints
は、コードポイント単位で区切るメソッドです。要素は、Integerです。
"a"
は97で、"あ"
は12354となります。
chars
等でもブロックをとって回せるが
なお、chars
等の複数形のメソッドでも、each
系と同じようにブロックをとって回せます。
# ブロック有りの場合は、どちらも同じ動作をする
"abc".each_char{ |c| p c }
"abc".chars{ |c| p c }
ただchars
等でブロックをとって回せるのは、知らない人にはわかりにくいはずです。
chars
でブロックをとって回せなくしようという議論もあったはずで、
実際chars
等でブロックをとって回すのがdeprecatedであるという警告が、Ruby 2.6まではでてました。
しかし、Ruby 2.7で警告はでなくなったので、これから先chars
でブロックをとって回せなくなる可能性はかなり低くなったように思います。
依然としてchars
にブロックをつけて文字を回せますが、
ブロックで回すなら業務等ではeach_char
で書いた方が良いだろうと思います。
Hashのeach系メソッド
文字列は、each
は存在せず、chars
やlines
等でもブロックをとって回すことも出来ました。
一方、Hash
には、each
が存在しEnumerable
で、keys
やvalues
はブロックで回せずeach_key
やeach_value
でないと回せません。
keys
やvalues
でブロックで回せないこと自体はいいとして、Stringのchars
等はブロックで回せるので、文字列とハッシュで動作が一貫してない点には注意ですね。
Hashのeach始まりのメソッドは下記です。
配列を返すメソッド | each系メソッド | 要素 |
---|---|---|
to_a | each, each_pair | キーと値(の配列) |
keys | each_key | キー |
values | each_value | 値 |
Hashのeach
とeach_pair
はエイリアスです。
Hashではeach
よりもeach_pair
を使った方が、Hash
であろうという推測が成り立ち可読性が高くなると思うのですが、個人的にはeach_pair
を使ったコードを見た覚えがないです。
なお、String
の流れ的にはあっても変じゃないと思うのですが、pairs
というメソッドは存在しないです。
余談1「charsは配列を返していなかった」
もともとRuby 1.9まではStringのchars
はeach_char
とエイリアスで、ブロックがない際はEnumerable
のオブジェクトを返していました。しかし、次のyharaさんによる提案から、変更されたようです。
ザッと読む感じ、次のようなコードでto_a
が必要な理由を初心者に説明しにくいよねって発端です。
str = "abc"
str.chars.to_a.last
str.chars.to_a[1,3]
余談2「ARGF」
ARGF
オブジェクトのchars
等は、Ruby 3.0で削除されました。
文字列のchars
と異なり、このARGF
のchars
は、配列を返したわけではなく、each_char
と同じ振る舞いをしてたようです。
参考リンク
String#eachがRuby 1.9で削除された話について、
詳しく知りたい方は下記リンク先などを読むといいかもしれないです。
それで文字とかバイトで使いたいときには、明示的に区切ってから繰り返しなさいと(していた)。言ってみれば「行」が一級市民で、「文字」と「バイト」が二級市民と、扱いに差別があったわけです。差別はよくないので、何に対して繰り返すかを明示的に指定することにしました。
最後に
参考になったら、他の記事も読んでもらえると嬉しいです!
今年は、次の企画アドベントカレンダーの記事もやってみましたので、皆様も挑戦されてみてはいかがでしょう。