Iconは、高水準のプログラミング言語であり、アプリケーションの開発やデータ処理、テキスト処理に適しています。Iconは、多くの人が使用するようになっており、特にUNIXやLinuxシステムでよく使用されています。

Wikipedia
Wikipedia
ウィキペディアIconの記事があります。

このチュートリアルでは、Iconの基本的な概念や文法について説明し、プログラムの作成方法や実行方法を学ぶことができます。また、実際に手を動かしながら学習できるように、多数のサンプルプログラムも提供しています。

このチュートリアルを完了することで、Iconを使用してプログラムを作成するために必要な基礎的な知識を身につけることができます。是非、このチュートリアルを通じてIconを学び、プログラミングの世界に足を踏み入れてみてください。

イントロダクション

編集

Iconは、文字列と構造の処理に多くの機能を持つ高水準プログラミング言語です。 Iconには、複数の結果を生成する可能性のある式、成功する結果を自動的に探す目標指向評価、文字列スキャンによる高度な概念レベルの文字列操作など、いくつかの新しい機能があります。

Iconは、高度な文字列処理と簡単なプログラミングという設計思想を重視しており、短く簡潔なプログラムを可能にします。 Iconでは、ストレージの割り当てとガベージコレクションが自動的に行われ、オブジェクトのサイズには制限がほとんどありません。 文字列、リスト、その他の構造はプログラムの実行時に作成され、プログラムを記述する際にそのサイズを知る必要はありません。 値は自動的に期待される型に変換され、たとえば入力として読み込んだ数字の文字列は、明示的な変換なしに数値計算に使用できます。 Iconには予約語を含む式ベースの構文があり、外見上、IconのプログラムはPascalやCのものに似ています。

Iconとは何か

編集

Iconは、1977年にRalph Griswoldによって設計された高水準プログラミング言語で、SNOBOL, SL5およびALGOLに影響を受けています。 Iconは、構文的な柔軟性、文字列処理、抽象データ型など、さまざまな機能を提供しています。

Iconの特徴

編集

Iconは、高レベルの文字列処理と、プログラミングの容易さと短く簡潔なプログラムを可能にする設計思想に重点を置いています。

Iconの主な特徴としては、以下のようなものがあります:

  • 文字列処理 - 文字列処理:文字列を処理するための豊富な機能を備えており、パターンマッチング機能による文字列の便利な分析も可能です。
  • 目標指向の評価 - Iconは、制御構造を用いて、評価とバックトラックを組み合わせています。
  • 組み込むデータ構造 - 数値、文字列、リスト、文字、レコード、プロシージャ、連想検索が可能なテーブルなどのデータ構造が用意されています。
  • ポータビリティ Iconはポータブルな言語であり、様々なプラットフォームでコンパイルして実行することができます。
  • Iconは強力で汎用性の高い言語であり、様々なアプリケーションを作成することができます。初心者にも経験豊富なプログラマーにもおすすめです。

Hello world

編集

次のプログラムを、hello.icnの名前で保存してください。

hello.icn
# Iconのプログラムのエントリーポイントは main プロシージャです。
procedure main()          
   write("Hello world!");
end

コマンドラインから、次のようにタイプしてください。

$ icon hello.ico

次のように表示されます。

実行結果
Hello world!

プロシージャ

編集

プロシージャの使い方を説明するために、先のプログラムを次のように分割しました。

hello2.icn
# Iconのプログラムのエントリーポイントは main プロシージャです。
procedure main()
    hello()
end

procedure hello()
    write("Hello world")
end

mainからのhelloの呼び出しが前方参照になっていることに、気がついたかもしれません。

Iconは、前方参照を処理系が自動的に解決します。

変数

編集

Iconにおける変数について

編集

動的型付け言語のIconの変数には型はありません。 変数を予め宣言する必要は基本的にありません。

変数に値を割り当てる

編集

Hello world を少し書き換えてみます。

variable.icn
# 変数の使用例
procedure main()
   x := "Hello world!"           
   write(x);
   x := 42;
   write(x);
end
実行結果
Hello world!
42
最初に、変数xには文字列"Hello world!"を代入しました。
Iconの代入は := です。
一度、xを表示した後、今度は整数42を代入しています。
何もなかったかのように、今度は42が表示されます。

このようにIconの変数には型はなく、値(=オブジェクト)が型を持っています。

グローバル変数宣言

編集

Iconのグローバル変数は、予約語globalを使って宣言します。

# プロシージャ f の定義
procedure f()
   write("Begin");
   write(x);
   write("End");
end

procedure main()
   x := "Hello world!"           
   f();
end
実行結果
Begin

End

プロシージャmainの中で変数xに代入した結果は、プロシージャfでは参照できていない事がわかります。

# グローバル変数の宣言
global x

# プロシージャ f の定義
procedure f()
   write("Begin");
   write(x);
   write("End");
end

procedure main()
   x := "Hello world!"           
   f();
end
実行結果
Begin
Hello world!
End

今度は、プロシージャfから変数xを参照することが出来ました。

違いは、予約語globalを使って宣言したことです。

ローカル変数宣言

編集

グローバル変数は、特定の用途では便利ですが以下のようなデメリットがあります。

  1. 名前の衝突: 大規模なプログラムでは、グローバル変数を使うと、同じ名前の変数が別の場所で誤って定義される可能性があります。これにより、プログラム全体でのデータの整合性が損なわれる可能性があります。
  2. メモリ使用量: グローバル変数は、プログラム全体で共有されるため、その変数の値を保持するために必要なメモリ量が増加します。大量のグローバル変数を使用すると、プログラムの実行に必要なメモリが不足する可能性があります。
  3. デバッグの難しさ: グローバル変数を使用すると、プログラムの動作が予測不能になる可能性があります。特に、グローバル変数がどこで設定され、どこで変更されているかを特定するのは困難です。したがって、バグの修正やテストの実行が困難になる可能性があります。

特に、偶発的にグローバル変数と同じ名前を踏んだときには、原因とかけ離れたところで障害が出るので、グローバル変数は多用すべきではありません。

Iconでは、グローバル変数と名前が重なってもローカル変数であることを宣言することが出来ます。

# グローバル変数の宣言
global x

# プロシージャ f の定義
procedure f()
   write("Begin");
   write(x);
   write("End");
end

procedure main()
   local x # ローカル変数を宣言
   x := "Hello world!"           
   f();
end
実行結果
Begin

End

静的変数の宣言と初期化

編集

Iconでは、静的変数の宣言には予約語staticを、その初期化には予約語initialを使います。

procedure main()
   func();
   func();
   func();
   func();
end

procedure func()
   static i;
   initial i := 0;
   write("i = ", i);
   i +:= 1;
end
実行結果
i = 0
i = 1
i = 2
i = 3

予約語

編集

Iconの予約語( Reserved Words )は以下のとおりです。 これらは、変数や関数の名前(識別子)に使うことは出来ません。

break by case create default do else end every fail global if initial invocable link local next not of procedure record repeat return static suspend then to until while

演算子と区切子

編集

Iconの演算子と区切子は以下のとおりです。 これらは1つ1つがトークンで、間に空白を含めることは出来ません。

! % %:= & &:= ( ) * ** **:= *:= + ++ ++:= +: +:= , - -- --:= -: -:= . / /:= : := :=: ; < <- <-> <:= << <<:= <<= <<=:= <= <=:= = =:= == ==:= === ===:= > >:= >= >=:= >> >>:= >>= >>=:= ? ?:= @ @:= [ \ ] ^ ^:= { | || ||:= ||| |||:= } ~ ~= ~=:= ~== ~==:= ~=== ~===:=

