ダイクストラ先生の議論とRubyの範囲オブジェクト

延々と範囲オブジェクトのことばかり調べているぼくは異常なRuby入門者だろうか……、という心配を感じつつも、まだ範囲オブジェクトのことを。

Rubyの範囲オブジェクトは始点と終点を含む。(1..5)は1≤i≤5であって、1≤i<5ではない。いっぽう、ドットを3つ並べた(1...5)という表記を使うと、

irb(main):091:0> (1...5).to_a
=> [1, 2, 3, 4]

のように1≤i<5を表現できる。マニュアルや解説の類を見ると、範囲オブジェクトでは2ドットのほうが標準であって、3ドットのほうは「こういうのもあるよ」と二級市民的な扱いとなっているようだ。範囲オブジェクトを使って、例えば

for i in 1..5
  puts i
end

などとやるわけだけど(ぼくはfor文は捨ててeachで統一することにしたけど)、これはCで書けば、

for (i = 1; i <= 5; i++)

ということだ。しかし、配列の添字はゼロからスタートするし、ループの停止条件は「<=」ではなく「<」のほうが一般的なんじゃないだろうか。

for (i = 0; i < 5; i++)

というように。Rubyソースコードgrepしてみたところ、「<=」か「<」を含むfor文が781個、そのうち「<=」は38個しかなかった。コンベンショナルには、やっぱり0≤i<5とやるのが普通なんじゃないだろうか。

と、いうことについてダイクストラ先生が考察をしていて、http://www.cs.utexas.edu/users/EWD/transcriptions/EWD08xx/EWD831.htmlを見ると、以下の4つのうちどれを使うべきかという理由が書いてある。

  • (a) 2 ≤ i < 13
  • (b) 1 < i ≤ 12
  • (c) 2 ≤ i ≤ 12
  • (d) 1 < i < 13

Mesaというプログラミング言語には、この4種類があったけど、混乱のもとだったので結局(a)しか使わなくなったというようなことも実例として引いている。興味深い。

(a)や(b)がいいのは、引き算すれば要素数になることと、上限と下限が一致するものは隣接していると判定できること。で、(a)か(b)のどちらかって話だけど、自然数には最小のもの(=1)があるので(b)よりも(a)のほうがいいのだという。なるほどね。

Rubyではなぜ(c)を範囲オブジェクトの標準にしていて、(a)を予備的な構文にしているのだろうか。きっとまつもとさんなら、それについて1時間ぐらい考察を述べてくれるのだろうと思うけど、ぼくの勝手な推論では「0 ≤ i < 5」とかって、やっぱりコンピュータの理屈でしかなくて、人間にとっては「1から5」といえば、ふつうは1も5も含むでしょ、という理由なんじゃないかと思う。それに抽象度が高い言語では、あんまり配列に数値の添字でアクセスするってないような気もするし、律儀に0からスタートする意味も薄れてるとか。違うかな。