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

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

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

参照用 記事

CatyScript2.0が完成

言語仕様を決めてから20ヶ月以上…。予定していた構文・機能は全部動くようになりました。バグはまー残っているでしょうし、パフォーマンスもこれから、後から追加した機能で中途半なモノもあります。が、擬似コード/机上コード(処理系なし)として書いたものが今は全て実行できます

以下、スクリプトコードを羅列するだけ。


まず、2010年11月に机上で書いた無限FizzBuzz。2011年の時点で(「CatyScriptで無限に動き続けるFizzBuzzが書けた」)は代替手段を使って動いていたのですが、今は当初の予定通りの繰り返し制御構造begin/repeatで動きます。


module sample;

/** 入力が、引数で指定された数で割り切れるかどうかを判定する */
command dividable [integer] :: integer -> (@True integer | @False integer) {
[
[pass > n, %1] | num:mod,
0
] | eq |
when {
True => @True %n,
False => @False %n,
}
};

/** 入力値を開始の数として、FizzBuzzを永遠に続ける。
* 出力を生成することはないので、出力の型はneverである。
*/
command fizzbuzz-forever :: integer -> never {
begin {
dividable 15 |
when {
True => [pass, ("Fizz Buzz" | cout)] | nth 1, // 15の倍数
False => dividable 3 |
when {
True => [pass, ("Fizz" | cout)] | nth 1, // 3の倍数
False => dividable 5 |
when {
True => [pass, ("Buzz" | cout)] | nth 1, // 5の倍数
False => [pass, (to-string | cout)] | nth 1 // その他
}
}
} | sleep 1000 | num:inc | repeat
}
};

次は「CatyScriptで記述するCatyシェル」で書いたシェル。これもbegin/repeatで繰り返し、例外捕捉try/catchも使っています。


/** quitコマンドであるかどうか判定する */
command quit-p :: string -> @True string | @False string {
pass > in |
text:regmatch "^\\s*quit\\s*$" |
when {
OK => @True %in,
NG => @False %in,
}
};

/** エラーメッセージをコンソールに印字する */
command print-error :: void -> void {
"ERROR" | cout
};

/** 評価して結果をコンソールに印字する */
command eval-print :: string -> void {
try {eval} |
catch {
except => print-error,
normal => xjson:pretty | cout
}
};

/** シェルを実行する */
command shell :: void -> void {
"Caty interactive shell" | cout;
begin {
"> " | cout --no-nl;
cin | quit-p |
when {
True => void, // ブロックを抜けて終了
False => eval-print; repeat
}
}
};

CatyScriptに高階関数(関数を引数にする関数)はないのですが、文字列のevalを使うとモナドはけっこう簡単に書けます。典型的なモナドであるListモナドとMybeモナド


/** コマンドライン文字列
* CatyScriptは高階の対象(指数対象)を直接は扱えない。
* しょうがないから、文字列として実行すべきコードを渡す。
*/
type commandStr = string(remark="コマンドライン文字列");


/** Listモナド台関手の対象部分
*/
type List<T> = [T*];

/** Listモナドのその他の構成要素
*/
class List {
/** Listモナド台関手の射部分
* List型のmap関数
*/
command map<S, T> [commandStr /* S -> T */] :: List<S> -> List<T> {
each {
[pass, %1] | eval
}
};

/** モナド単位 */
command unit<T> :: T -> List<T> {
[pass]
};
/** モナド乗法 */
command flatten<T> :: List<List<T>> -> List<T> {
list:concat
};
};

/** Maybeモナド台関手の対象部分
*/
type Maybe<T> = {
"defined": boolean,
"value": T?,
};

/** Maybetモナドのその他の構成要素
*/
class Maybe {
/** Maybeモナド台関手の射部分
* Maybe型のmap関数
*/
command map<S, T> [commandStr /* S -> T */] :: Maybe<S> -> Maybe<T> {
pass > in |
$.defined |
cond {
false => {"defined": false},
true => [%in | $.value, %1] | eval > value; {"defined": true, "value": %value},
}
};

/** モナド単位 */
command unit<T> :: T -> Maybe<T> {
{"defined": true, "value": pass}
};

/** モナド乗法 */
command flatten<T> :: Maybe<Maybe<T>> -> Maybe<T> {
pass > in |
$.defined |
cond {
false => {"defined": false},
true => %in | $.value.defined |
cond {
false => {"defined": false},
true => {"defined": true, "value": (%in | $.value.value)},
},
}
};
};

次のTreeRec(ツリーレコード)モナドはベキ等モナドですが、型の定義には再帰と型パラメータが使われています。


/** TreeRecモナド台関手の対象部分
* TreeRecは、再帰的な総称型である。
*/
type TreeRec<T> = {
*: (T | TreeRec<T>)?
};

/** TreeRecモナドのその他の構成要素
*/
class TreeRec {
/** モナド台関手の射部分
* TreeRec型のmap関数
* TreeRec型が再帰的なので、再帰的コマンドになる。
*/
command map<S, T> [commandStr /* S -> T */] :: TreeRec<S> -> TreeRec<T> {
each --obj {
when {
object => TreeRec.map<S, T> %1 ,
* => [pass, %1] | eval ,
}
}
};

/** モナド単位 */
command unit<T> :: T -> TreeRec<T> {
{"": pass}
};

/** モナド乗法 */
command flatten<T> :: TreeRec<TreeRec<T>> -> TreeRec<T> {
/* TreeRec はベキ等モナドである */
pass
};
};

最後に、僕が一番欲しかった/入れたかった機能はgoto制御(forward構文)なので、繰り返しではなくて、制御をピンポンして無限に Hello, world. を印字し続けるスクリプト


/** Hello, と印字してworldにフォーワードする */
command hello :: void -> never {
"Hello, " | cout --no-nl;
forward sample:world
};

/** world. と印字してhelloにフォーワードする */
command world :: void -> never {
"world." | cout;
forward sample:hello
};