キーワード

編集

Iconのキーワードは以下のとおりです。

  • &allocated - ストレージ領域で使用されているスペース
  • &ascii - 128個のASCII文字から構成される文字集合
  • &clock - 現在の時刻から構成される文字列
  • &collections - 総計、静的要求によってトリガーされたコレクションの数
  • &col - テキスト列のマウスの水平位置
  • &column - 現在の実行ポイントのソース列番号
  • &control - 最後のXイベントで制御キーが押されていればnull、それ以外はfailure
  • &cset - すべての256文字から構成される文字集合
  • &current - 現在アクティブな共役
  • &dateline - 現在の日時
  • &date - 現在の日付
  • &digits - 10個の10進数字から構成される文字集合
  • &dump - 終了ダンプを制御する変数
  • &error - エラー変換の有効化/無効化
  • &errornumber - 最後に変換されたエラーのエラー番号(failureに変換)
  • &errortext - 最後に変換されたエラーのエラーメッセージ(failureに変換)
  • &errorvalue - 最後に変換されたエラーの誤った値(failureに変換)
  • &errout - 標準エラー出力
  • &e - 自然対数の底
  • &eventcode - 監視プログラム内のイベント
  • &eventsource - 監視プログラム内のイベントのソース
  • &eventvalue - 監視プログラム内のイベントからの値
  • &fail - 単に失敗する
  • &features - このバージョンのIconの機能を識別する文字列を生成する
  • &file - 現在の実行ポイントのソースファイルの名前
  • &host - 実行中のIconが動作しているホストコンピュータを識別する文字列
  • &input - 標準入力ファイル
  • &interval - 前のイベントからの経過時間(ミリ秒)
  • &lcase - 26個の小文字アルファベットから構成される文字集合
  • &ldrag - 左ボタンのドラッグ
  • &letters - 52個の文字から構成される文字集合
  • &level - 手続き呼び出しのレベル
  • &line - 現在の実行ポイントのソース行番号
  • &lpress - 左ボタンの押下
  • &lrelease - 左ボタンのリリース
  • &main - main co-expression
  • &mdrag - 中央ボタンのドラッグ
  • &meta - 最後のXイベントでメタキーが押されていればnull、それ以外はfailure
  • &mpress - 中央ボタンの押下
  • &mrelease - 中央ボタンのリリース
  • &null - null値
  • &output - 標準出力ファイル
  • &phi - 黄金比
  • &pi - 円周率(円周の直径に対する比率)
  • &pos - 文字列スキャンの現在のフォーカスを含む変数
  • &progname - プログラム名を含む変数
  • &random - ランダム操作の現在のシードを含む変数
  • &rdrag - 右ボタンのドラッグ
  • &regions - 領域のサイズを生成する
  • &resize - ウィンドウのリサイズ
  • &row - テキスト行のマウスの垂直位置
  • &rpress - 右ボタンの押下
  • &rrelease - 右ボタンのリリース
  • &shift - 最後のXイベントでシフトキーが押されていればnull、それ以外はfailure
  • &source - 現在の共役を呼び出した共役
  • &storage - 各領域に使用されるストレージの量を生成する
  • &subject - 文字列スキャンの現在の対象を含む変数
  • &time - 実行時間(ミリ秒)
  • &trace - 手続きのトレースを制御する変数
  • &ucase - 26個の大文字アルファベットから構成される文字集合
  • &version - このバージョンのIconを識別する文字列
  • &window - 現在のグラフィックス描画コンテキストを含む変数
  • &x - マウスの水平位置
  • &y - マウスの垂直位置

リテラル

編集

Iconのリテラル( Literals )には、数値リテラル( Numeric literals )と引用符付きリテラル( Quotedliterals )の2つのカテゴリに分けられます。

数値リテラル

編集

数値リテラルは、さらに2つのカテゴリに分けられます:

整数リテラル

編集

整数リテラルには2つの形式があります。

数字リテラル

編集

数字リテラルは1つ以上の数字で構成されています。

数字リテラルの例
0, 1, -90, 876

基数リテラル

編集

基数リテラルでは、数字の基数を指定することができます。

基数リテラルの書式
数字リテラル 基数指定 数字指定
基数指定
r
R
数字リテラル
数字リテラルの値は基数を指定し、2から36までの範囲でなければなりません。
数字指定
数字指定は数字と文字のシーケンスで構成され、aは10を表し、bは11を表し、zまで続きます。
数字指定内の大文字と小文字は同等です。
数字指定内の文字は基数より小さい値を表さなければなりません。
基数リテラルの例
2r1011, 2R1111, 4r0320, 16rbadbeef, 36rZoo

実数リテラル

編集

実数リテラルには2つの形式があります:

10進数リテラル

編集
10進数リテラルの書式
数字リテラル . [ 数字リテラル ]
[ 数字リテラル ] . 数字リテラル

指数リテラル

編集
指数リテラルの書式
数字リテラル 指数指定 [ 符号 ] 数字リテラル
10進数リテラル 指数指定 [ 符号 ] 数字リテラル
指数指定
e
E
指数指定
+
-

引用符付きリテラル

編集

引用リテラルは2つのカテゴリに分けられます。

csetリテラル

編集

csetリテラルは、シングルクォート( ’ )で囲まれた文字列で構成されます。シングルクォートは、エスケープされていない限り、囲まれたクォート内に現れることはできません。エスケープシーケンスについては後述します。

文字列リテラル

編集

文字列リテラルは、ダブルクォート( ” )で囲まれた文字列で構成されます。ダブルクォートは、エスケープされていない限り、囲まれたクォート内に現れることはできません。

エスケープシーケンス

編集

エスケープシーケンスを使用することで、それ以外では不便または不可能な文字を文字列リテラルに含めることができます。エスケープシーケンスは、バックスラッシュに続く1つ以上の特殊な意味を持つ文字で構成されます。

エスケープシーケンスとそれに対応する文字は次の通りです。

エスケープシーケンス
エスケープシーケンス 文字
\b バックスペース
\d 削除
\e エスケープ
\f フォームフィード
\l ラインフィード
\n 改行
\r キャリッジリターン
\t 水平タブ
\v 垂直タブ
\' シングルクォート
\" ダブルクォート
\ddd 8進数コード
\xdd 16進数コード
^c 制御コード
  • ラインフィードと改行文字はASCIIでは同じです。両方が異なるコンピュータシステムの用語に適応するために含まれています。
  • \ddd のシーケンスは、dが8進数の0、1、...、7である文字に対応します。
  • \xdd のシーケンスは、dが16進数の0、1、...、a、...、fである文字に対応します。aやAなどの大文字と小文字の16進数の桁は同じです。望ましい8進数や16進数の数を指定するために、後続の文字がエスケープシーケンスの一部として解釈されないようにするために、十分な桁数のみが与えられれば十分です。たとえば、\43 はASCII文字 # を指定し、\xa は \x0a と等価です。
  • 制御コードシーケンス \^c は、ASCII文字の制御コード c に対応します。たとえば、\^A は制御コードAを表します。具体的には、\^c はcの下位5ビットに対応する文字を表します。
  • バックスラッシュの後に続く文字が前述のリストに含まれていない場合、バックスラッシュは無視されます。したがって、\a は文字 "a" を表します。

データ型

編集

Iconには次にような型があります。

