言語仕様と処理系仕様と実装と

プログラミング言語では仕様で保証されている挙動とそうでない挙動がある。 単に検討されなかった部分について記述が漏れている場合もあるし、実装の裁量で効率的な方法を選択できるようにあえて規定しない場合もある。 たとえば Scheme では map 手続きがリストを評価する動的順序は規定していない。 これは仕様書に「未規定である」と明記されているので意図的に規定を避けたものだと思う。 処理系によって、あるいは同じ処理系でも状況によって適当な順序を選択することが可能なのだ。 副作用を生ずる手続きを map に渡した場合には状況によって異なる結果が生じることが有り得る。

とは言うものの、現実的には評価順序はリストの先頭からか末端からかのどちらだろうし、仕様に熟知しているのでなければリストの先頭側から評価するものと期待してしまうというのもそう不自然ではないだろう。 初心者がそういうコードを書いているのを見たことは何度かある。 また、 Gauche では map はリストの先頭側から処理することがドキュメントに明記されているので Gauche に限ってはそれを期待することが出来る。 処理系の仕様として保証しているわけだ。

その他、手元の環境にインストールしている処理系で map がリストのどちら側から処理するか試してみた。 以下はリストの先頭側から処理した処理系だ。

  • Sagittarius 0.7.4
  • Chicken 4.11.0
  • Scheme48 1.9.2
  • Ypsilon 0.9.6-trunk/r506
  • Mosh 0.2.7
  • Rhizome/pi 0.57
  • Larceny 0.99
  • Guile 2.0.11
  • Racket 6.5
  • Foment 0.4

それぞれのドキュメントを確認していないので保証しているかどうかまでは知らないが、これらは実際にリストの先頭側から処理する実装になっている。

以下のように短かいリストで試してみただけなので複雑な状況で挙動が変わる場合も有るかもしれないが。

(map display '(1 2 3 4 5))

つまり、ある挙動を保証しているのは言語仕様か、処理系の仕様か、実装が結果的にそうなっているだけなのかという種類があって、その言語で書いたプログラムが動いているのも実は偶然でしかないという場合は意外にあると思う。 個別の処理系の方針について詳細を常に把握するのは難しいが、書いたプログラムをたまには別の処理系で動かしてみたりするくらいはした方が良いと思う次第である。

余談だが、上記の確認において Husk 3.19.2 は map がリストの末端側から処理した。

更に Chez Scheme 9.4.1 は 2 個ごとに入れ替わるような順序になった。 53412 というような順序である。 もちろんこれでも言語仕様には違反しないが、どうしてそういう選択をしたのかは興味深いのでいずれソースコードを確認してみようと思う。

Document ID: b939601ec40ec23a868fde033123cfd9