このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

CatyScript2.0の変数概念

CatyScript2.0は、諸般の事情でそうすぐには実装できないでしょうが、仕様は定めておかないと後で困ることになるのでボチボチと書いておこうかと。ひとつ断っておくと、今まで「Catyスクリプト」という表記を使ってきましたが、2.0からは「CatyScript」にしようと思ってます。

さて、今までのCatyスクリプトでとても困ったことは「変数がない」ことです。じゃ、変数機構を入れりゃいいじゃん。でもね、Catyスクリプトは変数と相性がわるいのですよ。安直に変数を導入できないのです。「これなら、うまくいきそうだ」と思えたのは、変数を単一代入の名前付きパイプのごとくに解釈する方法です。このセマンティクスだと、変数があっても、変数をまったく含まない等価なプログラムに変換できます。

変数概念の概要

OSシェルのコマンドライン echo -n hello > greeting を考えてみましょう。このリダイレクト付きのコマンドラインを実行すると、次のことが起きます。

  1. ファイルシステムに、greeting という名前のファイルがなければ新しく作られて、その中身は"hello"というテキストになります。
  2. ファイルシステムに、greeting という名前のファイルがあれば、もとの中身は破壊され、"hello"というテキストに置き換えられます。

CatyScriptでも、値の保存にはリダイレクト風の構文 "hello" > greeting を採用しますが、ecoh -n hello > greeting とは少し違います。

  1. 変数束縛環境に、greeting という名前の変数がなければ新しく変数が作られて、その値は"hello"という文字列になります。
  2. 変数束縛環境に、greeting という名前の変数があれば、書き換えることはできなくて、BoundVariable例外が発生します。

変数束縛環境は、変数名/値ペアの集合で、概念的にはJSONオブジェクトと思ってかまいません。入れ子の変数スコープを認めるので、複数の変数束縛環境がチェーンを形成します。当初は、手前(内側)の束縛環境が奥(外側)の束縛環境の一部を隠してしまうこと(変数名のシャドーイング)を許さない方向で考えていました。が、チト不便な感じなので、この制限は設けません。要するに、変数束縛のチェーンを手前から奥にたどっていって、最初に見つかった名前の値を採用するわけです。

変数参照構文は、<greeting (逆向きのリダイレクト)も魅力的なんですが、「ちょっとアンマリだ」つう話もあるので、%greeting とします。もし、名前greetingに対する変数束縛が見つからなければ、UnboundVariable例外が発生します。ただし、%greeting? という疑問符付きの参照形式を使うと、例外が起きずに未定義値(undefined value)が返ります。未定義値は潜在的に使っていた概念ですが、CatyScript2.0では正式に明示的に導入します。

変数の生成と束縛は必ず一緒に行われ、変数値を変更することはできず、明示的に変数の削除もできません。スコープが終わる所で自動的に(そのスコープに付随した)変数束縛環境が捨てられます。

代入式の構文

代入式 ::= 式 '>' 名前

これだけ。代入式の値は、'>' の左側の値で、式のなかに代入式も含まれるので、"hello" > greeting > hello も許されます。'>' を二項演算子と考えると、左結合的ですね(そうでないとうまく解釈できない)。代入式の実行の意味(副作用)は、既に述べたように現在のスコープ内に変数束縛を追加することになります。

代入式の値が邪魔になるときは、セミコロン「;」を付けると値を捨てることができます。"hello" > greeting; です。"hello" > greeting; は、単に "hello" > greeting | void のシンタックスシュガーです(voidは値を捨てるコマンド)。

BNFによる構文記述はまた今度(>主にKuwataさん)。

beginブロック

変数のスコープは、だいたいはインタプリタが勝手に作ります。プログラマがスコープを制御したいならbeginグロックを使います。begin { /* ブロックの内容 */ } という形。beginブロックも式であり、ブロックの内容の値がbeginブロック(begin式)の値となります。

beginの入り口で空な変数束縛環境が作られて、beginブロック内の代入式は新しい変数束縛環境に作用して、beginブロックを出るときにブロックローカルな変数束縛環境は捨てられます。

set式

set式は、複数の代入文を並べたものに set を前置する形式。これがなくても、表現力の観点からは全然困らないのですが、可読性/視認性が向上するという理由で導入します。

set式 ::= 'set' '{' 代入文 (',' 代入文)* ','? '}'

set {"hello" > greeting, "world" > toWhom} は、["hello" > greeting, "world" > toWhom] | void と変わりません。

サンプル

整数値の階乗を求める例を挙げます。次のコマンドは前もって定義されているとします。

  1. dec 入力から1を引いて出力
  2. non-positive-p 入力が非正(ゼロまたは負)数のときに@Trueを付加する述語コマンド
  3. mul 入力タプルの2つの数(タプルの成分)を掛け算して出力

repeatは繰り返しで、標準入出力データを伴ってブロック(以下のケースではfact2コマンドのボディ)の先頭に飛びます。繰り返しのときに、繰り返されるブロックの環境はリニューアルされます*1


/** 整数値の階乗を求める
* 入力として非正の整数を許す。ただし、非正の入力に対する出力は常に 1 とする。
*/
command fact :: integer -> integer {
[pass, 1] | fact2
};

command fact2 :: [integer, integer] -> integer {
set {
nth 1 > n,
nth 2 > m,
};
%n | non-positive-p |
when {
True => %m,
False => [(%n | dec), ([%n, %m] | mul)] | repeat
}
};

問題点

コマンドラインなどから渡されるパラメータがあります。オプションパラメータと引数パラメータです。引数パラメータは %0, %1, %2 などで参照しますが、オプションパラメータはどうすべきか。オプションは名前を持つので、普通の変数と同じに扱えます。ですが、あるコマンドが受け取ったオプション群をマルッと下位コマンドに渡すことなどを考えると、オプション達は普通の変数とは別に管理する必要があります。

引数パラメータが特殊な構文を使っているので、オプションパラメータも特殊な構文がいい気もします。一方で、構文を増やすと煩雑だから一様な変数参照構文がいいかな、とも思ったりして。ウーム。

*1:同じスタックフレームを再利用して実行される末尾再帰呼び出しと同じです。