データ型
型名 ニーモニック 説明
integer i 42, 2r1010, 16Rbadbeef 整数
real r 3.14, 1.23e45 実数
string s "Hello world!", "The Book" 文字列
cset c 'aeiou', '0123456789' 文字集合
file f &input, &output, &errout ファイル;open()の戻値として知られます。
null n &null &nullだけを取とり得る型
procedure p 手続き;戻値ではなく手続きそのもの
any type x 全ての型
list L list(3, -1), [-1, -1, -1] リスト
set S set() 集合
table T テーブル
record R 全てのレコード型
numeric N 数 = 整数または実数 (i or r)
co-expression C create Co-Expression
any structure type X 構造型 = レコード・リスト・集合またはテーブル (R, L, S, or T)
ニーモニックは、Iconのリファレンスマニュアルでも広く使われている型の表現です。

integer

編集

整数( i : integer ) は、数値( N : numeric )の一種で、多倍長整数でありメモリがある限り大きな値を表現できます。

procedure main()
   write(2 ^ 1024)
end
179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639474124377767893424865485276302219601246094119453082952085005768838150682342462881473913110540827237163350510684586298239947245938479716304835356329624224137216
21024

整数シーケンス

編集

整数シーケンス( integer sequence )は、型ではく演算子ですが、使い勝手は範囲型と理解して差し支えありません。

procedure main()
   s := [2, 3, 5, 7, 11]
   every i := 1 to *s do
      writes(s[i], " ")
   write()

   s := "Hello!"
   every i := 1 to *s do
      writes(s[i], " ")
   write()
end
2 3 5 7 11
H e l l o !
i := 1 to *s は、リストや文字列の要素の添字範囲を表すイディオムで[1]*sは要素数を返す前置演算子*を適用しています。

この例は、次のように書くことが出来ます。

procedure main()
   s := [2, 3, 5, 7, 11]
   every writes(!s, " ")
   write()

   s := "Hello!"
   every writes(!s, " ")
   write()
end
前置演算子!は、リストや文字列をジェネレータ化します。

実数( r : real ) は、数値( N : numeric )の一種で、IEEE 754の倍精度浮動小数点数( float64 : 8-octet )です。

procedure main()
   every i := 305 to 310 do
      write("10.0 ^ ", i, " = ", 10.0 ^ i)
end
10.0 ^ 305 = 1e+305
10.0 ^ 306 = 1e+306
10.0 ^ 307 = 1e+307
10.0 ^ 308 = 1e+308
10.0 ^ 309 = inf.0
10.0 ^ 310 = inf.0

string

編集

