Rubyist のための他言語探訪 【第 7 回】 Icon

著者:まつもとゆきひろ

はじめに

「Rubyist のための他言語探訪」は、Ruby 作者まつもとゆきひろが、Ruby と関係があったりなかったりする他の言語を紹介していく連載、のはずですが、いつの間にか「出てこいヘンな言語」になってるという説もあります。

今回は、読者からのリクエストにおこたえして Icon を紹介します。

Icon について

今回は非常に検索しにくい言語 Icon について紹介します。 もっとも検索しにくさでは Ruby も決して負けてはいないのですが。 ただし、Icon の開発が開始したのは 1977 年、バージョン 1 のリリースは 1978 年ですから、X Window System などにより、小さいウィンドウが「アイコン」と呼ばれるようになるよりも前のことですし、Icon 言語に罪はありません。

Icon は 1960 年代に開発されたテキスト処理用言語 SNOBOL (スノーボール (雪玉) と発音します)の 後継として開発されました。 1960 年代にテキスト処理というのはそれはそれは画期的なことなのですが、その件については別の機会に譲りましょう。 Icon を開発したのは SNOBOL の開発者と同じ、Arizona 大学の Ralph E. Griswold 教授 (とその仲間たち) です。

Icon は 1970 年代から 80 年代にかけて誕生した代表的な言語である Pascal や C と似たような外見を持っています。 たとえば、代入に「:=」演算子を使い、手続きを予約語「procedure」で宣言するところは Pascal 的ですし、制御構造のブロックをブレース ({ }) でくくるところは C に似ているような気がします。

しかし、Icon はいくつかの点で非常にユニークです。 今回はそのユニークな点に注目して Icon を紹介しましょう。

成功と失敗

多くの言語では真偽値は true と false で表現されます。 Ruby の真偽値は、nil または false は偽、それ以外の全ては真ですが、いずれにしても真と偽の値を持つ点では変わりがありません。 それが「普通」というものです。

ところが、Icon ではすべての手続きは成功か失敗かのいずれかの状態を取ります。 成功したときには手続きは値を返し、失敗したときには失敗したという情報を返します。 ある手続きは、

  • 期待した条件が成立しなかったとき (エラー)
  • チェックした内容が偽だったとき
  • 引数の評価が「失敗」したとき

失敗します。 失敗は例外のようなものと考えてもらえれば良いかもしれません。 ただし、失敗には他の言語の例外のような種類はなく、ただ失敗ということだけが伝わります。

一つの例として、大小比較を考えてみましょう。 比較演算子 < は左辺が右辺よりも小さいとき成功して、全体の値として右辺の値を返します。 左辺が右辺よりも小さいときには失敗します。 では、以下の式はどうなるでしょう。

 a < b < c

Rubyを含めて多くの言語では期待したとおりに動かないこの式ですが、Icon では、まず

 a < b

が評価されます。 偽であればこの式は失敗し、失敗する式を含む「a < b < c」全体が失敗します。 一方、「a < b」が真であれば b が返り、引き続いて「b < c」が評価されます。

他の言語であれば真偽値を取る制御構造は、Icon では式が成功するかどうか判定するという動作をします。 ですから、

 if a := read() then write(a)

は「一行読み込んできて、それが成功すればそのまま印字する」という動作をします。 「普通」の言語では、読み込みに失敗したときには nil などの特別の値を返して失敗を表現しているのですが、Icon では直接失敗を取り扱えます。

値について考えなくてよいということは、元々真偽値を返すようにできていない手続きでも、失敗しないかどうかという判定に使えるということです。 以下のプログラムは入力から出力へのコピーです。

 while write(read())

上記のプログラムは read() または write() が (おそらくは EOF に突き当たって) 失敗するまで read() と write() を繰り返します。 ループ本体にあたる部分が何もなくてもちゃんとコピーが行われる点が注目に値します。

ジェネレータ

Icon のもうひとつの特長がこのジェネレータです。 ジェネレータは「次々に値を返す」という点で Ruby のイテレータに似ているのですが、もっとループに特化している代わりに、Icon の失敗ベースの判定との組み合わせにより強力で興味深い機構を実現しています。

たとえば、演算子「|」はジェネレータを返します。 これは「失敗するまで両辺の値を生成する」というジェネレータです。 で、これを使うと「暗黙の繰り返し」が実現できるのです。 例を使って見てみましょう。

 if y < (x | 5) then write("y=", y)

これは「y が x または 5 よりも小さければ、y の値を印字する」という動作をします。 つまり、Ruby でいえば

 if y < x || y < 5 then
   print("y=", y, "\n")
 end

