【初級者向け】RubyのStringとHashのeach系メソッドを体系的に整理

# 最初に

[Ruby Advent Calendar 2021](https://qiita.com/advent-calendar/2021/ruby) Part2の18日目の記事です。
昨日は著名なigaigaさんの「[Ruby3.0キーワード引数仕様変更に伴う書き換えをしたので調べたことのまとめAdd Star](https://igarashikuniaki.net/diary/20211217.html)」でした！


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も同じ単位のはずなのに違うように見える旨の指摘がありました。確かにそうですね。ここに追記する形で済ませておきます。

## 簡単な実行例

それぞれのメソッドの実行例を簡単に見てみましょう。

```ruby
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`系と同じようにブロックをとって回せます。

```ruby
# ブロック有りの場合は、どちらも同じ動作をする
"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さんによる提案から、変更されたようです。

<!-- Feature #6670: str.chars.last should be possible - Ruby master - Ruby Issue Tracking System -->

https://bugs.ruby-lang.org/issues/6670

ザッと読む感じ、次のようなコードで`to_a`が必要な理由を初心者に説明しにくいよねって発端です。

```ruby
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`と同じ振る舞いをしてたようです。

<!-- 【Ruby 3.0 Advent Calendar 2020】Ruby3.0で非推奨から廃止になるメソッドたち【4日目】 - ゲームリンクスの徒然なる日常 -->

https://gamelinks007.hatenablog.com/entry/2020/12/04/222254


# 参考リンク

String#eachがRuby 1.9で削除された話について、
詳しく知りたい方は下記リンク先などを読むといいかもしれないです。

<!-- 第6回 Ruby 1.9に起きた変化 - O'Reilly Japan Community Blog -->

https://www.oreilly.co.jp/community/blog/2009/05/changes-on-ruby-1-9.html



> それで文字とかバイトで使いたいときには、明示的に区切ってから繰り返しなさいと（していた）。言ってみれば「行」が一級市民で、「文字」と「バイト」が二級市民と、扱いに差別があったわけです。差別はよくないので、何に対して繰り返すかを明示的に指定することにしました。

# 最後に

参考になったら、他の記事も読んでもらえると嬉しいです！
今年は、次の企画アドベントカレンダーの記事もやってみましたので、皆様も挑戦されてみてはいかがでしょう。

https://qiita.com/universato/items/b127d12d2a7494abcd0e