文字列( s : string )は、文字のシーケンスで、構造型( #any structure type )の一種です。

procedure main()
   s1 := "Hello"
   s2 := "World"

   infix(![s1, s2], !["||", "<<", "<<=", ">>", ">>=", "==", "~=="], ![s1, s2])
end

procedure infix(s1, opr, s2)
   write(image(s1), " ", opr," ", image(s2), " = ", image(opr(s1, s2)))
end
"Hello" || "Hello" = "HelloHello"
"Hello" || "World" = "HelloWorld"
"Hello" << "World" = "World"
"Hello" <<= "Hello" = "Hello"
"Hello" <<= "World" = "World"
"Hello" >>= "Hello" = "Hello"
"Hello" == "Hello" = "Hello"
"Hello" ~== "World" = "World"
"World" || "Hello" = "WorldHello"
"World" || "World" = "WorldWorld"
"World" <<= "World" = "World"
"World" >> "Hello" = "Hello"
"World" >>= "Hello" = "Hello"
"World" >>= "World" = "World"
"World" == "World" = "World"
"World" ~== "Hello" = "Hello"
条件式を引数にした関数は、条件が不成立だった場合(失敗した場合)関数そのものがなかったことにされます。

文字集合( c : cset)は、文字の集合で順序はなく、構造型( #any structure type )の一種です。

procedure main()
   digit := '0123456789'
   alphabet := 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'

   infix(![digit,alphabet], !["**", "++", "--", "--"], ![digit,alphabet])
   prefix("~", ![digit,alphabet])
end

procedure infix(s1, opr, s2)
   write(image(s1), " ", opr," ", image(s2), " = ", image(opr(s1, s2)))
end

procedure prefix(opr, s1)
   write(opr," ", image(s1), " = ", image(opr(s1)))
end
&digits ** &digits = &digits
&digits ** &ucase = ''
&digits ++ &digits = &digits
&digits ++ &ucase = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
&digits -- &digits = ''
&digits -- &ucase = &digits
&digits -- &digits = ''
&digits -- &ucase = &digits
&ucase ** &digits = ''
&ucase ** &ucase = &ucase
&ucase ++ &digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
&ucase ++ &ucase = &ucase
&ucase -- &digits = &ucase
&ucase -- &ucase = ''
&ucase -- &digits = &ucase
&ucase -- &ucase = ''
~ &digits = '\x00\x01\x02\x03\x04\x05\x06\x07\b\t\n\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\e\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\d\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff'
~ &ucase = '\x00\x01\x02\x03\x04\x05\x06\x07\b\t\n\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\e\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./0123456789:;<=>?@[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\d\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff'

ファイル( f : file)

procedure main()
   fn := "/etc/hosts"
   f := open(fn) | stop("cannot open " || fn)
   write(image(f), ": type = ", type(f))
   write()
   while write(read(f))
end
file(/etc/hosts): type = file

127.0.0.1	localhost
::1localhost ip6-localhost ip6-loopback
fe00::0	ip6-localnet
ff00::0	ip6-mcastprefix
ff02::1	ip6-allnodes
ff02::2	ip6-allrouters

Null( n : null )は、&null だけを取り得る型です。

procedure main()
   x := &null
   write(image(x), ": type = ", type(x))
 
   ary := list(3)
   x := ary[2]
   write(image(x), ": type = ", type(x))
end
&null: type = null
&null: type = null
キーワード&nullのほか、未初期化のリストの要素も値として&nullを保持しているように見えます。

procedure

編集

手続き( p : procedeure )

procedure main()
   every func := ![add2, sub2, mul2, div2, mod2] do
      write(image(func), ": type = ", type(func), "; func(17, 7) = ", func(17, 7))
end

procedure add2(a, b)
   return a + b
end

procedure sub2(a, b)
   return a / b
end

procedure mul2(a, b)
   return a * b
end

procedure div2(a, b)
   return a / b
end

procedure mod2(a, b)
   return a % b
end
procedure add2: type = procedure; func(17, 7) = 24
procedure sub2: type = procedure; func(17, 7) = 2
procedure mul2: type = procedure; func(17, 7) = 119
procedure div2: type = procedure; func(17, 7) = 2
procedure mod2: type = procedure; func(17, 7) = 3

any type

編集

全ての型( x : any type)

リスト( L : list )

procedure main()
   ary := [2,3,5,7,11]
   write(inspect(ary));
   
   ary := list(10, 1)
   write(inspect(ary));
   
   ary := list(10)
   write(inspect(ary));
   
   every i := 1 to *ary do ary[i] := i
   write(inspect(ary));
   
   mat := make_matrix(4, 0.0);
   write(inspect(mat))
end

procedure inspect(arg)
   result := "[ "
   every  elt := !arg do 
      if type(elt) == "list" then
         result ||:= inspect(elt) || ", "
      else 
         result ||:= image(elt) || ", "
   return result || "]"
end

procedure make_matrix(n, x)
   L := list(n)
   every !L := list(n, x)
   return L
end
[ 2, 3, 5, 7, 11, ]
[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ]
[ &null, &null, &null, &null, &null, &null, &null, &null, &null, &null, ]
[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ]
[ [ 0.0, 0.0, 0.0, 0.0, ], [ 0.0, 0.0, 0.0, 0.0, ], [ 0.0, 0.0, 0.0, 0.0, ], [ 0.0, 0.0, 0.0, 0.0, ], ]

まず、mainという手続きが定義されています。これはプログラムのエントリーポイントとなる部分です。

最初の行では、aryという変数に整数の配列 [2, 3, 5, 7, 11] を代入しています。そして、inspect(ary)を呼び出して、aryの内容を表示しています。inspectは後述します。

次に、aryに別の値を代入し、再びinspect(ary)を呼び出してその内容を表示しています。これは、list(n, x)を使用して長さがnで、全ての要素がxであるリストを生成しています。

さらに、aryに別の値を代入し、再びinspect(ary)を呼び出してその内容を表示しています。ここでは、list(n)を使用して長さがnで、全ての要素が初期値であるリストを生成しています。

次の行では、everyループを使用して、iを1からaryの要素数まで繰り返します。ループの中で、ary[i]iの値を代入しています。このループにより、aryの各要素がインデックスの値になるようになります。

最後に、make_matrix(n, x)を呼び出して4x4の行列(要素が全て0.0)を生成し、その内容をinspectを用いて表示しています。

次に、inspectという手続きが定義されています。

inspectは、引数として渡されたオブジェクトを文字列形式で表示するための手続きです。

この手続きでは、resultという変数を初期化し、"[ "という文字列を代入しています。そして、everyループを使用してargの要素に順番にアクセスします。

ループ内では、各要素eltの型がlistであるかどうかをチェックしています。もしeltがリストであれば、再帰的にinspect(elt)を呼び出し、結果の文字列をresultに追加します。

それ以外の場合、eltの値を文字列に変換してresultに追加します。image(elt)はオブジェクトを文字列に変換する組み込み関数です。

最後に、result"]"を追加して、結果の文字列を返します。

最後に、make_matrixという手続きが定義されています。

make_matrixは、指定されたサイズnと初期値xで要素が全てxである行列を生成するための手続きです。

まず、長さがnのリストLを生成し、それぞれの要素を初期値xで初期化します。

この手続きでは、everyループを使用して、Lの各要素に対してlist(n, x)を適用し、要素をリストに置き換えています。

最後に、生成された行列Lを返します。

集合( S : set)

procedure main()
   ary := [2,3,5,7,11]
   every writes(image(!set(ary)) || ", ");
   write()
   
   ary := list(10, 1)
   every writes(image(!set(ary)) || ", ");
   write()
   
   ary := list(10)
   every writes(image(!set(ary)) || ", ");
   write()
   
   every i := 1 to *ary do ary[i] := i
   every writes(image(!set(ary)) || ", ");
   write()
end
5, 3, 2, 7, 11, 
1, 
&null,  
5, 10, 4, 9, 3, 8, 2, 7, 1, 6,

[TODO: insert(), delete(), S1 ** S2, S1 ++ S2, S1 -- S2]

テーブル( T : table)は、キーと対応する値のペアからなる順序付けされない構造型( #any structure type )の一種です。 これらのペアは要素と呼ばれます。 キーは一意であり、値にアクセスするための索引として機能します。 テーブルは、データの保持、検索、更新などの操作を効率的に行うために使用されます。 Iconのテーブルは、他のプログラミング言語の連想配列やマップに似た機能を提供します。しかし、Iconのテーブルはより柔軟であり、キーと値のペアのコレクションとして表現されます。キーは任意のデータ型であることができます(整数や文字列など)。さらに、テーブル内の要素の順序は重要ではありません。

Iconのテーブルは、次のような特徴を持っています:

  • 要素の追加と削除: テーブルに新しいキーと値のペアを追加することができます。また、キーに対応する要素を削除することもできます。
  • 値の参照と変更: テーブル内の特定のキーに対応する値にアクセスすることができます。また、既存のキーに新しい値を割り当てることで値を変更することもできます。
  • 検索と存在確認: テーブル内に特定のキーが存在するかどうかを確認することができます。存在する場合は、そのキーに対応する値を取得できます。
  • サイズの管理: テーブル内の要素の数を管理することができます。要素の追加や削除によってテーブルのサイズが変化します。

テーブルは、データのグループ化や関連データの管理、高速な検索や更新など、さまざまな場面で有用です。Iconのテーブルは、高水準の抽象化と操作の容易さを提供し、プログラミングタスクを効率的に解決するための強力なツールとなっています。

procedure main()
   tbl := table(0)
   tbl["a"] := 1
   tbl["b"] := 2
   tbl["c"] := 3
   every x := key(tbl) do
      write(image(x), ":", image(tbl[x]))
end
"c":3
"a":1
"b":2

record

編集

レコード( R : recode )は、複数のフィールド(属性)から成るデータ構造です。レコードは固定サイズであり、各フィールドは名前で参照されます。Iconのレコードは、他のプログラミング言語における構造体やレコード型に類似しています。

レコードはグローバルなスコープを持ち、プログラム全体で利用可能です。レコードの宣言は手続きの内部ではなく、プログラムのトップレベルで行われます。

レコードの宣言は以下のような形式を取ります:

record レコード名(フィールド1, フィールド2, ..., フィールドn)

ここで、レコード名はレコードの識別子であり、フィールド1からフィールドnまでは各フィールドの名前です。フィールド名は識別子のルールに従う必要があります。

レコードはレコードコンストラクタ関数を使用してインスタンス化されます。レコードコンストラクタ関数はレコード名に対応し、各フィールドに値を指定して呼び出されます。

レコードのフィールドは、ドット演算子を使用して参照および代入されます。例えば、レコード名.フィールド名の形式でフィールドにアクセスできます。

Iconのレコードは静的なデータ構造であり、フィールドの値は変更可能です。また、フィールドへのランダムなアクセスやフィールドの反復処理もサポートされています。

record point(x, y)

procedure main()
   pt := point(3, 4)
   write("type(pt) = ", type(pt))
   write("pt.x = ", pt.x)
   write("pt.y = ", pt.y)
end
type(pt) = point
pt.x = 3
pt.y = 4

numeric

編集

数( N : numeric)は、整数( integer )または実数( real )です。

co-expression

編集

Co Expression( C : co-expression )

any structure type

編集

構造型( X : any structure type )は、レコード・リスト・集合またはテーブル (R, L, S, or T)のいずれかです。

前処理

編集

すべてのIconのソースコードは、コンパイル前に前処理( Preprocessing )を通過します。プリプロセッサディレクティブは、プリプロセッサの動作を制御し、Iconコンパイラには渡されません。

プリプロセッサディレクティブの一般的な形式は次の通りです。

$ディレクティブ 引数 # コメント

$define

編集

まず、定数の定義が可能です。$defineディレクティブを使用して、シンボルを定義し、それに値を与えることができます。定義されたシンボルは、コンパイル前にその値に置き換えられます。

$define START 0
$define END 12
$define STEP 3

procedure main()
   every writes(START to END by STEP, " ")
end
0 3 6 9 12
$define は、Cの #define と似ていますが、関数型のマクロの定義・展開には対応していません。

$undef

編集

定義した定数の定義を取り消すことが出来ます。

$undef START

$include

編集

また、他のファイルをプログラムに挿入することもできます。$includeディレクティブを使用すると、指定したファイルの内容がその場所に挿入されます。

$include "example.icn"

$ifdef

編集

さらに、定義された定数に応じてコードの含有または排除が可能です。定数の値に基づいて、特定のコードブロックを実行するかスキップするかを決定することができます。

$ifdef name 
    name が定義されていると実行される
$else
    name が定義されていないと実行される
$endif

前処理で、エラーや警告に使われる行番号(とファイル名)を変更することが出来ます。 この機能は、トランスパイルによって生成された Icon のコードの診断に使われることを意図しています。

$line line-number [ filename ]

式と演算子

編集

Iconは、構文は比較的小さなセットですが、式と演算子は非常に多種多様です。

様々な式
  • 0 や "Hello world!” のようなリテラル
  • write("Hello world!”) のような関数呼び出し
  • i や j のような識別子
  • -i や *str のような単項演算子式
  • 1 + 1 や 2 ^ 8 のような二項演算子
  • if i < j the i else j のような制御構造
  • obj.x のようなフィールド参照
  • 1 to 10 by 2 のような to-by
  • ary[3] のような要素参照
  • ary[1:8] のようなスライス
  • &fail のようなキーワード
  • ( 2 + 3 ) のようなグループ化
  • { 式 } のようなブロック

これらは全て式で、式でないものはプロシージャ定義・レコード定義や変数の宣言など少数派です。

算術演算子

編集

Iconには、以下の算術演算子が用意されています。

算術演算子
演算子 操作 優先度 結合 説明
+ 加算 1 左から右 N1 + N2 N1N2 の和
- 減算 1 左から右 N1 - N2 N1N2 の差
* 乗算 2 左から右 N1 * N2 N1N2 の積
/ 除算 2 左から右 N1 / N2 N1N2 の商
% 剰除 2 左から右 N1 / N2 N1N2 の除算の剰り
^ 累乗 3 右から左 N1 ^ N2 N1N2

評価は、優先度が高い演算子から行われます。

1 + 2 * 3

は、乗算のほうが優先度が高いので

1 + ( 2 * 3 )

の順で評価されます。

優先度が同じ演算子は、結合方向に従って評価されます。

1 - 2 + 3

の結合は、左から右なので

( 1 - 2 ) + 3

の順で評価されます。

また、

2 ^ 3 ^ 4

の場合、累乗は右から左に結合するので

2 ^ ( 3 ^ 4 )

の順で評価されます。

数値比較演算子

編集
数値比較演算子
意味
N1 < N2 N1N2 未満
N1 <= N2 N1N2 以下
N1 > N2 N1N2 より大きい
N1 >= N2 N1N2 以上
N1 = N2 N1N2 と等しい
N1 ~= N2 N1N2 と等しくない

関数

編集

関数の引数は左から右に評価されます。もし引数の評価が失敗した場合、関数は呼び出されません。 一部の関数は、与えられた引数の集合に対して複数の結果を生成する場合があります。 もし引数が複数の値を生成する場合、関数は異なる引数値で繰り返し呼び出されることがあります。

[TODO:]

演算子の関数的呼び出し

編集

演算子は、通常は式の中で使われますが、関数的に呼び出すことも出来ます。

例えば

3 + 4

"+"(3, 4)

演算子を表す文字列 ( 引数リスト )

の形式になります。

procedure main()
   write("3 * 4 = ", "*"(3, 4))
   write("- 127 = ", "-"(127))
   
   a := 42
   b := 73
   prefix("-", a)
   infix(![a, b], !["+", "-", "*", "/", "%"], ![a, b])
end

procedure infix(s1, opr, s2)
   write(image(s1), " ", opr," ", image(s2), " = ", image(opr(s1, s2)))
end

procedure prefix(opr, s1)
   write(opr," ", image(s1), " = ", image(opr(s1)))
end
3 * 4 = 12
- 127 = -127
- 42 = -42
42 + 42 = 84
42 + 73 = 115
42 - 42 = 0
42 - 73 = -31
42 * 42 = 1764
42 * 73 = 3066
42 / 42 = 1
42 / 73 = 0
42 % 42 = 0
42 % 73 = 42
73 + 42 = 115
73 + 73 = 146
73 - 42 = 31
73 - 73 = 0
73 * 42 = 3066
73 * 73 = 5329
73 / 42 = 1
73 / 73 = 1
73 % 42 = 31
73 % 73 = 0

演算子の優先度と結合性

編集

下の表は、演算子を優先度順にグループ化し、結合方向も示しました。

演算子の優先度と結合性
演算子 結合性
( expr )                   # 括弧
{ expr1; expr2;  }       # 複式
[ expr1, expr2,  ]       # リスト
expr . f                   # フィールド参照
expr1 [ expr2, expr3,  ] # 添字
expr1 [ expr2 : expr3 ]    # ストライプ
expr1 [ expr2 +: expr3 ]   # ストライプ
expr1 [ expr2 : expr3 ]   # ストライプ
expr ( expr1, expr2,  )  # 呼び出し
expr { expr1, expr2,  }  # 呼び出し
左から右
not expr                   # 成功失敗の反転
| expr                     # 反復評価
! expr                     # ジェネレート
 expr                     # サイズ
+ expr                     # 単項プラス
 expr                     # 単項マイナス
. expr                     # デリファレンス
/ expr                     # null 検査
\ expr                     # 非 null 検査
= expr                     # 文字列一致
? expr                     # 乱数生成
~ expr                     # コンプリメント
@ expr                     # co-expression をアクティブ化
^ expr                     # co-expression を再生成
左から右
expr1 \ expr2              # expr1 の生成数を expr2 で制限
expr1 @ expr2              # expr2をアクティブ化しexpr1に伝搬
expr1 ! expr2              # expr1 に expr2 を引数として呼び出し
左から右
累乗
expr1 ^ expr2              # expr1 の expr2 乗
右から左
expr1  expr2               # 積
expr1 / expr2               # 商
expr1 % expr2               # 剰
expr1 ∗∗ expr2              # 積集合
左から右
左から右
expr1 || expr2             # 文字列連結
expr1 ||| expr2            # リスト連結
左から右
比較
expr1 < expr2
expr1 <= expr2
expr1 = expr2
expr1 >= expr2
expr1 > expr2
expr1 ~= expr2
expr1 << expr2
expr1 <<= expr2
expr1 == expr2
expr1 >>= expr2
expr1 >> expr2
expr1 ~== expr2
expr1 === expr2
expr1 ~=== expr2
左から右
expr1 | expr2
左から右
数列生成
expr1 to expr2 by expr3
左から右
代入系
expr1 := expr2
expr1 < expr2
expr1 :=: expr2
expr1 <> expr2
expr1 op:= expr2
右から左
expr1 ? expr2
左から右
expr1 & expr2
左から右
break expr
case expr of { expr1 : expr2; expr3 : expr4;  }
create expr
every expr1 do expr2
fail
if expr1 then expr2 else expr3
next
repeat expr
return expr
suspend expr1 do expr2
until expr1 do expr2
while expr1 do expr2
左から右

制御構造

編集

Iconには、以下の制御構造があります。

  • if ... then ... else:条件分岐
  • case of:多方向分岐
  • until:条件が偽になるまで、処理を繰り返す
  • while:条件が真である限り、処理を繰り返す
  • repeat:条件が真である限り、処理を繰り返す
  • every:ジェネレータに対して、順次処理を実行する
  • fail:強制的な失敗

ゴール指向の評価

編集

Iconプログラミング言語における「Goal-directed evaluation(ゴール指向の評価)」は、条件式の成功や失敗を通じて制御構造を駆動する評価メカニズムの一部です。

Iconでは、条件式は成功すると結果を生成し、失敗すると結果を生成しません。例えば、比較演算子 x > y は、xyよりも大きい場合に成功しyの値を返します。それ以外の場合は失敗します。

また、「x > y > z」は、yの値がxzの間にある場合に成功し、zの値を返します。

このようにIconでは論理演算ではなく、成功すると値を返し、失敗すると失敗が上位に伝搬する仕組みを基本としています。

以下は、Iconの条件式を使用した例です。

procedure max(a, b)
   if a > b then
      return a           # a > b が成功したら、こちらを実行
   else       
      return b           # a > b が失敗すると、こちらを実行
end

procedure main()
    write(max(5, 10))    # 出力: 10
    write(max(8, 3))     # 出力: 8
end

この例では、max 関数は、2つの引数 ab のうち、大きい方の値を返します。 条件式 a > b が成功する場合は、 a を返し、失敗する場合は b を返します。

if-then-else

編集

Iconのifは以下のような構文を持ちます。

if 条件式 then 式A else 式B

式A条件式が成功した場合に実行され、式A条件が失敗した場合に実行されます。

number := 6
q := 3

if number < q then
	write("数値 ", number, " は、",q ,"未満です。")
else
	write("数値 ", number, " は、",q ,"以上です。")
実行結果
数値 6 3以上です
else を含んだ例です。
number := 6

if number < 10 then
	write("数値 ", number, " は、10未満です。")
実行結果
数値 6 10未満です
[else 式B]は省略できます。
number := 6
p := 3
q := 7

if number < p then
	write("数値 ", number, " は、",p ,"未満です。")
else if number < q then
	write("数値 ", number, " は、",q ,"未満です。")
else
	write("数値 ", number, " は、",q ,"以上です。")
実行結果
数値 6 7未満です
else if で複数の条件で分岐できます。
これは最初のelseに次のifが結合した結果です。

case of

編集

Iconのcase of文は、複数の値に対して条件分岐を行うために使用されます。

以下は、case of文の基本的な構文です。

case <value> of {
    <pattern1> : <expression1>
    <pattern2> : <expression2>
    <pattern3> : <expression3>
  ...
    default : <default_expression>
}
<value>: 条件分岐の対象となる値。
<pattern>: <value>と比較されるパターン。:の左側には、<pattern>がマッチした場合に評価される式が書かれます。
<expression>: <pattern>がマッチした場合に評価される式。
default: どのパターンにもマッチしない場合に評価される式。必ず最後に書かれます。

以下は、具体的な例です。

procedure print_weekday(day)
  case day of {
    "Monday" : write("This is Monday.")
    "Tuesday" : write("This is Tuesday.")
    "Wednesday" : write("This is Wednesday.")
    "Thursday" : write("This is Thursday.")
    "Friday" : write("This is Friday.")
    "Saturday" : write("This is Saturday.")
    "Sunday" : write("This is Sunday.")
    default : write("Unknown day.")
  }
end
この例では、dayに渡された文字列が曜日の名前と一致するかどうかを調べ、一致する場合にはその曜日を表示します。
一致しない場合には、「Unknown day.」と表示します。最後の_は、どの曜日の名前にも一致しなかった場合に評価される式を指定しています。

Iconにおけるwhileは、条件が満たされている限り、繰り返し実行する制御構造です。Iconでは真偽値(Boolean)ではなく、式の成功または失敗で条件を評価します。

procedure main()
   local i
   i := 0
   while i < 10 do {
      writes(i, " ")
      i +:= 1
   }
   write()
end
0 1 2 3 4 5 6 7 8 9

Iconのuntil文は、条件が失敗している限り繰り返し処理を繰り返し、条件が成功するとループが終了します。

procedure main()
   local i
   i := 0
   until i >= 10 do {
      writes(i, " ")
      i +:= 1
   }
   write()
end
0 1 2 3 4 5 6 7 8 9

repeat

編集

Iconのrepeatは、続く式を永久に繰り返します。

procedure main()
   local i
   i := 0
   repeat {
      writes(i, " ")
      i +:= 1
      if i >= 10 then break
   }
   write()
end
0 1 2 3 4 5 6 7 8 9

この例では[if i >= 10 then break]で脱出しています。

& と |

編集

&は二項の論理積演算子です。

a & b
a が失敗するとその場で失敗します(everyで修飾されない限りbは評価されません)
a が成功した場合、bが評価され成功したならbが式の値となり、失敗したなら失敗します。

やや禅問答のようですが、比較演算子と基本的には同じです。

|は二項の論理和演算子です。

a | b
a が成功するとaが式の値となります(everyで修飾されない限りbは評価されません)
a が失敗した場合、bが評価され成功したならbが式の値となり、失敗したなら失敗します。

&|の使用例

procedure main()
   local ors, ands, mix, mix2, mix3

   ors := (1 | 9 | 3 | "abc" )
   ands := (1 & 9 & 3 & "abc" )
   mix := (1 | 9 & 3 )
   mix2 := ((1 | 9) & 3 )
   mix3 := (1 | (9 & 3) )

   if x := ors then write(x)
   if x := ands then write(x)
   if x := mix then write(x)
   if x := mix2 then write(x)
   if x := mix3 then write(x)
   write()
   write(ors)
   write(ands)
   write(mix)
   write(mix2)
   write(mix3)
end
1
abc
3
3
1

1
abc
3
3
1
  • if 変数 := 式 then 関数( 変数 )のように、if では条件式で代入を行うことが可能です。
  • この形式は関数( 式 )と等価で、式が「失敗」すると関数は実行されません。
+これは、論理演算子式だけではなく、式一般に言え、例えばfind( pattern, string )のように string から pattern を含む文字列を返す関数でも、 pattern が見つからなければ「失敗」し、find() を引数に含む関数は実行されません。このような仕組みをゴール指向の評価と呼び、ゴール指向の評価を多用するIconでは、while/forは余り使う必要がなく、次に説明するeveryが反復の主役になります。

大概のプログラミング言語で反復構造の解説のトリを務めるのは、forあるいはforeachですが、Iconにはforforeachもなく、everyを使います。と言ってもeveryは単なる制御構造ではなく、パターンマッチングに基づくより高度な制御を実現することができます。

前述の while untilrepeat の例と等価なループをeveryを書いてみましょう。

procedure main()
   local i
   every i := 0 to 9 do
      writes(i, " ")
   write()
end
0 1 2 3 4 5 6 7 8 9

0 to 9 は、to-by式を使ったジェネレータです。 everyはジェネレータから値を1つずつ取り出して変数iに代入し、その値を画面に出力するという操作を行います。

さらに、このようにも書けます。

procedure main()
   every writes(0 to 9, " ")
   write()
end

他の他のプログラミング言語には余り見ることが出来ない構文ですが、これで writes()0 to 9 の回数だけ繰り返されます。

to-by式は、0 to 9 by 2 のように省略可能なby 整数を使い増分を指定できます(省略された場合は 1 が仮定されます)。

procedure main()
   every writes(0 to 9 by 2, " ")
   write()

   every writes(9 to 0 by -1, " ")
   write()
end
0 2 4 6 8 
9 8 7 6 5 4 3 2 1 0

breakは、repeatで示したとおりループを中断して脱出します。

また、ループも式であり値を持ちますが、break に値を渡すとループの式の値になります。

procedure main()
   local i, x
   i := 0
   x := repeat {
      writes(i, " ")
      i +:= 1
      if i >= 10 then break 123
   }
   write()
   write("x = ", image(x))
   
   x := 111
   i := 0
   x := while i < 10 do {
      writes(i, " ")
      i +:= 1
      if i >= 100 then break 99
   }
   write()
   write("x = ", image(x))
end
0 1 2 3 4 5 6 7 8 9 
x = 123
0 1 2 3 4 5 6 7 8 9 
x = 111

nextは、ループ内で使用される制御文の1つです。nextは、現在のループイテレーションをスキップして、次のイテレーションに進むために使用されます。

nextは通常、条件式を使用して特定の条件が満たされた場合にループイテレーションをスキップするために使用されます。

procedure main()
   every i := 1 to 10 do {
      if i % 2 = 0 then
         next
      writes(i, " ")
   }
end
1 3 5 7 9

上記の例では、1から10までの数値を反復処理するループがあります。if i % 2 = 0 then nextの行によって、数値が偶数の場合にはnextが実行され、ループイテレーションがスキップされます。したがって、奇数の数値のみが出力されます。

nextは、whileuntilrepeateveryなど、さまざまなループ構造で使用できます。また、 ネストされたループ内での使用も可能です。

return

編集

return は、手続きの戻り値を返すことと、手続きを終了し呼び出し元の手続きに変えるという2つの機能があります。

procedure fib0(n) 
   if n < 2 then 
      return n 
   else
      return fib(n - 1) + fib(n - 2);
end

procedure fib1(n) 
   if n < 2 then 
      return n 
   return fib(n - 1) + fib(n - 2);
end

procedure fib2(n)
   local result
   
   if n < 2 then 
      result := n 
   else
      result := fib(n - 1) + fib(n - 2);
   return result
end

procedure fib3(n) 
   return if n < 2 then n else fib(n - 1) + fib(n - 2)
end

procedure fib(n)
   return 2 > n | fib(n - 1) + fib(n - 2)
end

procedure main()
   every writes(fib(1 to 10), " ")
end
1 1 2 3 5 8 13 21 34 55

これはフィボナッチ数列を計算するためのいくつかの異なるプロシージャ(手続き)の実装です。フィボナッチ数列は、最初の2つの数字が0と1であり、それ以降の数は前の2つの数の和である数列です。

各プロシージャは引数として整数 n を受け取り、フィボナッチ数列の n 番目の値を返します。

以下に各プロシージャの解説を示します:

  1. fib0 プロシージャ: このプロシージャは最も基本的な形式で、条件分岐を用いて再帰的にフィボナッチ数列を計算しています。n が2未満の場合は n を返し、それ以外の場合は n - 1 番目と n - 2 番目のフィボナッチ数列の和を返します。
  2. fib1 プロシージャ: fib0 プロシージャと同様に、条件分岐を用いて再帰的にフィボナッチ数列を計算しています。異なる点は、if 文の else 節が省略されており、その場合には return 文だけが続くということです。
  3. fib2 プロシージャ: このプロシージャは一時変数 result を導入しています。条件分岐によって再帰的にフィボナッチ数列を計算し、結果を result に代入します。最後に result を返します。
  4. fib3 プロシージャ: fib1 プロシージャと同様に、条件分岐を用いて再帰的にフィボナッチ数列を計算しています。異なる点は、return 文が式として直接使用されていることです。条件式が真の場合には n を返し、偽の場合には fib(n - 1) + fib(n - 2) の結果を返します。
  5. fib プロシージャ: このプロシージャでは、再帰呼び出しを利用してフィボナッチ数列を計算します。条件式 2 > n が真の場合は n を返し、偽の場合は fib(n - 1) + fib(n - 2) を返します。
  6. main プロシージャ: fib プロシージャを呼び出して、1から10までのフィボナッチ数列を順番に出力しています。

これらのプロシージャは、異なる構文の使用や一時変数の導入など、同じ目的のためにいくつかの異なる方法でフィボナッチ数列を計算しています。 どのプロシージャを使用しても、正しいフィボナッチ数列の値が得られるはずです。

suspend

編集

サスペンド( suspend )は、ジェネレータプロシージャで使います。

素数を返す関数を考えてみます。

procedure get_primes(n)
   local primes
   primes := []
   every i := 2 to n do {
      flag := 1 
      every j := 2 to *primes do {
         if i % primes[j] = 0 then {
            flag := 0
            break j
         }
       }
       if flag ~= 0 then
          put(primes, i)
   }
   return primes
end

procedure main()
   # 結果を表示します。
   every writes(!get_primes(100), " ")
   write()
end
2 3 4 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

この関数をジェネレータに書き換えてみます。

procedure gen_primes()
   local primes
   primes := []
   every i := seq(2) do {
      flag := 1 
      every j := 2 to *primes do {
         if i % primes[j] = 0 then {
            flag := 0
            break j
         }
       }
       if flag ~= 0 then {
          put(primes, i)
          suspend i
       }
   }
end

procedure main()
   # 結果を表示します。
   every writes(gen_primes() \ 100, " ")
   write()
end
2 3 4 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 211 223 227 229 233 239 241 251 257 263 269 271 277 281 283 293 307 311 313 317 331 337 347 349 353 359 367 373 379 383 389 397 401 409 419 421 431 433 439 443 449 457 461 463 467 479 487 491 499 503 509 521 523
まず「100までの素数」から「最初の100個の素数」に変えたので結果は異なります。
every i := seq(2) doseq(2)が目を引きますが、2 to ∞と同じ意味で、これ自体がジェネレータです。
suspend iがここでの核心で、定義している手続きはここまで来ると return のように呼び出し元に返りますが、値を返すのではなくジェネレターを介し、返されたジェネレターは suspend の引数を返します。
every writes(gen_primes() \ 100, " ")が定義したジェネレータの呼び出しでgen_primes() \ 100は生成を100個で打ち切っています。もし打ち切らないと無限に素数を生成してしまいます。
もし100までの素数で打ち切りたい場合は
   every i := gen_primes() do
      if i > 100 then
         break
      else
         writes(i, " ")
   write()
とします。

ジェネレータ

編集

Iconでは、式や関数は単一の値を返すだけでなく、呼び出されるたびに新しい値を返すことができます。これらはジェネレータと呼ばれ、Icon言語の重要な部分です。

ジェネレータは、式や関数の評価によって生成される"結果シーケンス"を返します。結果シーケンスには、式や関数によって生成できるすべての可能な値が含まれます。結果シーケンスが使い切られると、式や関数は失敗します。

Iconでは、どの手続きでも単一の値または複数の値を返すことができます。制御は、予約語 fail、returnおよびsuspendを使用して行います。これらの予約語がない手続きは、実行が手続きの終わりに到達するとキーワード&failを返します。

ジェネレータに変換するには、予約語suspendを使用します。これは、「この値を返し、再度呼び出されたときにこの点から実行を開始する」という意味で、C言語のstaticの概念とreturnの組み合わせのようなものです。ジェネレータを作成するには、whileループの中にsuspendステートメントを配置します。このように、数値のシーケンスを簡単に作成できます。

また、Iconには、オルタネーターと呼ばれる、ブール演算子のような機能もあります。これを使うと、条件分岐をシンプルに記述することができます。

オルタネータ

編集

Iconのオルタネータは、論理和(OR)演算子と同様に機能するもので、複数の値のリストを評価することができます。例えば、以下のように書くことができます。

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

この式は「もし y が x または 5 よりも小さければ ...」と読めますが、実際には、オルタネータは値をリストから取り出して、式を評価し、条件を満たす場合はその値を返します。この場合、最初に y < x を評価し、x が y よりも大きければ、その値を返して y の値を出力します。x が y よりも小さければ、次に y < 5 を評価します。もし y が x または 5 よりも小さければ、y の値を出力します。しかし、y が x および 5 よりも大きい場合、オルタネータは評価する値がなくなり、条件式が失敗します。その場合、if 文は失敗し、write 文は実行されません。

オルタネータは、Pythonのジェネレータ式のように、リストやシーケンスの要素を評価するための手軽な方法として使うことができます。また、オルタネータは、関数の引数を評価する前に、必ず成功するようにしてくれるため、簡潔なコードを書くことができます。例えば、以下のようなコードがあります。

write("y=", (x | 5) > y)

このコードは、先ほどの例と同じ結果を得ることができます。オルタネータを使うことで、条件を満たす値が存在する場合に限り、評価されるため、無駄な処理を避けることができます。

脚註

編集
  1. ^ Chapelならば、.domainを使うところです。

附録

編集

コードギャラリー

編集

エラトステネスの篩

編集

エラトステネスの篩を、若干 Icon らしく書いてみました。

エラトステネスの篩
# `eratosthenes` は、「エラトステネスの篩」を使用して、2 から `n` までの素数のリストを返します。
procedure eratosthenes(n)
   local sieve, i, result
   # `sieve` は、2 から `n` までのすべての素数候補を表しています。
   # 素数候補を全て暫定的に1とします。
   sieve := list(n, 1)

   every i := 2 to n do 
      if \ sieve[i] then
         every sieve[i * i to n by i] := &null

   # sieve の &null ではない要素の添字をリストで返す
   result := list()
   every i := 2 to *sieve do
      if \ sieve[i] then
         put(result, i)
   return result
end

procedure main()
   # 結果を表示します。
   every writes(!eratosthenes(100), " ")
   write()
end
実行結果
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

最大公約数と最小公倍数

編集

最大公約数と最小公倍数を、若干 Icon らしく書いてみました。

最大公約数と最小公倍数
# gcd2は2つの整数mとnの最大公約数を計算します。
# n == 0の場合はmを返し、それ以外の場合はユークリッドの互除法に基づいて最大公約数を計算します。
procedure gcd2(m, n)
   return if n == 0 then m else gcd2(n, m % n)
end

# gcdは任意の長さの整数の配列の最大公約数を計算します。
procedure gcd(args)
   local result
   result := args[1]
   every result := gcd2(result, args[2 to *args])
   return result
end

# lcm2は2つの整数mとnの最小公倍数を計算します。
procedure lcm2(m, n)
   return m * n / gcd2(m, n)
end

# lcmは任意の長さの整数の配列の最小公倍数を計算します。
procedure lcm(args)
   local result
   result := args[1]
   every result := lcm2(result, args[2 to *args])
   return result
end

# gcd2、gcd、lcm2、lcmの各関数を呼び出します。
procedure main()
   write("gcd(32, 48) => ", gcd2(32, 48))
   write("gcd(30, 72, 12) => ", gcd([30, 72, 12]));
   write("lcm2(30, 72) => ", lcm2(30, 72));
   write("lcm(30, 42, 72) => ", lcm([30, 42, 72]));
end
実行結果
gcd2(30, 45) => 15
gcd(30, 72, 12) => 6
lcm2(30, 72) => 360
lcm(30, 42, 72) => 2520
※ 他のプログラミング言語の例では、可変長引数を使っていますが、Iconではリストを渡しました。

二分法

編集

二分法を、若干 Icon らしく書いてみました。

二分法
# bisection は、関数 f が 0 に等しくなる実数 x を二分法で検索します。
# low と high は、関数 f の値が 0 より小さい範囲の下限と上限です。
# x は、現在の中点です。
# fx は、関数 f の x の値です。
procedure bisection(low, high, f) 
  local x, fx

  # 中点を計算します。
  x := (low + high) / 2.0

  # f の x の値を計算します。
  fx := f(x)

  # fx が十分に小さい場合は、x を返します。
  if abs(fx) < +1.0e-10 then
    return x

  # 範囲を更新します。
  if fx < 0.0 then # fx が負の場合は、low を x に設定します。
    low := x
  else             # fx が正の場合は、high を x に設定します。
    high := x

  # 再帰的に呼び出し、関数 f が 0 に等しくなる x を見つけます。
  return bisection(low, high, f);
end

# 実数を小数点文字列に変換
procedure format(x)
   local s, prec
   
   prec := 16
   x := real(x) | return image(x)

   s := ""
   if x < 0 then {
      s := "-"
      x := -x
   }

   x := string(integer(x * 10 ^ prec + 0.5))
   if *x <= prec then
      x := right(x, prec + 1, "0")
   return s || x[1:-prec] || "." || x[-prec:0]
end

procedure f1(x)
  return x - 1
end
  
procedure f2(x) 
  return x * x - 1 
end

procedure main()
  write(format(bisection(0, 3, f1)))
  write(format(bisection(0, 3, f2)))
end
実行結果
0.9999999999417923 
1.0000000000291038
旧課程(-2012年度)高等学校数学B/数値計算とコンピューター#2分法の例を Icon に移植しました。

構文

編集
program ::= declaration
          | declaration program

declaration ::= "global" identifier-list
              | "invocable" "all"
              | "invocable" proc-list
              | "link" link-list
              | "procedure" IDENTIFIER "(" [ parameter-list ] ")" ";" [ locals ] [ "initial" expression ";" ] [ expression-sequence ] "end"
              | "record" IDENTIFIER "(" [ field-list ] ")"

identifier-list ::= IDENTIFIER
                  | IDENTIFIER "," identifier-list

proc-list ::= string-literal
            | string-literal "," proc-list

link-list ::= file-name
            | file-name "," link-list

file-name ::= IDENTIFIER
            | string-literal

parameter-list ::= identifier-list
                 |                     IDENTIFIER "[" "]"
                 | identifier-list "," IDENTIFIER "[" "]"

locals ::= local-specification identifier-list ";"
         | local-specification identifier-list ";" locals

local-specification ::= "local"
                      | "static"

field-list ::= field-name
             | field-name "," field-list

expression-sequence ::= [ expression ]
                      | [ expression ] ";" expression-sequence

expression ::= "(" [ expression ] ")"
             | "{" expression-sequence "}"
             | [ expression-list ]
             | expression "." field-name
             | expression [ expression-list ]
             | expression [ range-specification ]
             | [ expression ] "(" expression-list ")"
             | [ expression ] "{" expression-list "}"
             | "(" expression-list ")"
             | prefix-operator expression
             | expression infix-operator expression
             | expression "to" expression [ "by" expression ]
             | "create" expression
             | "return" [ expression ]
             | "suspend" [ expression ] [ do-clause ]
             | "fail"
             | "break" [ expression ]
             | "next"
             | "case" expression "of" "{" case-list "}"
             | "if" expression "then" expression [ "else" expression ]
             | "repeat" expression
             | "while" expression [ do-clause ]
             | "until" expression [ do-clause ]
             | "every" expression [ do-clause ]
             | IDENTIFIER
             | KEYWORD
             | literal

expression-list ::= expression
                  | expression "," expression-list

range-specification ::= expression ":" expression
                      | expression "+:" expression
                      | expression "–:" expression

do-clause ::= "do" expression

case-list ::= case-clause
            | case-clause ";" case-list

case-clause ::= expression ":" expression
              | "default" ":" expression

literal ::= numeric-literal
          | quoted-literal

numeric-literal ::= integer-literal
                  | real-literal

integer-literal ::= digit-literal
                  | radix-literal

digit-literal ::= digit
                | digit digit-literal

digit ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"

radix-literal ::= radix radix-specification digit-specification

radix ::= "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
        | "10" | "11" | "12" | "13" | "14" | "15" | "16" | "17" | "18" | "19"
        | "20" | "21" | "22" | "23" | "24" | "25" | "26" | "27" | "28" | "29"
        | "30" | "31" | "32" | "33" | "34" | "35" | "36"

radix-specification ::= "r" | "R"

digit-specification ::= /0-9A-Za-z/+