という動作です。Perl6 ではこの機能が Junction という名前で提供されるそうです。

Icon のジェネレータは「次々と値を返す」ので、その個別の値ごとに処理することができます。 そのためにはループ構文である every を使います。

 every k:=i to j do
   write(k)

という感じですね。「i to j」が、i から j までを返すジェネレータを与える式です。 ここまでは普通の言語の for 文と大差ないのですが、Icon の「恐ろしさ」はこの先にあります。 まず注意しなければならないのは、上の「k:=i to j」の部分は文法に組み込まれているわけではなく、ただの代入文だということです。 つまり、every が呼び出すジェネレータは別にトップレベルに存在する必要はありません。 every に与える式はジェネレータを含む式であれば、なんでも構わないのです。 ですから、

 every write(1 to 5)

のようなことも書けます。 これは上のプログラム同様、1 から 5 までを印字します。 また、複数のジェネレータを組み合わせることさえできます。

 every write((1 to 3) * (5 to 6))

このプログラムの実行結果は

 5
 6
 10
 12
 15
 18

になります。つまり、最初のジェネレータが返す値 (1,2,3) と二番目のジェネレータの返す値 (5,6) のすべての組み合わせを自動的に実行してくれます。 このように複数のジェネレータを組み合わせたりつなげたりするのが Icon プログラミングの醍醐味です。

ジェネレータは自分で定義することもできます。 値を返すためには予約語 suspend を使います。 ちょうど Ruby の yield のような感じですね。 こんな感じです。

 procedure myseq(x)
   repeat {
     suspend x
     x := x + 1
   }
 end

myseq 手続きは引数として与えられた数からはじめて、1ずつ増やしながら整数を(無限に)生成します。Icon には同じ働きをする組み込みのジェネレータ seq があるので myseq という名前で定義してみました。 every による繰り返しは break で中断できます。 以下のプログラムはエラトステネスのふるいにより無限に素数を求めつづけるジェネレータです。

 procedure sieve()
   suspend 1
   every i:=seq(2) & not(every i%(2 to i-1)=0 & break) & suspend(i)
 end

呼び出し方はこのようになります。

 procedure main()
   every write(sieve())
 end

本当の Icon プログラムはかならず main 手続きから実行が開始されます。 これまでの例題を試すときにも、かならず周囲を main の定義で囲ってください。

このままでは無限に素数を生成しつづけますが、生成する素数の数を限定することもできます。 Icon ではバックスラッシュ演算子でジェネレータの生成する値の数を制限できます。

 every write(sieve() \ 100)

これで最初の 100 個の素数 (523 まで) を取り出すことができます。

まとめ

この他にも Icon はいろいろなことができる実用的な言語ですが、今回紹介するのは、失敗ベースの判定とジェネレータだけです。 ここから先は自習してください。 Icon についての情報は以下から入手することができます。

Icon の処理系は上記 2 番目の Arizona 大学にある Icon 言語のホームページから入手可能です。 Debian では icont (トランスレータ) と iconx (実行系) というパッケージとして提供されています。

おまけ: Ruby への影響

Wikipedia 日本語版の Icon 言語のエントリには

Icon はプログラミング言語として広く普及しているとはいえないものの、そのユニークな機能の一部は、新世代のプログラミング言語の Python や Ruby に直接・間接に影響を与えている。

と書いてあります。 Python についてはよく知らないのですが、Ruby に与えた影響は大きくないと思います。 本連載の第 2 回 (「Rubyist のための他言語探訪 【第 2 回】 CLU」) でも紹介したように、Ruby のブロックは CLU 起源で、少し似た感じの Icon ジェネレータとは直接の関係はありません。 設計のごく初期段階には失敗ベースの真偽値判断も検討しましたが、結局取り込みませんでした。 取り込んでいたら歴史が変わっていたかもしれませんね。

では、Icon からの影響が少しもないかと考えると、思い出しました。 String や Array のインデックスで負の値を与えると後ろから数える仕様は Icon からいただいたかもしれません。 Python 経由だったような気もするんですが。 えらく些細な影響ですねえ。

次回予告

未定です。リクエスト歓迎。

著者について

matz_in_suit.jpgまつもとゆきひろは自他ともに認める日本を代表する言語オタクです。 言語好きが昂じて自分の言語を設計してしまった大馬鹿者です。 が、オタクとかハッカーとか呼ばれる人種はみんな多かれ少なかれそんなものじゃないでしょうか。

バックナンバー