Ruby の while の使い道

大多数のプログラミング言語に `while` というループ（繰り返し）のための制御構造があります。
基本的な考え方は

* 条件式と〈本体〉を持つ
* 条件式が真である間，本体を実行（評価）し続ける

というものですね。
ここで〈本体〉と書いたのはループの中身のことです。

Ruby にも制御構造 `while` があります。
[制御構造 (Ruby 3.0.0 リファレンスマニュアル)](https://docs.ruby-lang.org/ja/latest/doc/spec=2fcontrol.html)

多くの言語と同様，まず最初に条件式を評価して本体を実行するかどうか決めます[^until]。

[^until]: この順序は重要。`while` の論理反転として `until` がある。Ruby の `until` は `while` の真／偽を逆にしただけだが，Pascal の `repeat`〜`until` は本体を実行したあとで条件式を評価して脱出するか否かを決める（なので最低 1 回は本体が実行される）。

Ruby の初心者向け記事[^ar]の定番テーマの一つですが，そういった記事に書かれているサンプルコードに違和感を持ったのがこの記事を書いたきっかけです。

[^ar]: プログラミングスクールの客寄せ記事や Qiita の記事など。

# よくある while のサンプルコード

`while` を解説するためのサンプルコードでこういうのがよくあります。

```rb
n = 1

while n < 10
  puts n
  n += 1
end
```

Ruby の公式リファレンスマニュアルの `while` の説明にも似たようなサンプルが掲載されています。

あまり良い例とは言えません。C 言語プログラムの直訳みたいに見えます。
こんな会話を聞かされているようです。
「貴殿は如何にありや。」
「息災なり。貴公に謝す。して貴公は。」

# 何がいけないのか，どう書くべきか

上記のコードは何がいけないのでしょうか。

第一に Ruby らしくありません。

そして分かりづらい。
私のようなヘタレには，表示される数が 1 から 9 までであるということが一瞬では分かりません（しかし，こういうコードに馴れている人なら一瞬で分かるでしょう）。


この処理は

```rb
1.upto(9) do |n|
  puts n
end
```

とか

```rb
(1..9).each do |n|
  puts n
end
```

などと書くべきです[^p]。これらのほうが簡潔で，何がやりたいのか明瞭です。
ループ変数（`n` のこと）をあらわに初期化する記述もインクリメントする記述も不要です。

[^p]: ここでは 1 から 9 までのループをどう書くかという観点でコードを示した。単に 1 から 9 まで改行しながら表示させたいなら `puts [*1..9]` でよい。

最初に掲げたコードについて，「実際的なコードではなくても，`while` の働きを簡潔に説明するのが目的だから問題ない」という反論があるかもしれません。
一理あると思います。ただ，初心者が現場でこういうコードを書いてしまわないよう，一言あってよいでしょう。

# Ruby では while の出番は少ない

Ruby では `while` の出番は少ない，と言い切ってよいと思います。
matz（まともとゆきひろさん）が，「メソッドとブロックという仕組みで繰り返し処理が簡潔に書ける」ことを発見したことで Ruby の大きな特長が生まれたと思います[^rt]。いや知らんけど。

[^rt]: 参考：[「プログラミング言語の歴史を会話方式で振り返る」を横目で見ながらの会話 - Qiita](https://qiita.com/scivola/items/b31729f7f8e11402256a)

値をすこしずつ変えてループを回すような場合，たいがいそれに適したメソッドがあります。`times`，`upto`，`downto`，`step` などですね。
コレクション[^coll]の要素について繰り返すメソッドも非常に充実しています。これらは [Enumerable](https://docs.ruby-lang.org/ja/3.0.0/class/Enumerable.html) モジュールに集約されています。
Enumerable の機能を幅広く使えるようにするための [Enumerator](https://docs.ruby-lang.org/ja/3.0.0/class/Enumerator.html) という仕組みもあります。
[^coll]: 配列やハッシュなど要素の集まりであるようなオブジェクトをコレクションと呼ぶ。

これら繰り返しのためのメソッドをイテレーターと呼んでいます[^iter]。

[^iter]: 一般的な用語としてはイテレーターはもっと広い概念。

以下に Ruby の繰り返し処理の例を挙げます。

```rb
# 双子素数（差が 2 である素数ペア）の最初の 10 組を表示
require "prime"
p Prime.lazy.each_cons(2).select{ _1 + 2 == _2 }.take(10).to_a
# => [[3, 5], [5, 7], [11, 13], [17, 19], [29, 31], [41, 43], [59, 61], [71, 73], [101, 103], [107, 109]]

# 文字列の偶数番目の文字を取り出して繋げる
puts "clump".chars.select.with_index{ _2.even? }.join
# => "cup"
```

`while` で書こうとするとなかなか面倒ではないでしょうか。

# while の使い道

ではどんな場合に `while` の出番が回ってくるのでしょうか。

ループ変数が存在するようなループの場合，「ループ変数がどんなふうに変化していってどこで終了するか」が簡単に分かるようなループはたいがいイテレーターメソッドで足りそうです。

ループ変数が存在していて，`while` が使われるのは，値の変化が簡単には言えない場合が多いのではないかと思います。
あるいはループ変数が存在しないようなループですね（具体例が思いつかず）。

# コラッツの問題

`while` がふさわしそうな例を一つ挙げます。

「[コラッツの問題](https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%A9%E3%83%83%E3%83%84%E3%81%AE%E5%95%8F%E9%A1%8C)」とか「<ruby>角<rt>かく</rt>谷<rt>たに</ruby>の問題」と呼ばれる数学の未解決問題があります。
何か一つ自然数（つまり正の整数）を考えます。それが偶数なら 2 で割り，奇数なら 3 倍して 1 を足します。出来た数についても同じことを繰り返します。
そうすると，どんな自然数から始めても必ず 1 に達する，という予想があるのです。
しかしまだ証明はされていません。
証明されるか，逆に反例があることが示されれば解決ですが，この問題は超難問と言われているようです。

試しに 3 から出発して手計算でやってみましょう。

```
3 → 10 → 5 → 16 → 8 → 4 → 2 → 1
```

途中で 2 の累乗になれば一気に 1 にたどり着きます。
もっと長いステップを楽しみたければぜひ 27 からスタートしてみてください。

ともかく，人類がこれまでに調べた限りにおいてすべての自然数が最後には 1 にたどり着いたのだそうです。

では，上のような系列を表示するコードを書いてみましょう。

```rb
n = 3

while n > 1
  print n, " → "
  n = n.even? ? (n / 2) : (n * 3 + 1)
end
puts 1
```

しかし，これとてもイテレーターメソッドで書けます。
`while` を一切使わないで（しかも無理をせずに）プログラミングすることは可能です。

もう一つくらい `while` の使用例を挙げようと思いましたが，どうも気の利いたのが思いつきません。どなたか教えてください。