はじめに
Ajisaiは、監査可能で正確なベクトル計算と機械可読な契約を実現する、AIファーストのベクトル指向データフロー言語です。プログラムは要素の並びであり、その要素の多くはワードの呼び出しです。値は単一のスタック上で管理され、各ワードがスタックからオペランドを取り出し(消費し)、処理結果をスタックに積みます。分数とデータ構造におけるVectorが中心的な役割を持ちます。
整った操作が値を作れなかったときはNILとなり、理由を持ったまま下流の計算へそのまま渡されます。一方、操作や入力の形そのものが誤っているときはエラーとなり、評価はその場で停止します。形が正しい場合でも、ホストが支えられる限度を超えれば同じくエラーになります。この限度については1.2.2 評価のコストで扱います。
なぜAjisaiを作ったのか
私がAjisaiを開発した理由は二つあります。一つは、あらゆるプログラミング言語の習得に挫折してきたこと。もう一つは、言語という道具でなにかをつくりたかったわけではなく、道具そのものをつくってみたかったことです。
そんな私に大きな転機をもたらしたのが、Forthとの出会いです。スタック指向という、極限まで削ぎ落とされたミニマリズム。構文のノイズを排し、計算という行為そのものをむき出しにするその姿は、言語がここまで小さくてよいのだと教えてくれました。
しかし、普段のビジネスシーンでマニュアルやセオリーを重んじる性質である私は、プログラミングにおける「型」の堅牢さをどうしても諦めきれませんでした。そこで生まれたのが、「型を大切にしたい、しかし型について書きたくない」という強烈な葛藤です。
この矛盾への答えが、厳密さを、値に添える注釈ではなく、ワードの側の機械可読な契約として持たせることでした。Coreのワードはすべて、何をいくつ受け取り、純粋か、値を作れなかったとき何を返すかを契約として持っています。ユーザー定義のワードにも同じ形で宣言を書けて、実行前に契約と照合されます。プログラマが型を書く場所は言語のどこにもありませんが、検査はワードの側に残ります。
数についても同じ方針です。整数や小数として書いても、値はすべて厳密な有理数で、SQRTがそこから代数的無理数まで数の体を広げます。丸めは起きません。floatとdoubleとdecimalのどれを選ぶか、という問いそのものが存在しません。
そして、この妥協のない極端な設計を現実のものにしたのが、AIとの出会いでした。AIが意図を解釈し、開発の強力なパートナーとなる現代において、人間のための甘い構文(シンタックスシュガー)で言語を複雑に飾る必要はありません。AIという新たな知性が共にコードを紡ぐ「AIファースト」を前提とすれば、機械可読な厳密なルールだけを置き、あとは純粋なデータフローに身を委ねる設計が成立する——そう確信したのです。
厳密な数と、それを運ぶスタック。この形が定まったとき、私の脳裏に浮かんだのは「器に注がれた水」の情景でした。器を満たすのは、決して丸められることのない水だけです。しかし、AIとともにその水面へ広げていく波紋のかたちに、際限はありません。
奇しくも、「水の器」を学名の語源に持つ花があります。紫陽花(あじさい)です。開発を始めたのは、雨が降り続く日本の6月、梅雨の時期でした。Ajisaiは、その情景を思い浮かべながらつくった言語です。
数値の厳密性
すべての数値は厳密に扱われます。値は厳密な有理数と、それらをSQRTで閉じたときに生じる代数的無理数、すなわち多重二次正規形として保持されます。算術も比較も丸め誤差なしに実行されます。なお、スタック表示のsqrt(2)や1/1+sqrt(2)は正規形をそのまま一つのトークンとして書いた表示であり、打ち切りも近似もありません。Playgroundのスタック領域も同じトークンをそのまま表示します。
本リファレンスの特徴
本書では、各機能を検証済みの短いサンプルコードとともに解説します。すべての例には「Playgroundで開く」ボタンが用意されており、クリックするだけでアプリのエディタにコードが読み込まれ、実際の挙動をすぐに確認できます。
本書は、プログラムを要素に分け、要素をさらに分けられる限り分けていく順に述べます。分けていった先にあるのが個々のワードで、そこで本書は終わります。
本リファレンスの位置づけ
本リファレンスは、Ajisaiを利用・学習する方を対象としています。言語自体の実装や移植を行う場合は、唯一の設計上の典拠であるSpecificationを参照してください。本書は、日常的な利用や学習向けにその内容を再構成したものです。
例の読み方
各セクションのExpected value(期待値)列には、プログラム実行後の最終的なスタックの状態を、言語の描画仕様に準拠して表示しています。あらかじめ把握しておくべき表示規則は次のとおりです。
1. 基本的な表示規則
- 数値:
分子/分母の形の厳密な分数として表示されます。整数の3は3/1と描画されます。 - 真偽値:
TRUEまたはFALSEと表示されます。 - 値の不在:
NILと表示されます。
これらはStack領域における標準的な描画形式であり、本書のExpected value列の記載内容と一致します。画面が3/1ではなく3と表示している場合、それは次に述べるLaTeX表示モードが有効になっているためです。この設定はブラウザに記憶され、再読み込みしても引き継がれるので、以前の閲覧で有効にしたまま忘れていることがあります。Expected value列と見比べるときは、チェックを外した状態を基準としてください。
2. LaTeX表示モード(数式組版)
Stack領域にはLaTeXチェックボックスがあり、初期状態はオフです。これを有効にすると、同じ値を組版された数式として再描画できます。
- 分数は分子と分母を縦に並べた形で表示されます。
- 数値ベクトルは行列形式で描画されます。
- 分母が
1の分数は整数として扱われ、3/1は3と描画されます。
これは同一の値に対するもう一つの表示形式であり、内部的な値が変化するわけではありません。どちらの表示モードでも厳密な有理数のままであり、丸めや勝手な約分は行われません。チェックを外せば元の正準形に戻ります。文字列やNIL、形状の不揃いなベクトルなど、数学的に一意な組版表現を持たない値は、チェックを有効にしても標準表示のまま維持されます。
3. 大きなコレクションの省略表示
コレクションの要素数が非常に多い場合、Stack領域には先頭部分のみが描画され、末尾に… 199900 moreのようなマーカーが付与されます(画面表示は英語です)。このマーカーは画面描画上の都合による注記であり、内部データが切り詰められているわけではありません。スタック上のコレクション自体は完全な状態で保持されており、すべてのワードはデータ全体を参照します。LENGTHも実際の要素数を返します。
4. 記号・トークンの表記
本書の本文中で灰色の背景が付いているテキストは、すべてAjisaiのトークンを表します。灰色の背景は、その表記が地の文ではなくプログラムコードの一部であることを示しています。記号だけで綴られたトークンも、ほかと同じ通常の名前です。
1 プログラム
Ajisaiのプログラムは要素の並びです。
要素を0個以上、空白で区切って並べたものがプログラムです。
要素は空白で区切って書き、左から右へ1つずつ評価されます。条件分岐・反復・定義もワードであり、ほかの要素と同じく並びの中に書きます。[ 2 MUL ] 'DOUBLE' DEFは、ベクトル・文字列・名前の3要素からなるプログラムです。
プログラムについて述べることは2つあります。テキストがどう要素に区切られるか(1.1 要素の区切り)と、並んだ要素がどう評価されるか(1.2 評価)です。要素そのものは2 要素で述べます。
1.1 要素の区切り(空白, #)
要素は空白で区切られます。テキストはまずトークンの並びに分けられ、角括弧[ ]の組がベクトルにまとめられて、要素の並びになります。テキストからトークン列を作る規則はspec/grammar.jsonに一つだけ定義されており、どんなテキストも「ちょうど1つのトークン列」か「名前の付いた1つのソースエラー」のどちらかになります。
字句解析の流れ
レキサーはテキストを左から右へ読み、トークンを置ける位置ごとに次の4つの規則を上から順に試します。最初に当てはまった規則が、その位置の文字を消費します。
- 空白(スペース、タブ、改行などUnicodeの空白文字)なら、1文字読み飛ばす。何も生成しない。
#ならコメント。行末の手前までを捨てる。'なら文字列リテラル。直後が空白か入力の終わりである閉じ引用符'までを読む。- それ以外なら、次の空白(または入力の終わり)までを1つの語として切り出す。途中の文字はどれも検査しない。
切り出した語は、次の順に分類されます。判定に使うのは語の全体なので、文字ごとの規則はありません。
- ちょうど
[か]なら区切り記号(ベクトルの開始・終了)。 - それ以外で
[か]を含むならソースエラー(bracketMustStandAlone)。 - 分母が0の分数の形(
1/0)ならソースエラー(zeroDenominator)。 - 全体が数の文法に一致すれば数値リテラル。
- 残りはすべて名前。
最後に、トークン列全体で角括弧の対応を確かめます。開いていない]と閉じていない[はソースエラーです。どの段階でソースエラーになっても、そのソースは1トークンも実行されません。
トークンの区切り規則
トークンを区切るのは空白文字(スペース、タブ、改行)だけです。ほかにトークンを分割する文字はありません。記号も句読点も、すべて通常の名前を構成する文字として扱われます。
この規則に例外はなく、次の2つも例外ではありません。
[と]は、他のどのワードとも同じく単独で立たなければなりません。[ 1 2 3 ]は正しく、[1 2 3]は空白の不足を指摘するソースエラーになります。#がコメントを開始するのは、それがトークンの先頭にあるときだけです。直前のトークンに密着した#はその名前の一部になるため、ADD# メモはコメント付きのADDではなく、ADD#という未定義のワードです。
Ajisaiが意味を与える括弧は[ ]の1組だけです。ベクトルの括弧で、コードブロックにも同じものを使います。{ }や( )に構文上の役割はありません。これらは名前を構成するただの文字なので、単独で書けば「そんなワードは無い」という結果になります。詳しくは厳密な数を参照してください。
演算ワードと空白の必要性
演算ワードも名前の一種であるため、前後に空白が必要です。2 ADDは2とADDの2つのトークンとして正しく解釈されます。2ADDは「2ADD」という1つの名前とみなされ、加算ではなく未定義ワードとしてエラーになります。
角括弧[ ]も同じです。それ自体で1つのワードであり、前後に空白が必要です。[ 1 2 ]LENGTHは]LENGTHという不正なトークンとして拒否されるため、[ 1 2 ] LENGTHと書いてください。
PlaygroundのFormatボタンは、この足りない空白を補います(実行前にも同じ整形が走るため、[1 2 3]はPlaygroundでは[ 1 2 3 ]として実行されます)。整形されるのは編集中のソースだけで、言語の規則がゆるむわけではありません。CLIやMCPに同じソースをそのまま渡せばエラーになります。
改行
改行はスペースと同じ空白で、トークンを区切る以外の意味を持ちません。プログラムは好きなだけ多くの行にわたって書いてかまわず、1行で書いても複数行に分けて書いても同じプログラムです。ワード定義の本体も同じで、改行の位置が違うだけの2つの定義は同じワードになり、DIGESTも一致します。改行が終わらせるのはコメントだけです。単語の先頭に書いた#から行末までがコメントになります。
コメント
#は行末まで続くコメントを開始します。#からその行の末尾までの文字は、トークン化される前に破棄されます。なお、文字列リテラルの中にある#は通常のテキストとして扱われるため、'a # b'は全体で1つの文字列です。
具体例
| 記述 | トークン数 | 理由 |
|---|---|---|
3 4 ADD | 3トークン | 空白が各トークンを区切る。 |
3 4ADD | 2トークン | 4ADD全体で1つの名前とみなされ、加算ではなく未定義エラーになる。 |
[ 1 2 ] LENGTH | 5トークン | 空白が各トークンを区切る。空白を省いた[ 1 2 ]LENGTHは]LENGTHが不正なトークンとなりエラー。 |
トークンの種類
字句解析が生み出すトークンは5種類だけです。空白とコメントはトークンを生みません。
| 種類 | 書き方の例 | 運ぶもの |
|---|---|---|
| 数値リテラル | 3 -1/2 0.25 1e5 | 字句そのもの。厳密な数として読まれる |
| 文字列リテラル | 'hello' | 引用符の間のテキスト |
| 名前 | ADD NIL? DOUBLE | 書かれたとおりの字句。ワードか束縛を名指す([ ]の中ではSymbol) |
| ベクトル開始 | [ | なし |
| ベクトル終了 | ] | なし |
ソースエラー
字句解析で起こりうるエラーは次の5つだけで、分類はどれもmalformedSourceです。
| 条件 | 例 | 意味 |
|---|---|---|
unclosedLiteral | 'foo | 文字列リテラルが、直後に空白のある閉じ引用符に出会わないまま入力の終わりに達した。 |
zeroDenominator | 1/0 | 分数リテラルの分母が0。 |
bracketMustStandAlone | [1 | 語が[か]を含むが、その記号そのものではない。 |
unexpectedCloseBracket | ] | 開いている[がないのに]が現れた。 |
unclosedBracket | [ | [が閉じられないまま入力が終わった。 |
1.2 評価
プログラムは要素ごとに、左から右へ評価されます。要素の種類ごとに起こることは次のとおりです。
- 数値リテラル・文字列リテラル: その値をスタックに積む。
- ベクトル
[ ... ]: 中身を評価せず、ベクトルのまま積む。中の名前は、TRUEFALSENILを除いてSymbolとして残る。 - 名前: 束縛されていればその値を積み、そうでなければ辞書のその名前のワードを呼び出す。どちらでもなければエラー(Unknown word)。
スタックに載る値
評価によってスタックに積まれる値は、次の7つの定義域のどれか1つに属します。定義域どうしは互いに素で、間に暗黙の変換はありません。
- 数: 厳密な有理数と代数的無理数(2.1 数)
- 文字列(2.2 文字列)
- ベクトル: 値の並び。コードとしても読まれる(2.3 ベクトル)
- Symbol: データとして持たれた名前(2.4.1 束縛)
- 真偽値:
TRUEとFALSE(2.4.2.1 論理・比較・算術) - Record: キーと値の対応(2.4.2.3 キーによる対応)
NIL: 理由を持つ不在(1.2.1 評価の結末)
消費
Ajisaiのワードは、スタックからオペランドを取り出し、結果をスタックへ積み戻します。読み込んだ値は、その場で必ず消費されてスタックから取り除かれます。この挙動を変える修飾子はありません。BIND、DEF、DELのように結果を何も積まないワードも例外ではなく、オペランドを消費します。
同じ値を2回使うにはBIND
1つの値を2回以上使いたいときは、BINDで名前を付け、その名前を必要な回数だけ書きます。名前を書くたびに、その値がスタックに積まれます。5 'N' BIND N N 1 ADDは5/1 6/1になります。値は名前で読まれるので、どの値がどこで使われるかが字面から分かります。
ワードは本体に置き換えられる
どの呼び出しも読んだオペランドをちょうど消費するので、自分で定義したワードを、その本体で置き換えて書いても、消費されるオペランドは変わりません。[ 2 MUL ] 'DOUBLE' DEFがあるとき、5 DOUBLEと5 2 MULはどちらも10/1です。ユーザー語を使ったプログラムを、Core語だけの形に展開して確かめられるのは、この性質があるからです。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
3 4 ADD |
7/1 |
両方のオペランドが消費され、和が積まれる。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
5 'N' BIND N N 1 ADD |
5/1 6/1 |
名前を付けた値は、名前を書くたびに積まれる。 |
1.2.1 評価の結末(値, NIL, エラー)
1つの要素の評価が行き着く先は3つです。成功して契約どおりの結果を積む(結果が1つもないワードもあります)か、理由を持つNILを積むか、エラーで評価全体を止めるかです。
NILの規則
失敗のモデルは一言でまとめられます。値を作れなかったならNIL、そもそも使い方が違うならエラーです。正しい形の操作が結果を作れない場合は、構造化された理由を伴うNILに写像されます。一方、型違いやオペランド不足といった誤った使い方は、通常のエラーを送出します。
NILは伝播する
算術と比較はどちらもNILをそのまま通します。NILのオペランドが入っても、パイプラインをクラッシュさせることなく結果がNILになるだけです。EQも例外ではなく、NIL 1 EQはFALSEではなくNILになります。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
1 0 DIV |
NIL |
ゼロ除算はトラップではなく、理由付きのNILになる。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
NIL 1 ADD |
NIL |
NILは算術をそのまま通り抜け、その理由は追跡可能なまま保たれる。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 10 20 30 ] 5 GET |
NIL |
有効なベクトルに対する範囲外のインデックスはNILになる(理由:indexOutOfBounds)。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 1 2 3 ] 5 TAKE |
NIL |
あるだけより多く要求するのも「末尾を越えた位置」を指す同じ問いであり、GETと同じ答えになる(理由:indexOutOfBounds)。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 1 2 3 ] 9 5 PUT |
NIL |
PUTも同様。範囲外のインデックスは、プログラムの誤りではなくデータの都合なのでNILになる。 |
1つの条件には1つの答えがあります。「末尾を越えた位置」はGET・TAKE・PUTのどれでもNILであり、語彙全体で同じ扱いです。一方、整数ですらないカウントやインデックス([ 1 2 3 ] [ 'x' ] TAKE)は今もエラーです——そちらは使い方が違うからです。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
'ABC' NUM |
NIL |
数としてパースできないテキストはNILになる(理由:invalidEncoding)。 |
1.2.2 評価のコスト
反復のコスト
厳密算術は、ループのコストを変えます。このリファレンスの他のすべての節はプログラムが何を意味するかについてですが、この節はそれを繰り返すことが何のコストを伴うかについてです。厳密性が見返りに何かを要求してくる、唯一の場所だからです。
コストはどこから来るのか
厳密な有理数は分子と分母の組として格納されており、分数と分数の積は分母同士の積になります。1回掛けるだけなら何も起きません。しかしループの中で掛け続けると、分母はステップを追うごとに大きくなります。勾配降下法、ニュートン法、k-means、べき乗法、その他あらゆる反復的手法がやっているのは、まさにこれです。何も失われず、何も近似されません。数はまったく正しいままです。ただ、その桁が記録しているのはその値がどう計算されたかであり、その履歴は回を追うごとに長くなります。
以下は緩和ステップの例で、xが毎回2に向かって10分の1ずつ近づいていきます。DELTAが補正量、STEPがそれを適用します。ループは、要素が単なるカウンタでしかない範囲に対するFOLDであり、ブロックは要素を捨てて累積値だけを保持します。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 2 SUB 1/10 MUL ] 'DELTA' DEF |
7282969/1250000 |
10からの7ステップ(0 6 RANGEは0から6までの7要素)。分母はすでに7桁。値はおよそ5.83。 |
問題は、どれか1つの数の大きさではなく、成長の形そのものです。同じループを21ステップ回すと359418989131512359209/125000000000000000000になります。21桁分の21桁で、値はおよそ2.875です。さらにステップを重ねるたびに分母は再び10倍になっていくので、答えはほとんど動かないのに、1ステップあたりのコストは際限なく上がっていきます。30回の反復なら一瞬で終わるループが、300回では這うように遅くなり得ます。
分解能を名指しする
x d MUL 1/2 ADD FLOOR d DIVは値を1/dの最も近い倍数に丸めます(ちょうど中間なら大きい側へ)。結果の分母はdの約数になり、それを超えて大きくなることは決してありません。1ステップにつき1回適用すれば、導出の履歴を捨てて値だけを保ちます。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 2 SUB 1/10 MUL ] 'DELTA' DEF |
2877/1000 |
21ステップ経っても分母は1000のまま。0 20 RANGEは0から20までの21要素。丸めない場合の実行結果は2.875351…で、要求した分解能まで見れば同じ数。 |
これは近似ですが、それこそが要点です。自分で書き記した近似だからです。分解能はソース中のリテラルとして、自分で選んだ場所に書かれており、それ以降のすべての値は正確に1/dの倍数になります。与り知らないところで何かが丸められることはなく、答えにどれだけの精度がふさわしいかを、何かが勝手に決めることもありません。
この丸めを組み立てているMUL・ADD・FLOOR・DIVはどれもベクトルに対して持ち上がるので、パラメータベクトル全体を同じ一続きのワードで丸められます。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 119/125 32/125 ] 10 MUL 1/2 ADD FLOOR 10 DIV |
[ 1/1 3/10 ] |
状態の形がどうであれ、1ステップにつき1回の呼び出し。 |
閉じた形はループに勝る
反復法に手を伸ばす前に、その問題に閉じた形があるかどうかを確かめてください。厳密算術においては、閉じた形は単に速いだけでなく、種類の異なる答えになるからです。最小二乗法、有理数のペナルティを使うリッジ回帰、k近傍法、有理確率上のナイーブベイズ、ジニ分割による決定木、パーセプトロン、単体法は、いずれも反復を伴わない厳密な解を持ちます。Ajisaiは係数をまさにその有理数として返します。R²が529/532として返ってくるなら、それが厳密な答えです。
指数関数、対数関数、三角関数、任意のべき乗といった超越関数には、有理数の答えは存在しないので、Ajisaiはそれらを扱いません。SQRTは、この言語が代数的数として厳密に構成する唯一の無理数であり、整数指数とp/2の指数のPOWはその体の中に留まります。数の領域はこの体がすべてなので、どの比較も有限時間でTRUEかFALSEに決まります。
もう一つのコスト: ホストの安全制御
ここまでのコストは、正しく書けたプログラムでも厳密算術そのものから自然に積み上がっていくものでした。これとは別に、ホストが計算資源を守るために設けている複数の上限があります。どれも言語の意味論ではなくホスト側の安全制御であり、具体的な数値は実装が定めます。同じソースでも、より大きな上限を宣言したホストでは通ることがあります。ただし、超過したときにどちらの結果になるかは決まっていて、1つの値そのものの生成が上限を超えれば理由付きのNILに、実行そのものが続けられないほど超えればエラーになり評価が停止します。
実行時エラー: 複数の理由を持つ一つの結果
整った操作や入力であっても、ホストが支えられる限度を超えればエラーになり、評価はその場で停止します。限度は1つではなく、超過したものが何かによって少なくとも3つの名前で報告されます。名前が違うのは、直し方も違うからです。
| 報告される名前 | 何が超過したか | 典型的な直し方 |
|---|---|---|
executionLimitExceeded | プログラム全体が費やせる作業ステップの総量 | 一括操作(SORTやGET)へ書き換える、終了条件を見直す |
resourceLimitExceeded | 1つの値そのものの大きさ(係数のビット幅、代数項の数など) | 1/dの格子に丸めて履歴を刈り込む、閉じた形を探す |
recursionLimitExceeded | 呼び出し連鎖(循環はしないが極端に長い、ワードがワードを呼ぶ鎖)のネストの深さ | 呼び出しの連鎖を浅くする、または一括操作に書き換える |
executionLimitExceededには、CLIのajisai run --step-limit <N>で明示的に指定できるという特徴があります。既定値はホストの実行環境が定める実装上の選択であり、本書が固定の数として約束するものではありません。
上の3つはインタプリタが数える上限ですが、Playgroundにはもう1つ、実時間の打ち切りがあります。1回の実行が5秒を超えると、実行中のワーカーごと停止され、Execution timed out after 5000 msとして報告されます。これは言語の意味論でもインタプリタの予算でもなく、この画面だけが持つホスト側のガードです。上のどの名前も付かないのはそのためで、代わりに「どのガードが止めたか」「一括操作への書き換え」「格子への丸めによる刈り込み」を述べる診断が付きます。ホストごとの上限は、画面上部のビルド表示の隣にあるresource limits:バッジにマウスを合わせると一覧できます(このガードもそこに載ります)。MCPホストはこれとは別の、より厳しい具体化上限を宣言します。同じ0 100001 RANGEが、Playgroundでは成功し、MCPホストではNIL(spaceExhausted)になるのはこのためです。
プログラム全体の作業量と、1つの値の大きさは別々に測られています。0 99999 RANGE UNIQUEのように、ステップ数としては1回でも内部で要素数ぶんの比較を行うワードがあるので、ステップ予算だけを見ていては値の重さは見えません。反復のコストの節で見た、分母が肥大化していく分数を思い出してください。格子に丸めて刈り込まずに育て続ければ、行き着く先はまさにこのresourceLimitExceededです。recursionLimitExceededはさらに別の性質を持ちます。ワードは自分自身を呼び出せませんから、これは再帰の深さではなく、A→B→C→…と多数の異なるワードを渡り歩く、循環しない呼び出し連鎖の深さです。
具体化の上限: 理由付きのNIL
RANGEとFILLは、1回の呼び出しで生成するベクトルの大きさに上限を持ちます。要求そのものは整った形をしていても、それを超えて具体化しようとすると、エラーではなく理由付きのNILになります。理由はspaceExhaustedです。負の数や非整数のように、次元や形状の指定そのものが誤っている場合はこれとは別で、従来どおりエラーになります。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
0 999999999999 RANGE NIL-REASON |
'spaceExhausted' |
要求は整った形だが大きすぎるので、エラーではなく理由付きのNILになる。FILLも同じ理由で同じ振る舞いをする。 |
NILなので、代わりの値に差し替えて先へ進めます。0 999999999999 RANGE 'S' BIND [ ] S S NIL? SELECTは空ベクトルへ回復します。エラーにはこの回復手段がありません。
2 要素
要素は次の4種類です。
要素は、数・文字列・名前・ベクトルのどれか1つです。ベクトルは[と]のあいだに要素を0個以上並べたもので、要素の中に要素が入る唯一の形です。
| 要素 | 例 | 評価すると |
|---|---|---|
| 2.1 数 | 3 -1/2 0.25 1e5 | その数を積む |
| 2.2 文字列 | 'hello' | その文字列を積む |
| 2.3 ベクトル | [ 1 2 3 ] | 中身を評価せず、ベクトルとして積む |
| 2.4 名前 | ADD N DOUBLE | 束縛されていればその値を積み、そうでなければ同じ名前のワードを呼び出す |
空白とコメントは要素ではありません(1.1 要素の区切り)。
表示形式は要素ではない
スタックに表示される形は、そのまま要素として書けば同じ値になるものを基本とします。例外は無理数のsqrt(2)(2.1 数)で、これは値の見え方であって、要素として書けばsqrt(2)はただの名前です。無理数はSQRTで作ります。Recordに専用の見え方はなく、[ 'x' ] [ 1/1 ] RECORDのように、それを組み立てるプログラムとして表示されます(2.4.2.3 キーによる対応)。
2.1 数
数値リテラル
小数リテラルは、小数点の両側に少なくとも1桁の数字を必要とします。0.5や5.0は数ですが、.5や5.は数ではありません。この規則により小数点が数の先頭や末尾の文字になることは決してなく、単独の.を不完全なリテラルではなく通常の名前として安全に使えます。
数の書き方は次の4つの形です。「数字の並び」はASCIIの0〜9を1桁以上並べたものです。
| 形 | 書き方 | 例 |
|---|---|---|
| 整数 | 数字の並び。先頭に-か+を付けてよい | 5 -5 +5 007 |
| 分数 | 整数、/、数字の並び | 1/2 -1/2 |
| 小数 | 整数、.、数字の並び | 0.5 5.0 |
| 指数付き | 整数か小数のあとにeかE、符号(省略可)、数字の並び | 1e5 1E5 1e-5 1.5e+3 |
分数には小数部も指数も付きません(1/2.5や1/2e3は名前です)。この書き方は語の全体に当てはめます。途中まで数に見えても最後まで一致しなければ、壊れた数ではなく名前です。全角数字は名前の文字です。
| 数になる | 名前になる |
|---|---|
5 -5 +5 007 | - + -x |
1/2 -1/2 | / 1/ 1/2/3 |
0.5 5.0 | .5 5. 1.2.3 |
1e5 1e-5 1.5e+3 | 1e 1e+ 5.e3 |
厳密な数
Ajisaiにおけるすべての数値は厳密数として扱われます。
数値の表現と定義域
整数、分数、小数、指数表記など各リテラルは、同一の厳密な値を表す異なる表記形式にすぎません。0.5は厳密な1/2そのものです。
数の定義域は、厳密な有理数と、それらをSQRTで閉じたときに生じる代数的無理数、すなわち多重二次正規形で構成されます。組み立てられるすべての数はそのどちらかです。
無理数のスタック表示(sqrt(2)、1/1+sqrt(2))は正規形を一つのトークンとして書いた表示です。Playgroundのスタック領域も同じトークンを表示します。詳しくは無理数の表示形式と構文規則を参照してください。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
0.5 0.25 ADD |
3/4 |
小数リテラルは厳密な有理数として計算される。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
1 3 DIV |
1/3 |
除算は厳密な有理数を返す。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
2 3 DIV 1 3 DIV ADD |
1/1 |
\(\frac{2}{3} + \frac{1}{3}\)は厳密に\(1\)になる。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
1000000000000 1000000000000 MUL |
1000000000000000000000000/1 |
任意精度整数は、桁数がいくら大きくなっても厳密なまま保たれる。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
9 SQRT |
3/1 |
完全平方の平方根は厳密な有理数に簡約される。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
2 SQRT 2 LT |
TRUE |
\(\sqrt{2}\)のような無理数は遅延評価される連分数であり、それに対する比較は厳密に計算される。 |
無理数の表示形式と構文規則
無理数の表示形式は1つです。値の正規形を空白なしの一つのトークンとして書きます。2 SQRTはsqrt(2)、1 2 SQRT ADDは1/1+sqrt(2)、2 SQRT 3 SQRT SUBはsqrt(2)-sqrt(3)、8 SQRTは2/1*sqrt(2)です(根号の中は平方因子を持たない形に正規化されます)。打ち切りも近似もなく、ベクトルの中でも一要素として読めます。CLIやMCPホストのstackDisplayも、Playgroundのスタック領域も、この同じトークンを表示します。CLIとMCPでは同じ項がsemantics.exactTermsに構造化されて並びます。
これは表示専用の出力であり、ソースコードの構文ではありません。sqrt(2)をプログラムに書いても未知の名前です。( )は構文ではなく名前の文字なので、プログラム中に書いても括弧としては働きません。
2.2 文字列(' ')
文字列リテラル
文字列リテラルは、トークン境界に位置する引用符(')で閉じます。ここでもトークンを区切るのは空白だけなので、閉じる'の直後には空白(または入力の終わり)が必要です。文字列の内部にアポストロフィがあっても、直後が空白でなければ文字列は終了しません。これにより、エスケープ文字なしで'It's fine'のような文字列を1つのリテラルとして書けます。逆に、引用符の直後が空白以外であればテキストの内側にとどまるため、'abc'CHARSも[ 'abc']も、2つのトークンではなく閉じられていない文字列として扱われます。
文字列(' ')
文字列は'で引用され、スタック上では引用符付きで表示されます。この引用符は表示上の便宜であって、値の一部ではありません。PRINTは生のテキストを書き出すので、'TEST'はTESTとして出力されます。詳しくは出力を参照してください。ただしコレクションの中に入れ子になった文字列は引用符を保ったままで、[ 'AB' 'CD' ]はそのままの形で表示されます。文字列と数や文字との相互変換については、文字列操作にまとめてあります。
独立した値の定義域
文字列は7つの値の定義域の1つであって、コードポイントのベクトルではありません。'A' [ 65 ] EQはFALSEになり、文字列に対する算術はコードに対する算術ではなくエラーになります。空文字列''は普通の文字列であり、NILではありません。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
'hello' |
'hello' |
文字列リテラルはそれ自身に評価される。 |
2.3 ベクトル([ ])
ベクトル([ ])
ベクトルは、要素となる値を角括弧[ ]で囲み、空白で区切って記述します。ベクトルリテラルは評価されるとそのままベクトル値になり、自由に入れ子にできます。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 1 2 3 ] |
[ 1/1 2/1 3/1 ] |
スタックに積まれたベクトル。整数は分母が1の厳密な分数として表示される。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ [ 1 2 ] [ 3 4 ] ] |
[ [ 1/1 2/1 ] [ 3/1 4/1 ] ] |
入れ子はそのまま保持され、グループ化されたデータや不揃いなデータを表現する手段になる。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 7/3 ] |
[ 7/3 ] |
分数は第一級のリテラルであり、厳密なまま保たれる。 |
リテラル中の裸の名前
ベクトルリテラルは最後まで徹底してリテラルです。[ ]の中に書かれた裸の名前はSymbolを表し、ワードの呼び出しとして実行されることも未定義エラーになることもありません。したがって[ FOO BAR ]は2つのSymbolからなるベクトル[ FOO BAR ]です。テキストが欲しければ引用符で囲んでください――[ 'FOO' 'BAR' ]はStringのベクトルになります。これは値に名前を付け始めたときに戸惑いやすい点でもあります。変数やワードを実行した結果をベクトルに入れたい場合は、リテラルではなくCOLLECTのような実行時のコレクション構築ワードを使ってください。詳しくは[ ]の中の名前はSymbolになるを参照してください。
[ ]とNILの違い
[ ]は空ベクトルです。値を1つも持たないコレクションですが、書いたり入れ子にしたり測ったりできる普通の値であり、[ ] LENGTHは0になります。これはNILではありません。NILは値の不在であり、たまたま何も保持していないコレクションとは別のものです。結果が空になる操作は、NILになるのではなく[ ]を返します。[ 1 2 3 ] [ 5 GT ] FILTERの結果は[ ]です。
データとしてのコード
コードブロックとベクトルは同じ種類の値です。[ ]はその両方にとって唯一の括弧であり、後で評価されるためのソースを保持したベクトルというだけで、データとは別の領域ではありません。あるワードの契約がそれをコードとして要求したときに実行可能なものとして読まれるだけなので、スタック上のブロックはどんなベクトルとも同じ姿で表示されます。[ 2 MUL ]は[ 2/1 MUL ]と表示され、[ ]は[ ]です。[ 1 2 ADD ] [ 1 2 ADD ] EQはTRUE――値がどう組み立てられたか(構築履歴)はその値の一部ではないので、あるベクトルが「コード」か「データ」かは使われ方より前には決まりません。ブロックの中の裸の名前――上のMUL――はSymbolであり、リテラルを組み立てる時点では参照されません。EXECや評価を要求するワードがそれを実行し、それ以外は実行しません。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 1 2 ADD ] [ 1 2 ADD ] EQ |
TRUE |
構築の経緯は値の一部ではない:EQには見分けがつかない。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 1 2 ADD ] EXEC |
3/1 |
EXECはオペランドがどう組み立てられたかを気にしない。 |
実行時の値をブロックの中に入れる
ブロックはソースなので、プログラムの実行中に計算した値はその中には入っていません。MAPやFILTERはブロックだけしか取らないため、ここで現実的な疑問が生じます。実行時にしか分からない閾値でフィルタするには、どうすればよいのでしょうか。[ 5 LT ]はリテラルの5と比較しますが、その数がプログラムの計算結果である場合、書くべき5はどこにもありません。
ブロックは普通のベクトルなので、普通のベクトル用ワードで組み立てられます。CONCATは、実行時の値を持つ1要素ベクトルと、ブロックの実行に必要なSymbolを持つ1要素ベクトルを結合します――[ 5 ] [ LT ]は[ 5 LT ]になり、その結果はどんなブロックとも変わりません。[ ]で書かれていなくても、ブロックを取るすべてのワードがそれを受け入れます。独立した橋渡しは不要で、これは上のサンプルがすでに示していた構築そのものです。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 3 1 4 1 5 9 2 6 ]
[ 5 ] [ LT ] CONCAT
FILTER |
[ 3/1 1/1 4/1 1/1 2/1 ] |
ここでの5は普通のスタック値。プログラムが計算した何かに置き換えれば、述語もそれに従う。 |
これがAjisaiにおける部分適用のすべてです。そのための独立したワードはなく、必要もありません。ブロックとは、他のどんなベクトルとも同じように内容を計算できる値だからです。
ブロックと評価文脈([ ])
ブロックはそれ自身のスタック規律を持ちません。ブロックを評価するワードが、そのブロックが何に到達でき、何を残してよいかを決めます。誤った規則で読むと、ブロック内のADDが2つのオペランドを見つけるか、1つも見つけられないかの違いになります。規則は5つあり、ブロック自身のテキストはそのどれが適用されるかを語りません。決めるのは、そのブロックを受け取るワードです。
| 評価するワード | ブロックが見るもの | ブロックが残すもの |
|---|---|---|
ワードの本体(DEF) | スタック全体 | 積んだものすべて、いくつでも |
EXEC | スタック全体 | 積んだものすべて、いくつでも |
MAP FILTER | 現在の要素1つだけ | ちょうど1つ。写像後の要素、またはFILTERの場合は真偽値。何も残さなければエラー。最上位より下にあるものは捨てられる |
FOLD SCAN | 累積値と要素の2つ | 次の累積値ちょうど1つ。何も残さなければエラー。最上位より下にあるものは捨てられる |
したがって[ ADD ] 'ADDW' DEFに続けて3 4 ADDWとすると7になり、3 4 [ ADD ] EXECも同様です。どちらもスタック全体を見るからです。しかし100 [ 1 2 ] [ ADD ] MAPはスタックアンダーフローです。ブロックが見るのは1つの要素だけで、100はそのフレームに入っていません。
「最上位より下にあるものは捨てられる」は読み返しておいてください。これが静かに起こるからです。2つの値を残す述語もそのまま通ります。[ 1 2 3 ] [ 1 GT TRUE ] FILTERはすべてを残します。最上位のTRUEが答えであり、その下の比較結果は捨てられるからです。エラーになるのは何も残さなかったときだけです。高階ワードのブロックが説明のつかない答えを返したら、それが残しているものの個数を数えてください。
ブロックの内部で何かが失敗すると、診断はブロックを実行したワードではなく、失敗したワード自身を名指しします。[ 1 2 ] [ 'x' 1 ADD ] MAPは、実行された文の位置とともにinside MAPを添えてADDを報告します。自分で定義したワードのところで、この掘り下げは止まります。その呼び出しはそのワード自体を報告します。呼び出し側から見れば、失敗したのはそれだからです。
別のブロックの内側に書かれたブロックは、書かれたその場所ではデータです。それが評価されるのは、それを受け取ったワードが実行したときだけであり、そのときは外側のブロックの規則ではなく、その受け取ったワード自身の規則の下で評価されます。ただしこれは評価の話であって、参照の話ではありません。定義しようとしているワード自身の名前が、MAPやEXECに渡す[ ]の内側にしか書かれていなくても、DEFの非循環規則はそれを検出し、定義そのものを拒否します。
2.4 名前
名前は、数でも文字列でも角括弧でもない語のすべてです。空白と角括弧を除くどんな文字も名前の一部になれます。ただし語の先頭の#はコメントを、'は文字列を始めるので、名前はこの2文字では始まりません(1.1 要素の区切り)。
名前を評価すると、その名前が束縛されていれば値が積まれ(2.4.1 束縛)、そうでなければ同じ名前のワードが呼び出されます(2.4.2 ワード)。どちらでもない名前はエラー(Unknown word)です。[ ]の中に書かれた名前は評価されず、TRUE FALSE NILを除いてSymbolという値になります。
記号とトークン判定
Ajisaiにおける記号は、単に記号で綴られた名前にすぎません。ワードはどれも英語名1つだけで呼ばれ、記号の綴りを持つワードはありません。+や<も通常の名前で、定義しなければ未定義ワードです。記号は名前の綴りの一種にすぎないため、たとえ複数の文字からなる記号があっても上の規則は何も複雑になりません。トークンが終わるのは空白のときだけで、途中で終わることは決してないからです。トークンの意味は先頭の文字ではなく、トークン全体の字句によって決まります。たとえば1/2は全体が分数リテラルとしてパースされ、単独の/はただの名前としてパースされます。
名前はワードか束縛のどちらか、両方にはなれない
CoreワードまたはUserワードがすでに持っている名前を束縛することはできず、現在束縛されている名前でワードを定義することもできません。どちらの方向も拒否されるので、ある名前がどちらを経由して解決されたのかを考える必要は決してありません。W、DX、LIMITのような短い使い捨ての名前は、ここではまさにふさわしいものです。ワード名と違って命名規則の警告の対象にもなりません。これらはローカルであり、そのスコープは目の前の画面の中に収まっているからです。
名前解決と再定義
名前はCoreかUserのどちらかで解決され、UserがCoreを覆い隠すことは決してありません。したがってワードの名前が、そのワードの住所のすべてです。修飾された形もなく、選ぶべき辞書もなく、裸の名前が曖昧になる余地もありません。名前は大文字・小文字を区別しないので、add10とADD10は同じワードです。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 10 ADD ] 'ADD10' DEF
5 ADD10 |
15/1 |
定義済みのワードは、その名前で参照される。 |
[ 1 ] 'ADD' DEF |
error | Coreは封印されているので、Coreの名前を奪うことはできない。 |
[ 10 ADD ] 'ADD10' DEF
[ 20 ADD ] 'ADD10' DEF
5 ADD10 |
25/1 |
再定義は、唯一のUserエントリを束縛し直す。ただし他のワードがそれに依存している場合は、DEFが拒否する。 |
2.4.1 束縛(BIND)
BINDで名前を与える
スタックは一度に1つの値しか手渡してくれませんが、書く価値のあるたいていの式は、1つの値を2回以上使います。残差は二乗誤差と勾配の両方に使われ、閾値は比較と報告の両方に使われ、座標の組は和と差の両方に使われます。Ajisaiのワードは読んだ値を必ず消費するので、2回目に使う値はスタックには残っていません。
そこでBINDは値に名前を与えます。値と名前を取って両方を消費し、それ以降その名前がすなわちその値になります。好きなだけ、好きな順序で使えます。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 4 9 ] [ 'A' 'B' ] BIND A B ADD A B SUB 2 COLLECT |
[ 13/1 -5/1 ] |
1つのベクトルから2つの名前。それぞれを2回読む。スタックだけで書こうとすると行列と畳み込みが必要になる。 |
名前が1つなら値全体を受け取ります。複数なら、同じ長さのベクトルを位置ごとに分解します。長さは正確に一致しなければならず、一致しなければBINDはエラーを送出します。黙って通してしまうと、ベクトルの方が長いときは末尾が捨てられ、短いときはどれかの名前に何も対応しないという、書いた本人の意図とは違う結果になってしまうからです。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
5 'T' BIND T T MUL |
25/1 |
名前1つの場合。GETのインデックスと同様、[ 'T' ]と書いても同じ意味になる。 |
[ ]の中の名前は値ではなくSymbol
束縛で戸惑いやすいのは、ここ1箇所だけです。自分のプログラムで詰まる前に、目を通しておいてください。ベクトルリテラルの中では、裸の名前はSymbol――何かが実行するまではデータ――を表し、参照されることはありません。束縛された名前も例外ではありません。したがって3 'K' BINDの後、[ K ]は[ 3/1 ]ではなく、SymbolKを保持する要素数1のベクトル[ K ]です。
これが人を戸惑わせるのは、このリファレンスのイディオムが0 GETや3 TAKEのように書かれているため、自然な次の一手がXS [ K ] TAKEになってしまうからです。そしてこれは失敗します。SymbolはTAKEが期待する整数ではないからです。意図を表す2つの書き方があります。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
3 'K' BIND [ 1 2 3 4 5 ] K TAKE |
[ 1/1 2/1 3/1 ] |
名前を裸のまま書く。個数やインデックスは単なる数でよいので、何も包む必要はない。 |
3 'K' BIND K 1 COLLECT |
[ 3/1 ] |
本当に要素数1のベクトルが欲しいときは、組み立てる。COLLECTは値を読むが、[ ]はSymbolを読む。 |
名前が届く範囲
束縛は、それを作ったフレームに属し、そのフレームとまったく同じだけ生きます。フレームとは、ワードの本体か、直接入力した場合は実行そのものです。ここから2つの規則が導かれ、この2つで話のすべてです。
名前は、そのフレームの中に書かれたブロックにも届きます。MAP、FILTER、FOLD、EXECが実行するブロックは、名前にとって新しいフレームではないので、周囲にある名前をそのまま読めます。これらのワードが強制する隔離はスタックについてのものであり、名前はスタックの上にはありません。隔離の詳細はブロックと評価文脈を参照してください。これにより、述語はプログラムが実行時にしか知り得なかった値に対してテストできます。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
3 'LIMIT' BIND [ 1 5 2 9 ] [ LIMIT LT ] FILTER |
[ 1/1 2/1 ] |
ブロックはLIMITが束縛されている場所に書かれているので、LIMITと書ける。 |
名前は、呼び出した先のワードの中までは届きません。ワードの意味は、そのオペランドと辞書だけで決まり、それ以外の何ものでもありません。これがワードを単体で読めるようにしています。本体が読むのは自分自身の束縛だけであり、呼び出し元の束縛を読むことは決してありません。中でその値が必要なら、オペランドとして渡してください。それこそがオペランドの役目です。これを誤ると、未知のワードとして報告されるのではなく、はっきりとそう告げられます。
束縛を行うワードは、呼び出し元に対して何の名前も残しません。また実行の束縛は、その実行が終われば消えます。これは意図的です。フレームより長く生き延びる名前があれば、それは辞書の隣にある、編集可能な第2の名前空間になってしまうからです。ここには名前空間は1つしかありません。保持しておきたいデータには、[ [ 1 2 3 ] ] 'XS' DEFのようにワードを定義してください。これはまさに辞書が本来するべき仕事です。
2.4.2 ワード(DEF)
ワードは辞書に登録された名前付きの操作です。名前で呼び出されると、スタックからオペランドを消費し、契約どおりの結果(あれば)を積みます。組み込みワードはその働きごとに族に分かれ、族ごとの頁の最後に各ワードの契約があります。個々のワードが、このリファレンスの最後の単位です。
操作はすべてワード
Ajisaiの字句が区別するのは、数・文字列・名前と、[ ]の1組だけです。予約語はありません。算術も比較も、分岐・反復・定義・束縛も、値を1つ積むTRUE FALSE NILでさえ、英語名を1つ持つ普通のワードとして辞書の中にあり、名前で呼び出されます。
| 役割 | ワード |
|---|---|
| 算術 | ADD SUB MUL DIV |
| 比較 | EQ LT GT |
| 論理 | AND NOT |
| 真偽値と不在 | TRUE FALSE NIL |
| 選択 | SELECT |
| 反復 | MAP FILTER FOLD SCAN |
| 定義と束縛 | DEF BIND |
すべてのワードの一覧は組み込みワードにあります。
辞書とワードの同一性
2つの階層: CoreとUser
辞書には2つの階層があり、それがすべてです。Coreは78個の正準なワードを保持し、封印されています。Coreの名前を再定義したり削除したりすることはできません。Userは、DEFで定義したすべてを保持します。
この78個のワードは、48語のSemantic Kernelと30語のStandardワードに分かれます。Kernelのワードは、ある種類の値を組み立てたり観察したりするものか、何かを行う唯一の明示的な方法であるものです。後者にはコードブロックの実行、NILからの回復、辞書の変更、出力が含まれます。Standardのワードは、SUB、MIN、SORT、TRIMのように、頻出する発想に1つの固定された名前と契約を与えます。この区分は、あるワードが設計の中のどこに位置するかを教えてくれるだけであり、それを使う方法には何も変えません。78個すべては同じ1つのフラットな辞書の中にある普通のCoreワードであり、平易な名前で参照でき、契約・例・エラー条件も同じです。各ワードの階層は組み込みワードの一覧と各頁の契約にあり、そのワードを調べたときにも表示されます。
DEFで新しいワードを作る
新しいワードは、コードブロックと名前から定義されます:[ body ] 'NAME' DEF。本体は、そのワードが呼ばれるたびに実行されます。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ [ 10 ] ADD ] 'ADD10' DEF
5 ADD10 |
[ 15/1 ] |
ADD10を定義し、それを呼び出す。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 2 MUL ] 'DBL' DEF
[ 1 2 3 ] [ DBL ] MAP |
[ 2/1 4/1 6/1 ] |
ユーザー定義ワードは、引用された名前によって高階ワードと組み合わさる。 |
DEFが検査すること、しないこと
DEFは本体を保存するだけで、その中の名前を解決しません。存在しないワードを呼び出す本体を持つ定義は受け入れられ、定義済みとして報告されます。失敗が表面化するのは、そのワードが最初に呼び出されたときです。[ TOTALL 1 ] 'FOO' DEFは成功し、FOOを実行して初めてUnknown word: TOTALLが報告されます。
これは検査漏れではなく設計上の選択です。名前は呼び出し時に解決されます。これにより、あるワードが自分より後に定義される別のワードを呼び出すことができ、定義を依存関係に合わせて並べ替える必要は決してありません。
その代償として、本体の中の誤字は、何かがそれを呼び出すまで静かに潜んでいます。これを事前に検出できるのがヘッドレスCLIです。ajisai checkは何も実行せずにファイル中のすべての名前を解決し、未知のものを報告します。ajisai check --contractはさらに宣言されたワード契約も検証します。Playgroundにはこれに相当するものがなく、アプリの中では、ワードを呼び出すこと自体が検査になります。
ワードは自分自身を呼び出せない
User辞書の参照グラフは循環しません。[ REC ] 'REC' DEFのように、定義しようとしているワード自身の名前を本体が含む場合、DEFはエラーで拒否します。名前がどこに書かれていても関係ありません。本体の中に直接書かれていても、MAPやEXECに渡す[ ]ブロックの内側だけに書かれていても同じです。
これは間接的な循環にも及びます。AがBを呼び出すよう定義し、続けてBがAを呼び出すよう定義しようとすると、その2つ目のDEFが拒否されます。AはすでにBという名前を(まだ存在しなくても)本体に含んでおり、そこへBからAへの参照を足すとAに戻る循環が閉じるからです。すでに存在する言葉を新しい定義が呼び出すこと自体は自由です。禁じられているのは、今まさに定義しようとしているワードへ、その連鎖が戻ってくることだけです。
反復はワードが自分自身を呼び出すことでは表現できません。すでに有限なVectorに対するMAP FILTER FOLD SCANだけが反復の手段です。User辞書に循環がなければどのワードも自分自身を呼び出せないため、すべての評価は構造的に有限です。これは実行時のステップ予算(実行時エラーで扱います)より強い性質で、予算はなお1回の評価のコストを制限しますが、予算を大きくしても止まらないプログラムというもの自体が存在しません。
ワードの管理
DELは、引用された名前でユーザー定義ワードを削除します。依存関係は追跡されており、他のワードが依存しているワードはまったく削除できません。したがって宙ぶらりんの参照は起こり得ません。先に依存している側を削除してください。
ワード名は大文字・小文字を区別しないので、add10とADD10は同じワードです。慣例としてAPPLY-GAINのような動詞-目的語の形に従います。名前がどのように定義と対応付けられるかは、辞書とワードの同一性で扱います。
ワードを調べる
カーソルをワードの上に置いてCtrl+Alt+Lを押すと、PlaygroundはOutput領域に辞書から答えます。Coreワードならそのリファレンス項目、自分で定義したワードならそのDEFを、どちらも同じように「読むための文章」として表示します。エディタに戻して編集する形ではありません。これはプログラムが行うことではなく、エディタが行うことです。何も積まれず、何も消費されず、答えは値ではなく文章なので、プログラムがここで呼び出せるワードはありません。
ショートカットのみで存在する操作はほかに3つあります。ResetのCtrl+Alt+Enter、Stack clearのCtrl+Alt+S、Editor clearのCtrl+Alt+Eで、それぞれセッション全体、スタック、この入力欄のテキストを破棄します。これら4つはいずれも入力して実行したりプログラムから呼んだりできません。RESETやLOOKUPという名前のワードがあったとしても、それはほかの名前と同じ普通のワードです。
ワードの同一性
すべてのワードはコンテンツ同一性を持ちます。これは、正規化された定義と、それが呼び出すワードたちの同一性にわたるダイジェストです。同じ同一性を持つ2つの定義は、名前が何であれ同じワードであり、定義を変更すると、それに依存するすべてのものの同一性が変わります。これが、DEFとDELが、他の何かがまだ参照しているワードを乱すことを拒否する理由です。依存しているのはワードに対してであって、綴りに対してではないからです。
辞書と契約を言語の中から読む: DIGEST・CONTRACT
2つのワードが、機械がすでに辞書について知っていることを言語の中から読みます。ワードを名指すオペランドはSymbol、つまり[ ]の中に書いた裸の名前であって、文字列ではありません(CONTRACTはコードブロックも受けます)。'ADD' CONTRACTはnotASymbolのエラーです。テキストから名前を引けるワードがあると、「どのワードもテキストをSymbolに変えない」という、DEF時の循環検査が依拠している事実が崩れるからです。
DIGESTは、ワードを名指すSymbolにはそのコンテンツ同一性を、それ以外の値にはその意味内容のダイジェストを、テキストで答えます。同じダイジェストは同じもの、違うダイジェストは何も意味しません。CONTRACTは、ワードの契約をRecordで答えます。Coreワードなら登録済みの契約(inputs・outputs・nil・projection・errors・purity・costなど)、自分で定義したワードなら本体から推論した契約です。コードブロックをそのまま渡せば、その契約を走らせずに推論します——実行前検査(ajisai check --contract)を言語の中から呼ぶ形であり、confidenceとgapsが「検証できた/できない」を値として運びます。走らせる前に費用級を問える、という性質はここから来ます。
SymbolがCoreかUserのワードとして解決するかは、CONTRACTが契約を答えるかどうかで分かります。ワードでない名前には理由付きのNILが返るので、sym CONTRACT NIL? NOTがその問いになります。NIL?はオペランドを消費するので、残るのは真偽値だけです。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ TWICE ] 0 GET CONTRACT NIL? NOT |
FALSE |
未定義の名前には契約がないのでFALSE。 |
'ADD' CONTRACT |
error | 文字列は名前ではない(notASymbol)。 |
[ 2 MUL ] 'TWICE' DEF [ 2 MUL ] 'DOUBLE' DEF [ TWICE ] 0 GET DIGEST [ DOUBLE ] 0 GET DIGEST EQ |
TRUE |
同一性は内容に対して、綴りに対してではない。 |
8 SQRT DIGEST 2 SQRT 2 SQRT ADD DIGEST EQ |
TRUE |
値は意味内容でダイジェストされる。 |
[ DIV ] 0 GET CONTRACT 'projection' GET |
[ 'divisionByZero' ] |
登録済みの契約を、走らせずに読む。 |
[ 42 PRINT ] CONTRACT 'effects' GET |
[ 'consoleWrite' ] |
ブロックを渡すと走らせずに契約を推論する。何も出力されない。 |
[ NOPE ] 0 GET CONTRACT NIL-REASON |
'notFound' |
ワードでない名前は理由付きのNIL。 |
組み込みワード
正準な語彙の全78語を族ごとにまとめたものです(spec/words.jsonから生成)。48語がSemantic Kernel、30語がStandardワードで、どれも平易な名前で参照できる普通のCoreワードです。階層は設計の中での位置を示すだけです(辞書とワードの同一性)。ワードを選ぶとその契約(構文・スタック効果・NIL方針・ERROR条件など)へ、頁名を選ぶとサンプル付きの解説へ移ります。
契約: 辞書と名前
CONTRACT Semantic Kernel
ワードまたはブロックの契約をRecordで答える。[ DIV ] 0 GET CONTRACT 'partiality' GETは'projecting'。CoreワードをSymbolで名指せば、spec/words.jsonに登録された記録で(LANG.CONTRACT.REGISTRY)、キーはレジストリ自身のフィールド名name vocabularyTier inputs outputs nilPolicy projection errorWhen partiality purity determinism cost effects。だから[ DIV ] 0 GET CONTRACT 'cost' GETは実行する前にワードの費用級を問う。UserワードをSymbolで名指すか、コードのブロックを渡せば、何も実行せずに推論した契約で(言語の外からajisai check --contractが行うのと同じ推論)、キーはinputs outputs partiality purity determinism cost effects(同じキーで同じ語彙なので両者を直接比べられる)とconfidence gaps。confidenceとgapsは検査自身の三分法(LANG.CONTRACT.CHECK)をデータとして運ぶので、解決できない依存はERRORではなく答えの中の欠落になる。ブロックは決して評価されないので、[ 42 PRINT ] CONTRACTは出力せずにeffectsの下にconsoleWriteを報告する。どのワードも名指さないSymbolはnotFoundを投影し、SymbolでもブロックでもないオペランドはERROR(notASymbol)。
- 構文
[ ADD ] 0 GET CONTRACT- スタック効果
- [ symbol | code ] -> [ record ]
- スタック
- 入力 1 → 出力 1
- オペランド
control(LANG.FAILURE.PASSTHROUGH)- NIL方針
createsNil; 投影:symbolNamesNoWord→notFound- 部分性
projecting- 純粋性 / 決定性
pure/stateRelative- 作用
- なし
- ERROR条件
notASymbol- 条項
LANG.CONTRACT.REGISTRY,LANG.CONTRACT.CHECK,LANG.DICTIONARY.RESOLUTION,LANG.SOURCE.CODE,LANG.FAILURE.PROJECT
BIND Semantic Kernel
値に名前を付け、それを作ったフレームの残りで使えるようにする。5 'N' BIND N N ADDは10、[ 1 2 ] [ 'A' 'B' ] BIND B A SUBは1。名前が1つなら値全体を受け取り、複数なら同じ長さのベクトルを位置ごとに分解する。両オペランドとも消費され、その後名前を書くたびに、何回でもその値が積まれる。束縛はそのフレームの中に書かれたブロックには届き、そこから呼ばれたワードには決して届かず、フレームが終わると終わる。CoreワードかUserワードがすでに持つ名前は拒否されるので、名前はワードか束縛のどちらかであって、両方にはならない。直接または他の束縛を通して自分自身の束縛を名指す値は、DEFと同じ規則でselfReferentialDefinitionとして拒否される(LANG.DICTIONARY.ACYCLIC)。
- 構文
[ 1 2 3 ] 'XS' BIND- スタック効果
- [ x ] [ name... ] -> [ ]
- スタック
- 入力 2 → 出力 0
- オペランド
element,control(LANG.FAILURE.PASSTHROUGH)- NIL方針
consumeNil; 投影: なし- 部分性
partial- 純粋性 / 決定性
pure/stateRelative- 作用
- なし
- ERROR条件
nonText,nameConflict,shapeMismatch,invalidName,selfReferentialDefinition- 条項
LANG.SOURCE.FRAME,LANG.DICTIONARY.RESOLUTION
DEF Semantic Kernel
本体と名前からUserワードを定義する。[ 2 MUL ] 'DOUBLE' DEF 5 DOUBLEは10。本体はCoreワードとUserワードを呼べるが、直接にも他を通しても、定義しようとしているワード自身は決して呼べない(selfReferentialDefinition、LANG.DICTIONARY.ACYCLIC)。Userワードの再定義は、他がまだそれを呼んでいなければそれを置き換える(definitionConflict)。Coreワードの名前はprotectedWord、束縛が持つ名前はnameConflict、1つのトークンとして書けない名前はinvalidName。
- 構文
[ 2 MUL ] 'DOUBLE' DEF- スタック効果
- [ body ] [ name ] -> [ ]
- スタック
- 入力 2 → 出力 0
- オペランド
control,control(LANG.FAILURE.PASSTHROUGH)- NIL方針
rejectNil; 投影: なし- 部分性
partial- 純粋性 / 決定性
effectful/stateRelative- 作用
dictionaryWrite- ERROR条件
invalidName,protectedWord,nameConflict,definitionConflict,selfReferentialDefinition,nonText,invalidDefinitionBody- 条項
LANG.DICTIONARY.RESOLUTION,LANG.DICTIONARY.MUTATION,LANG.DICTIONARY.ACYCLIC
DEL Semantic Kernel
辞書からUserワードを削除する。[ 1 ] 'W' DEF 'W' DEL [ W ] 0 GET CONTRACT NIL?はTRUE。名前がもうワードを名指さないからである。Coreワードは拒否され(protectedWord)、どのUserワードも持たない名前はwordNotFound、他のUserワードがまだ呼んでいるワードは、それらを先に削除するまでdefinitionConflict。
- 構文
[ 1 ] 'W' DEF 'W' DEL- スタック効果
- [ name ] -> [ ]
- スタック
- 入力 1 → 出力 0
- オペランド
control(LANG.FAILURE.PASSTHROUGH)- NIL方針
rejectNil; 投影: なし- 部分性
partial- 純粋性 / 決定性
effectful/stateRelative- 作用
dictionaryDelete- ERROR条件
wordNotFound,protectedWord,nonText,definitionConflict- 条項
LANG.DICTIONARY.RESOLUTION,LANG.DICTIONARY.MUTATION
DIGEST Semantic Kernel
ワードのコンテンツ同一性、または値の意味内容のダイジェストをテキストで答える。8 SQRT DIGEST 2 SQRT 2 SQRT ADD DIGEST EQはTRUE。UserワードをSymbolで名指せばそのワードのコンテンツ同一性(正規化された定義と、それが呼ぶワードたちの同一性にわたるダイジェストで、辞書がすでに保持しているもの。LANG.DICTIONARY.MUTATION)を、CoreワードをSymbolで名指せばその封印されたワードの固定の同一性を答える。それ以外の値は、何も名指さないSymbolを含め、その意味内容のダイジェストを答える。EQが1つの値とみなす2つの値は、組み立て方によらず同じダイジェストになるので、8 SQRT DIGESTは2 SQRT 2 SQRT ADD DIGESTと等しく、NILは理由によってダイジェストされる。等しいダイジェストは1つのものを意味し、等しくないダイジェストは何も意味しない。
- 構文
[ ADD ] 0 GET DIGEST- スタック効果
- [ x ] -> [ digest ]
- スタック
- 入力 1 → 出力 1
- オペランド
element(LANG.FAILURE.PASSTHROUGH)- NIL方針
consumeNil; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/stateRelative- 作用
- なし
- 条項
LANG.DICTIONARY.MUTATION,LANG.VALUES.DENOTATION,LANG.VALUES.EXACT,LANG.DICTIONARY.RESOLUTION
2.4.2.1 論理・比較・算術(AND, EQ, ADD)
算術
算術ワードは、ベクトル同士の要素ごとの演算として機能します。各ワードは英語名1つだけで書き、記号の綴りは持ちません。
算術ワード一覧
| ワード | 意味 |
|---|---|
ADD | 加算 |
SUB | 減算 |
MUL | 乗算 |
DIV | 厳密な除算 |
ブロードキャスト規則
ブロードキャストは、単に要素数1のベクトルを拡張するだけの規則ではなく、多次元ベクトルの軸全体に適用される一貫した規則です。
- 軸の対応付け: 2つのベクトルは最も内側の軸から順に対応付けられます。
- 長さ1の軸の拡張: 短いベクトルの届かない軸は長さ1とみなされ、長さ1の軸は相手側の長さに合わせて繰り返し使われます。
[ 1 2 3 ] [ 10 ] MULは[ 10/1 20/1 30/1 ]に、[ [ 1 2 ] [ 3 4 ] ] [ 10 20 ] MULは[ [ 10/1 40/1 ] [ 30/1 80/1 ] ]になります。 - 形状の不一致: 軸の長さが一致せず、どちらも1でない場合はエラーになります。
[ 1 2 3 ] [ 10 20 ] MULは長さ3と2を対応付けられないため、エラーです。 - スカラーの適用: スカラー値は軸を必要としません。不揃いなものを含め、ベクトルがどう入れ子になっていてもすべての要素と組み合わさります。
- 矩形条件: 2つのベクトル同士がブロードキャストできるのは、両方とも矩形、つまり各軸の長さが揃っているときだけです。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 1 2 3 ] [ 4 5 6 ] ADD |
[ 5/1 7/1 9/1 ] |
要素ごとの加算。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 1 2 3 ] [ 10 ] MUL |
[ 10/1 20/1 30/1 ] |
単一要素のベクトルは、もう一方のオペランド全体へブロードキャストされる。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
7 'A' BIND 3 'B' BIND A A B DIV FLOOR B MUL SUB |
1/1 |
7を3で割った余り。剰余は専用のワードを持たず、a - floor(a/b)·bをそのまま書く。 |
その他の数値演算ワード
基本四則演算に加えて、次の数値演算ワードが用意されています。
- 符号操作: 符号反転は
x -1 MUL、絶対値はx 'X' BIND X X MUL SQRTと書きます。 - 極値:
MIN(最小値)、MAX(最大値) - 平方根:
SQRT - 丸め処理:
FLOOR(切り捨て)、ROUND(四捨五入)。切り上げはx -1 MUL FLOOR -1 MULと書きます。 - 格子への丸め:
x d MUL 1/2 ADD FLOOR d DIVは値を1/dの倍数に丸めます。ループ処理や反復計算で分母の肥大化を止めるのに使います。詳しくは反復のコストを参照してください。
ゼロ除算が発生してもプログラムはクラッシュせず、該当する要素だけがNILになります。詳しくはNILの規則を参照してください。
累乗と、整数・有理数を読む: POW・GCD・RATIO
x y POWは、厳密な体の中で答えます。整数の指数なら底の層に留まり(2 10 POWは1024、2 SQRT 2 POWは2)、非負の有理数の底に対する指数p/2なら平方根のべきとして体の中に留まります(2 1/2 POWは2 SQRTとまったく同じ値)。それ以外の指数——分母が1でも2でもない指数、無理数の底に対するp/2、無理数の指数——は体の外に出るので、答えが有理数になる場合(8 1/3 POW)も含めてdomainMissを投影します。0の負のべきはdivisionByZero、負の底のp/2べきは実数の答えを持たないのでdomainMiss、機械が保持できないほど大きな指数はspaceExhaustedを投影します。
GCDは2つの整数の最大公約数を非負で答えます。ユークリッドの互除法は入力依存の反復であり、すでにあるベクトルの上でしか反復しないこの言語では定義で書けません——機械はすべての有理数を既約に保つためにこの計算を常に行っており、このワードはそれを見えるようにするだけです。RATIOは有理数を既約の分子と分母の2要素ベクトルに開きます。答えがベクトルなので、算術はそのまま持ち上がります。整数でない引数(GCD)や無理数(RATIO)はdomainMissを投影します。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
2 10 POW |
1024/1 |
整数指数は底の層に留まる。 |
2 1/2 POW 2 SQRT EQ |
TRUE |
指数p/2は体の中:SQRTと同じ値。 |
8 1/3 POW NIL-REASON |
'domainMiss' |
立方根は体の外。答えが有理数でも投影する。 |
-2 1/2 POW NIL-REASON |
'domainMiss' |
負の底のp/2べきに実数の答えはない。 |
12 18 GCD |
6/1 |
最大公約数。 |
6/4 RATIO |
[ 3/1 2/1 ] |
既約の分子と分母。 |
2 SQRT RATIO NIL-REASON |
'domainMiss' |
無理数に分子はない。 |
ベクトルへの自動リフティング
数値演算ワードはいずれもベクトルに対して自動的に持ち上がり、ADDと同じ規則でブロードキャストします。負の値を0で打ち切るレクティファイアは[ -1 2 -3 ] 0 MAXと書けて[ 0/1 2/1 0/1 ]になり、特徴量ごとの標準偏差は分散のベクトルに対する1回のSQRTで求まり、2つのベクトルの要素ごとの小さい方は[ 1 5 3 ] [ 4 2 6 ] MINで得られます。いずれもMAPもブロックも不要です。これらに手を伸ばすのは、演算そのものが数値ワードでないときだけにしましょう。SQRTの定義域から外れたレーンはそこだけがNILになるので、[ 4 -1 ] SQRTはベクトル全体の失敗ではなく[ 2/1 NIL ]になります。この持ち上げは数値演算に限りません。文字列や真偽値など一つの値として読むオペランドを持つワードは、そこにベクトルやRecordを渡すと同じ規則で要素ごとに適用されます([ 'ab' 'c' ] UPPERは[ 'AB' 'C' ])。1要素のベクトルも常にベクトルとして扱われ、その要素とは見なされません。
比較と真偽値
比較ワードはEQ・LT・GTの3つです。結果は真偽値であり、それ自体が独自の種類の値であって、数ではありません。残りの3つの関係はNOTで書きます。「以下」はa b GT NOT、「以上」はa b LT NOT、「等しくない」はa b EQ NOTです。
比較ワード一覧
| ワード | 意味 |
|---|---|
EQ | 等しい |
LT | より小さい |
GT | より大きい |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
5 3 GT |
TRUE |
決定できた比較は真偽値を返す。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
1 1 GT NOT |
TRUE |
「1は1以下」。比較の結果は決定できた真偽値なので、NOTで否定すれば残りの関係になる。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
TRUE 1 EQ |
FALSE |
真偽値は数の1ではない。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
2 SQRT 2 SQRT SUB 0 EQ |
TRUE |
\(\sqrt{2} - \sqrt{2}\)はちょうど0。平方根およびその和・差・積・商は厳密に比較されるため、比較は予算に頼らずに決定できる。 |
厳密な比較
比較は、有理数も平方根も含め、ワードが組み立てられるすべての値にわたって厳密に決定します。したがって比較は常に有限時間でTRUEかFALSEを返します。
論理
AjisaiにはANDとNOTという2つの論理ワードがあり、真偽値TRUEとFALSEに対する論理演算を行います。どちらも、ほかのワードと同じく綴りのまま書きます。論理和はド・モルガンの法則どおりa NOT b NOT AND NOTと書きます。
NILはそのまま伝わる
NILは真偽の位置では「不明」として読まれ、強いクリーネ論理で合成されます。もう一方のオペランドだけで答えが決まる場合はその答えになり(FALSEを含むANDはFALSE)、決まらない場合はNILがその理由を保ったまま結果に残ります。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
TRUE FALSE AND |
FALSE |
確定した2つの値の論理AND。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
TRUE NOT FALSE NOT AND NOT |
TRUE |
確定した2つの値の論理和。ANDとNOTの組み合わせで書く。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
TRUE NOT |
FALSE |
確定した値の論理NOT。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
1 0 DIV TRUE AND |
NIL |
左のオペランドがNILになったので、その不在が結果まで伝わる。必要な箇所で代わりの値を選んで回復させる。 |
契約: 論理
TRUE Semantic Kernel
真偽値のTRUE。TRUEは1 1 EQである。これは真偽値であって数の1ではない(LANG.VALUES.DISJOINT)ので、TRUE 1 EQはFALSEになる。
- 構文
TRUE- スタック効果
- -> [ TRUE ]
- スタック
- 入力 0 → 出力 1
- NIL方針
preserveReason; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- 条項
LANG.VALUES.TRUTH
FALSE Semantic Kernel
真偽値のFALSE。FALSEは1 2 EQである。これは真偽値であって数の0ではない(LANG.VALUES.DISJOINT)ので、FALSE 0 EQはFALSEになる。
- 構文
FALSE- スタック効果
- -> [ FALSE ]
- スタック
- 入力 0 → 出力 1
- NIL方針
preserveReason; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- 条項
LANG.VALUES.TRUTH
AND Semantic Kernel
強いクリーネの真理値表による論理積(LANG.VALUES.TRUTH)。TRUE FALSE ANDはFALSEで、ベクトルには要素ごとに働き、[ TRUE TRUE ] [ TRUE FALSE ] ANDは[ TRUE FALSE ]になる。ここで読まれたNILはUNKNOWNである。FALSEがあれば答えはFALSEに決まり(NIL FALSE ANDはFALSE)、それ以外はUNKNOWNのまま理由を保つ。論理和はa NOT b NOT AND NOTと書く。真偽値でもNILでもないオペランドはnonTruthValue。
- 構文
TRUE FALSE AND- スタック効果
- [ a ] [ b ] -> [ a AND b ]
- スタック
- 入力 2 → 出力 1
- オペランド
truth,truth(LANG.FAILURE.PASSTHROUGH)- NIL方針
kleeneAbsorbing; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonTruthValue,shapeMismatch- 条項
LANG.VALUES.TRUTH,LANG.COLLECTIONS.LIFT
NOT Semantic Kernel
強いクリーネの真理値表による否定(LANG.VALUES.TRUTH)。TRUE NOTはFALSEで、ベクトルには要素ごとに働き、[ TRUE FALSE ] NOTは[ FALSE TRUE ]になる。UNKNOWN(ここで読まれたNIL)は理由を保ったままUNKNOWNにとどまる。真偽値でもNILでもないオペランドはnonTruthValue。
- 構文
TRUE NOT- スタック効果
- [ a ] -> [ NOT a ]
- スタック
- 入力 1 → 出力 1
- オペランド
truth(LANG.FAILURE.PASSTHROUGH)- NIL方針
kleeneAbsorbing; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonTruthValue- 条項
LANG.VALUES.TRUTH,LANG.COLLECTIONS.LIFT
SELECT Semantic Kernel
すでに計算済みの2つの値から、真偽値で一方を選ぶ。'yes' 'no' TRUE SELECTは'yes'、'yes' 'no' FALSE SELECTは'no'。選択は要素ごとに行われる(LANG.COLLECTIONS.LIFT)ので、真偽値のベクトルは2つのベクトルをレーンごとに編み合わせ、1レーンのオペランドは相手の長さに合わせて繰り返し使われる。UNKNOWNのレーン(真偽の位置で読まれたNIL。理由は問わない)はどちらも選ばず、その不在をそのまま答えるので、理由は選択を越えて残る。どちらのオペランドもプログラムがすでに組み立てた値であり、SELECTは何も評価しない。それらを計算したものはSELECTより前に、ちょうど1回実行されている。
- 構文
[ 'yes' ] [ 'no' ] TRUE SELECT- スタック効果
- [ whenTrue ] [ whenFalse ] [ mask ] -> [ chosen ]
- スタック
- 入力 3 → 出力 1
- オペランド
element,element,truth(LANG.FAILURE.PASSTHROUGH)- NIL方針
kleeneAbsorbing; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonTruthValue,shapeMismatch- 条項
LANG.VALUES.TRUTH,LANG.COLLECTIONS.LIFT
契約: 比較
EQ Semantic Kernel
2つの値が同じ1つの値かどうか(LANG.VALUES.DENOTATION)。どう組み立てたかは問わない。1 1/1 EQはTRUE、2 SQRT 2 SQRT MUL 2 EQはTRUE、[ 1 2 ] [ 1 2 ] EQはTRUE。EQはオペランドを丸ごと比較し、要素ごとには持ち上がらない。両オペランドを読むので、他のデータオペランドと同じく、不在のオペランドがそのまま答えになる(LANG.FAILURE.PASSTHROUGH)。1 0 DIV 1 EQは、比較するものがないので、その不在がすでにそうであったUNKNOWNになる。ベクトルの中のNILは他と同じ1つの要素で、理由によって比較されるので、[ NIL ] [ NIL ] EQはTRUE。これはMEMBER?・INDEX-OF・UNIQUEが使う等価性でもある。不在かもしれない2つの値を素通しではなく比較したいときは、それぞれを先に1 COLLECTで包む。
- 構文
1 1 EQ- スタック効果
- [ a ] [ b ] -> [ TRUE | FALSE ]
- スタック
- 入力 2 → 出力 1
- オペランド
data,data(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- 条項
LANG.VALUES.TRUTH,LANG.VALUES.EXACT,LANG.VALUES.DENOTATION
LT Semantic Kernel
左の数が右より小さいかどうか。1 2 LTはTRUEで、ベクトルには要素ごとに働き、[ 1 5 ] 3 LTは[ TRUE FALSE ]。厳密な体の上の順序は、無理数を含めて常に決定する(LANG.VALUES.EXACT)。2 SQRT 3/2 LTはTRUE。順序があるのは数だけで、それ以外はnonNumeric。
- 構文
1 2 LT- スタック効果
- [ a ] [ b ] -> [ TRUE | FALSE ]
- スタック
- 入力 2 → 出力 1
- オペランド
leaf,leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonNumeric,shapeMismatch- 条項
LANG.VALUES.TRUTH,LANG.VALUES.EXACT,LANG.COLLECTIONS.LIFT
GT Semantic Kernel
左の数が右より大きいかどうか。2 1 GTはTRUEで、ベクトルには要素ごとに働き、[ 1 5 ] 3 GTは[ FALSE TRUE ]。厳密な体の上の順序は常に決定する(LANG.VALUES.EXACT)。順序があるのは数だけで、それ以外はnonNumeric。
- 構文
2 1 GT- スタック効果
- [ a ] [ b ] -> [ TRUE | FALSE ]
- スタック
- 入力 2 → 出力 1
- オペランド
leaf,leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonNumeric,shapeMismatch- 条項
LANG.VALUES.TRUTH,LANG.VALUES.EXACT,LANG.COLLECTIONS.LIFT
契約: 厳密な算術
ADD Semantic Kernel
厳密な和。1/3 1/6 ADDは1/2で、ベクトルにはブロードキャスト付きで要素ごとに働き(LANG.COLLECTIONS.LIFT)、[ 1 2 ] 10 ADDは[ 11 12 ]。何も丸められない。2 SQRT 2 SQRT ADDは8 SQRT。数でないものはnonNumeric。
- 構文
1 2 ADD- スタック効果
- [ a ] [ b ] -> [ a + b ]
- スタック
- 入力 2 → 出力 1
- オペランド
leaf,leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonNumeric,shapeMismatch- 条項
LANG.VALUES.EXACT,LANG.COLLECTIONS.LIFT,LANG.FAILURE.TRICHOTOMY
SUB Standard · shorthand
厳密な差(左から右を引く)。5 3 SUBは2で、ベクトルにはブロードキャスト付きで要素ごとに働き(LANG.COLLECTIONS.LIFT)、2 [ 1 2 3 ] SUBは[ 1 0 -1 ]。数でないものはnonNumeric。
- 構文
5 3 SUB- スタック効果
- [ a ] [ b ] -> [ a - b ]
- スタック
- 入力 2 → 出力 1
- オペランド
leaf,leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonNumeric,shapeMismatch- 条項
LANG.VALUES.EXACT,LANG.COLLECTIONS.LIFT,LANG.FAILURE.TRICHOTOMY
MUL Semantic Kernel
厳密な積。2/3 3/4 MULは1/2で、ベクトルにはブロードキャスト付きで要素ごとに働き(LANG.COLLECTIONS.LIFT)、[ 1 2 3 ] [ 10 ] MULは[ 10 20 30 ]。数でないものはnonNumeric。
- 構文
2 4 MUL- スタック効果
- [ a ] [ b ] -> [ a * b ]
- スタック
- 入力 2 → 出力 1
- オペランド
leaf,leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonNumeric,shapeMismatch- 条項
LANG.VALUES.EXACT,LANG.COLLECTIONS.LIFT,LANG.FAILURE.TRICHOTOMY
DIV Semantic Kernel
厳密な商(左を右で割る)。1 3 DIVは厳密に1/3。ベクトルにはブロードキャスト付きで要素ごとに働き(LANG.COLLECTIONS.LIFT)、[ 2 4 ] 2 DIVは[ 1 2 ]。除数が0のレーンはNIL(divisionByZero)を投影する。数でないものはnonNumeric。
- 構文
10 2 DIV- スタック効果
- [ a ] [ b ] -> [ a / b ]
- スタック
- 入力 2 → 出力 1
- オペランド
leaf,leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthroughThenProject; 投影:divisorEqualsZero→divisionByZero- 部分性
projecting- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonNumeric,shapeMismatch- 条項
LANG.VALUES.EXACT,LANG.COLLECTIONS.LIFT,LANG.FAILURE.TRICHOTOMY,LANG.FAILURE.PROJECT
FLOOR Semantic Kernel
その数を超えない最大の整数。7/3 FLOORは2、-7/3 FLOORは-3で、ベクトルには要素ごとに働く。無理数も厳密に切り捨てられる。2 SQRT FLOORは1。数でないものはnonNumeric。
- 構文
7/3 FLOOR- スタック効果
- [ x ] -> [ floor x ]
- スタック
- 入力 1 → 出力 1
- オペランド
leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonNumeric- 条項
LANG.VALUES.EXACT,LANG.COLLECTIONS.LIFT,LANG.FAILURE.TRICHOTOMY
ROUND Standard · algorithm
最も近い整数。ちょうど半分は0から遠い側へ。5/2 ROUNDは3、-5/2 ROUNDは-3で、ベクトルには要素ごとに働く。FORMATも最終桁を同じ規則で丸める。数でないものはnonNumeric。
- 構文
5/2 ROUND- スタック効果
- [ x ] -> [ round x ]
- スタック
- 入力 1 → 出力 1
- オペランド
leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonNumeric- 条項
LANG.VALUES.EXACT,LANG.COLLECTIONS.LIFT,LANG.FAILURE.TRICHOTOMY
MIN Standard · namedPattern
2つの数の小さい方。1 2 MINは1で、ベクトルにはブロードキャスト付きで要素ごとに働き、[ 3 1 ] 2 MINは[ 2 1 ]。数でないものはnonNumeric。
- 構文
1 2 MIN- スタック効果
- [ a ] [ b ] -> [ min ]
- スタック
- 入力 2 → 出力 1
- オペランド
leaf,leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonNumeric,shapeMismatch- 条項
LANG.VALUES.EXACT,LANG.COLLECTIONS.LIFT,LANG.FAILURE.TRICHOTOMY
MAX Standard · namedPattern
2つの数の大きい方。1 2 MAXは2で、ベクトルにはブロードキャスト付きで要素ごとに働き、[ -1 2 -3 ] 0 MAXは[ 0 2 0 ]。数でないものはnonNumeric。
- 構文
1 2 MAX- スタック効果
- [ a ] [ b ] -> [ max ]
- スタック
- 入力 2 → 出力 1
- オペランド
leaf,leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonNumeric,shapeMismatch- 条項
LANG.VALUES.EXACT,LANG.COLLECTIONS.LIFT,LANG.FAILURE.TRICHOTOMY
SQRT Semantic Kernel
非負の数の厳密な平方根。4 SQRTは2、2 SQRTは無理数そのもので、多重二次正規形で保持され丸めなしに比較される(LANG.VALUES.EXACT)ので、2 SQRT 2 SQRT MULは2。根号の中は平方因子を含まない形に簡約されるので、1つの数は組み立て方によらず1つの形を持つ。8 SQRTは2 SQRT 2 MULである。この簡約は根号の中を素因数分解し、実行の数値作業量に計上される。残りの作業量で分解できない根号の中身はresourceLimitExceeded。ベクトルには要素ごとに働き、負の数はNIL(domainMiss)を投影する。[ 4 -1 ] SQRTは[ 2 NIL ]で、2番目のレーンはその理由で不在になる。数でないものはnonNumeric。
- 構文
2 SQRT- スタック効果
- [ x ] -> [ sqrt(x) ]
- スタック
- 入力 1 → 出力 1
- オペランド
leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthroughThenProject; 投影:negativeScalar→domainMiss- 部分性
projecting- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonNumeric- 条項
LANG.VALUES.EXACT,LANG.COLLECTIONS.LIFT,LANG.FAILURE.TRICHOTOMY,LANG.FAILURE.PROJECT
POW Semantic Kernel
厳密なべき乗x y POW。ベクトルには要素ごとに働き、厳密な体の中で答える。整数の指数なら結果は底と同じ層に留まる。2 10 POWは1024、2 SQRT 2 POWは2、2 -1 POWは1/2。非負の有理数の底に対する指数p/2も体の中に留まる。2 1/2 POWは2 SQRTの答えとまったく同じで、2 3/2 POWは2 2 SQRT MUL。負のyによる0 y POWはdivisionByZeroを投影する。p/2乗の負の底は実数の値を持たずdomainMissを投影する。それ以外の指数(分母が1でも2でもないもの、無理数の底に対するp/2、無理数の指数)も体の外に出るのでdomainMissを投影する(8 1/3 POW、2 2 SQRT POW)。機械が具体化できる範囲を超える指数はspaceExhaustedを投影する。体を組み立てるワードは今もSQRTであり、POWはその糖衣ではない。
- 構文
2 10 POW- スタック効果
- [ x ] [ y ] -> [ x^y ]
- スタック
- 入力 2 → 出力 1
- オペランド
leaf,leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthroughThenProject; 投影:zeroBaseNegativeExponent,negativeBaseFractionalExponent,exponentOutsideTheField,exponentTooLargeToMaterialize→divisionByZero,domainMiss,spaceExhausted- 部分性
projecting- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonNumeric,shapeMismatch- 条項
LANG.VALUES.EXACT,LANG.COLLECTIONS.LIFT,LANG.FAILURE.TRICHOTOMY,LANG.FAILURE.PROJECT
GCD Standard · operational
2つの整数の非負の最大公約数。ベクトルには要素ごとに働く。12 18 GCDは6、0 0 GCDは0。ユークリッドの互除法は入力に依存する反復で、すでにあるベクトルの上でしか反復しない言語では定義として書けない。機械はすべての有理数を既約に保つためにこれをすでに実行しており、このワードはそれを見えるようにするだけである。整数でないオペランド(分数や無理数)はdomainMissを投影する。
- 構文
12 18 GCD- スタック効果
- [ a ] [ b ] -> [ gcd ]
- スタック
- 入力 2 → 出力 1
- オペランド
leaf,leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthroughThenProject; 投影:nonIntegerOperand→domainMiss- 部分性
projecting- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonNumeric,shapeMismatch- 条項
LANG.VALUES.EXACT,LANG.COLLECTIONS.LIFT,LANG.FAILURE.TRICHOTOMY,LANG.FAILURE.PROJECT
RATIO Standard · operational
有理数を既約の分子と分母に開き、分母を正とする2要素のベクトルで答える。6/4 RATIOは[ 3 2 ]、-3 RATIOは[ -3 1 ]で、ベクトルには要素ごとに働く。この言語は厳密な有理数を掲げており、これがその2つの部分を読み戻すワードである。答えはベクトルなので、算術は他のベクトルと同じようにその上へ持ち上がる。無理数(2 SQRT)には分子がなくdomainMissを投影する。
- 構文
6/4 RATIO- スタック効果
- [ q ] -> [ [ numerator denominator ] ]
- スタック
- 入力 1 → 出力 1
- オペランド
leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthroughThenProject; 投影:irrationalOperand→domainMiss- 部分性
projecting- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonNumeric- 条項
LANG.VALUES.EXACT,LANG.COLLECTIONS.LIFT,LANG.FAILURE.TRICHOTOMY,LANG.FAILURE.PROJECT
2.4.2.2 ベクトル操作(GET, LENGTH)
ベクトルのインデックスは0始まりです。負のインデックスは末尾から数えます。GETやLENGTHのような読み取り系のワードは、他のすべてのワードと同様、読み取ったベクトルを消費します。元のベクトルを後でも使いたい場合は、BINDで名前を付けてから読み取ってください。
GETで読み取る
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 10 20 30 ] 0 GET |
10/1 |
インデックス0は最初の要素。ベクトルとインデックスの両方が消費される。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 10 20 30 ] -1 GET |
30/1 |
負のインデックスは末尾から数える。 |
答えの形はインデックスの形に従う
GETのインデックスは、裸の整数なら要素そのものを、ベクトルなら要素ごとに持ち上がって、インデックスが指定した順序での選択結果のベクトルを返します。したがって、いくつかの位置を選び出すこと、並べ替えること、逆順にすることは、いずれも1回の呼び出しで済み、何も指さないインデックスは残りを台無しにするのではなく、その位置にNILを残すだけです。
答えの形はインデックスの形に従います。インデックスのベクトルは、要素が1つでもベクトルのままなので、答えも常にベクトルになります。分割の左側やメンバーが1人しかいないクラスのように、計算した選択がたまたま1つの位置に絞られても、ベクトルを想定して書いたLENGTHやFOLDはそのまま動きます。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 10 20 30 40 ] [ 2 ] GET |
[ 30/1 ] |
インデックスは1つだが、ベクトルで渡したので結果もベクトル。[ 2 0 ]なら[ 30/1 10/1 ]になり、後続のコードは2つの形を扱う必要がない。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 10 20 30 40 ] [ 2 0 ] GET |
[ 30/1 10/1 ] |
答えはベクトルではなくインデックスに従う。これは収集であり、同じ呼び出しの形で並べ替えや置換もできる。 |
その他の基本操作
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 1 2 3 ] LENGTH |
3/1 |
要素数。ベクトルは消費される。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 1 2 3 ] 'V' BIND V V LENGTH |
[ 1/1 2/1 3/1 ] 3/1 |
名前を付けておけば、元のベクトルが読み取りを生き延びる。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 1 2 ] [ 3 4 ] CONCAT |
[ 1/1 2/1 3/1 4/1 ] |
2つのベクトルを連結する。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 1 2 3 ] REVERSE |
[ 3/1 2/1 1/1 ] |
要素の順序を逆にする。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
1 5 RANGE |
[ 1/1 2/1 3/1 4/1 5/1 ] |
開始から終了までを含む整数の並び。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
0 5 RANGE 2 MUL |
[ 0/1 2/1 4/1 6/1 8/1 10/1 ] |
刻み幅は掛け算で作る。終点が始点より小さければ降順になる。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 1 2 3 4 5 ] 3 TAKE |
[ 1/1 2/1 3/1 ] |
最初のN個の要素を残す。 |
[ 1 2 3 4 5 ] -2 TAKE |
[ 4/1 5/1 ] |
負の個数は末尾から取る。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 3 1 2 ] SORT |
[ 1/1 2/1 3/1 ] |
ベクトル全体の昇順ソート。 |
さらなる構造系のワード:TAKE DROP REVERSE COLLECT FILL PUT。INDEX-OFはインデックス指定の代わりに検索を行います。[ 10 20 30 ] 20 INDEX-OFは1/1になり、見つからない値はエラーではなくNILになります。含まれるかどうかだけを問うならMEMBER?([ 1 2 3 ] 2 MEMBER?はTRUE)、昇順のベクトルならBSEARCH([ 1 3 5 7 ] [ 5 ] BSEARCHは[ 2/1 ]——二分探索。区間を半分にし続ける反復は自分では書けない)を使います。BSEARCHは昇順でないベクトルを受けるとエラーにします。
順序付け、計数、形状
ベクトル全体を一度に見る必要がある操作は、7つのワードでカバーされます。そのそれぞれは、他の語彙だけを使って書き下すこともできますが、書き下すと漸近的に高いコストがかかります。それだけが、これらがワードとして存在する理由です。
| ワード | 例 | 結果 |
|---|---|---|
ORDER | [ 30 10 20 ] ORDER | [ 1/1 2/1 0/1 ] — 昇順の位置。同点は元の位置のまま残る |
UNIQUE | [ 'b' 'a' 'b' ] UNIQUE | [ 'b' 'a' ] — 重複のない値。最初の出現順 |
TALLY | [ 'b' 'a' 'b' ] TALLY | [ 'b' 'a' ] [ 2/1 1/1 ] RECORD — 要素ごとの件数をRecordで返す。キーはUNIQUEの並び、VALUESを取れば件数だけのベクトル |
ZIP | [ [ 1 2 ] [ 3 4 ] ] ZIP | [ [ 1/1 3/1 ] [ 2/1 4/1 ] ] — 位置ごとの束ね直し。これは転置でもある |
PUT | [ 1 2 3 ] 1 9 PUT | [ 1/1 9/1 3/1 ] — 1つの位置を置き換える。負のインデックスは末尾から数える |
GROUP | [ 'a' 'b' 'a' ] [ 1 2 3 ] GROUP | [ 'a' 'b' ] [ [ 1/1 3/1 ] [ 2/1 ] ] RECORD — キーごとに束ねた値をRecordで返す。キーはUNIQUEの順序に従い、'a' GETで1つの組を取り出せる |
ベクトルの形——各軸の長さを外側から並べたもの——は、要素ごとの持ち上げがオペランドを揃えるときに使っている概念です。次の4語がそれをプログラムから読み、書き換えます。FLATTENとDEPTHは自分で書くことができません。入れ子の深さは前もって分からず、再帰も無限ループもない言語は深さの分からない構造を辿れないからです。
| ワード | 例 | 結果 |
|---|---|---|
SHAPE | [ [ 1 2 ] [ 3 4 ] [ 5 6 ] ] SHAPE | [ 3/1 2/1 ] — 矩形の入れ子の各軸の長さ。[ [ 1 2 ] [ 3 ] ]のように不揃いなベクトルには形がないのでNIL(domainMiss) |
RESHAPE | [ 1 2 3 4 5 6 ] [ 2 3 ] RESHAPE | [ [ 1/1 2/1 3/1 ] [ 4/1 5/1 6/1 ] ] — 葉を順に、新しい形に組み直す。形の積が葉の数と違えばエラー。大きすぎる形はNIL(spaceExhausted) |
FLATTEN | [ [ 1 [ 2 3 ] ] [ 4 ] ] FLATTEN | [ 1/1 2/1 3/1 4/1 ] — どれだけ深くてもすべての軸を1本に畳む。CONCATは1段だけ |
DEPTH | [ 1 [ 2 [ 3 ] ] ] DEPTH | 3/1 — 入れ子の深さ。数やテキストは0、平坦なベクトルは1 |
深さの分かっている値それぞれにブロックを適用するには、MAPをその深さだけ入れ子にします。深さ2なら[ [ 1 2 ] [ 3 4 ] ] [ [ 10 MUL ] MAP ] MAPで、結果は[ [ 10/1 20/1 ] [ 30/1 40/1 ] ]です。
まず手を伸ばすべきはORDERです。SORTはソート後の値が何であるかを返し、どこから来たかは捨ててしまいます。ORDERは位置を保持します。そのためk近傍、上位kスコア、中央値の位置、ランキングのように、キーのほかにペイロードを運ぶ必要があるものは、いずれもORDERに続けてGETという形になります。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 18 13 1 1 13 2 ] ORDER 3 TAKE 'P' BIND |
[ 'd' 'c' 'a' ] |
最小の3つの距離に対応するラベル。同点は元の位置で解決される。 |
UNIQUEとTALLYは数だけでなくあらゆる値に対して働きます。ラベルは通常テキストなので、これは実用上の違いになります。一方ORDERとSORTはフラットな数のベクトルを必要とします。厳密な順序関係が定義されているのはそこだからです。GROUPは値とキーを位置によって対応付けるので、2つのオペランドは同じ長さでなければなりません。
契約: ベクトル
GET Semantic Kernel
コンテナを読む。ベクトルならインデックスの位置の要素、Recordならキーの下の値。[ 10 20 30 ] 1 GETは20、R 'x' GETはRが'x'の下に持つもの。負のインデックスは末尾から数える。キーは葉なので、インデックスやキーのベクトルは、名指した順の答えのベクトルへ持ち上がる。[ 10 20 30 ] [ 2 0 ] GETは[ 30 10 ]で、並べ替えや収集が1回の呼び出しで済む。何も指さないものは形は正しいが答えのない問いなので、エラーではなくその場で投影する。両端を越えたインデックスはNIL(indexOutOfBounds)、Recordが持たないキーはNIL(notFound)で、[ 10 20 30 ] [ 0 9 ] GETは[ 10/1 NIL ]。HAS?は存在だけを問うので、格納されたNILとキーの不在を区別できる。書き込み側はPUT。第1オペランドがベクトルでもRecordでもなければERROR(nonContainer)、整数でないインデックスはinvalidInteger。
- 構文
[ 10 20 30 ] 1 GET- スタック効果
- [ container ] [ key ] -> [ value ]
- スタック
- 入力 2 → 出力 1
- オペランド
data,leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthroughThenProject; 投影:indexOutOfBounds,notFound→indexOutOfBounds,notFound- 部分性
projecting- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonContainer,invalidInteger- 条項
LANG.VALUES.VECTOR,LANG.RECORDS.STRUCTURE,LANG.COLLECTIONS.LIFT,LANG.FAILURE.PROJECT,LANG.MACHINE.LIMITS
LENGTH Semantic Kernel
ベクトルが持つ要素の数。[ 1 2 3 ] LENGTHは3、[ ] LENGTHは0。数えるのは最も外側の軸だけで、[ [ 1 2 ] [ 3 ] ] LENGTHは2。すべての軸はSHAPEが答える。ベクトルでないものはnonVector。
- 構文
[ 1 2 3 ] LENGTH- スタック効果
- [ vec ] -> [ count ]
- スタック
- 入力 1 → 出力 1
- オペランド
data(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonVector- 条項
LANG.VALUES.VECTOR,LANG.MACHINE.LIMITS
TAKE Standard · namedPattern
ベクトルの先頭N個、または末尾の-N個を取る。[ 1 2 3 ] 2 TAKEは[ 1 2 ]、[ 1 2 3 ] -2 TAKEは[ 2 3 ]。ベクトルより大きい個数はNIL(indexOutOfBounds)を投影する。あるより多くを求めるのは末尾を越えた位置を名指すことで、それはGETが末尾を越えて答える問いと同じであり、同じように答えられる。うまくいかなかった整ったデータであって、誤ったプログラムではない(LANG.FAILURE.PROJECT)。したがって[ 1 2 3 ] 9 TAKEはNILで、別の答えが欲しい呼び出し側はそれを書く。[ 1 2 3 ] 9 TAKE 'S' BIND [ 1 2 3 ] S S NIL? SELECTは代わりにベクトル全体を答える。そもそも整数でない個数は今もinvalidInteger。それはプログラムの誤りだからである。
- 構文
[ 1 2 3 4 5 ] 3 TAKE- スタック効果
- [ vec ] [ n ] -> [ prefix ]
- スタック
- 入力 2 → 出力 1
- オペランド
data,leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthroughThenProject; 投影:indexOutOfBounds→indexOutOfBounds- 部分性
projecting- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonVector,invalidInteger- 条項
LANG.VALUES.VECTOR,LANG.COLLECTIONS.LIFT,LANG.MACHINE.LIMITS,LANG.FAILURE.PROJECT
DROP Standard · namedPattern
ベクトルの先頭N個、または末尾の-N個を落として残りを答える。TAKEの対になるワードで、[ 1 2 3 4 5 ] 2 DROPは[ 3 4 5 ]、[ 1 2 3 4 5 ] -2 DROPは[ 1 2 3 ]。したがって[ n ] TAKEと[ n ] DROPは1つのベクトルを2つに分け、CONCATで元に戻せる。ベクトルより大きい個数はTAKEと同じくNIL(indexOutOfBounds)を投影する。末尾を越えた位置を名指しており、うまくいかなかった整ったデータだからである(LANG.FAILURE.PROJECT)。そもそも整数でない個数は今もinvalidInteger。それはプログラムの誤りだからである。
- 構文
[ 1 2 3 4 5 ] 2 DROP- スタック効果
- [ vec ] [ n ] -> [ rest ]
- スタック
- 入力 2 → 出力 1
- オペランド
data,leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthroughThenProject; 投影:indexOutOfBounds→indexOutOfBounds- 部分性
projecting- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonVector,invalidInteger- 条項
LANG.VALUES.VECTOR,LANG.COLLECTIONS.LIFT,LANG.MACHINE.LIMITS,LANG.FAILURE.PROJECT
CONCAT Semantic Kernel
2つのベクトルを端と端でつなぐ。[ 1 2 ] [ 3 ] CONCATは[ 1 2 3 ]。要素はそのまま保たれ、入れ子のベクトルは入れ子のまま。どちらのオペランドもベクトルでなければならない(nonVector)。
- 構文
[ 1 2 ] [ 3 4 ] CONCAT- スタック効果
- [ a ] [ b ] -> [ a ++ b ]
- スタック
- 入力 2 → 出力 1
- オペランド
data,data(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonVector- 条項
LANG.VALUES.VECTOR,LANG.MACHINE.LIMITS
REVERSE Standard · namedPattern
ベクトルの要素を逆の順序にしたもの。[ 1 2 3 ] REVERSEは[ 3 2 1 ]。逆にするのは最も外側の軸だけで、[ [ 1 2 ] 3 ] REVERSEは[ 3 [ 1 2 ] ]。ベクトルでないものはnonVector。
- 構文
[ 1 2 3 ] REVERSE- スタック効果
- [ vec ] -> [ reversed ]
- スタック
- 入力 1 → 出力 1
- オペランド
data(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonVector- 条項
LANG.VALUES.VECTOR,LANG.MACHINE.LIMITS
COLLECT Semantic Kernel
スタックからN個の値を取り、先に積まれたものを先にして1つのベクトルで答える。1 2 3 3 COLLECTは[ 1 2 3 ]。Nは非負の整数でなければならず(invalidInteger)、スタックの値がNより少なければstackUnderflow。
- 構文
1 2 3 3 COLLECT- スタック効果
- [ v1 ] ... [ vn ] [ n ] -> [ [ v1 ... vn ] ]
- スタック
- 入力 variable → 出力 1
- NIL方針
rejectNil; 投影: なし- 部分性
partial- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
invalidInteger,stackUnderflow- 条項
LANG.VALUES.VECTOR,LANG.MACHINE.LIMITS
RANGE Semantic Kernel
始点から終点までのすべての整数(両端を含む)。0 3 RANGEは[ 0 1 2 3 ]、3 0 RANGEは降順の[ 3 2 1 0 ]。刻み幅のオペランドはない。刻みは数列の掛け算で、0 3 RANGE 3 MULは[ 0 3 6 9 ]。だから方向は両端だけで決まり、どんな両端の組も無限の数列を表さない。整数でない端はERROR(invalidInteger)、機械が具体化できるより長い数列はNIL(spaceExhausted)を投影する。両端は葉なので、端のベクトルはレーンごとに1つの数列へ持ち上がる。
- 構文
0 5 RANGE- スタック効果
- [ start ] [ end ] -> [ seq ]
- スタック
- 入力 2 → 出力 1
- オペランド
leaf,leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthroughThenProject; 投影:materializationBudgetExceeded→spaceExhausted- 部分性
projecting- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
invalidInteger,shapeMismatch- 条項
LANG.VALUES.VECTOR,LANG.COLLECTIONS.LIFT,LANG.MACHINE.LIMITS,LANG.FAILURE.PROJECT
FILL Standard · operational
与えた形の、すべての葉が1つの値であるベクトル。[ 2 3 ] 0 FILLは[ [ 0 0 0 ] [ 0 0 0 ] ]、[ 2 ] 'a' FILLは[ 'a' 'a' ]。RESHAPEと同じく形が先に来る。形はSHAPEが答えるもの、つまり非負の整数のベクトルで、[ 0 ] 0 FILLは[ ]、空の形[ ]は値そのもの(ランク0)を答える。それ以外はinvalidShape。値はどの領域の葉でもよく、値のベクトルはそれぞれ1つの埋められたベクトルへ持ち上がる。具体化するには大きすぎる形(要素が多すぎる、または軸が入れ子の上限を超える)はNIL(spaceExhausted)を投影する。
- 構文
[ 2 2 ] 0 FILL- スタック効果
- [ shape ] [ value ] -> [ filled ]
- スタック
- 入力 2 → 出力 1
- オペランド
data,leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthroughThenProject; 投影:materializationBudgetExceeded→spaceExhausted- 部分性
projecting- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
invalidShape- 条項
LANG.VALUES.VECTOR,LANG.COLLECTIONS.LIFT,LANG.MACHINE.LIMITS,LANG.FAILURE.PROJECT
SHAPE Semantic Kernel
矩形のベクトルの各軸の長さ(外側から順に)。[ [ 1 2 ] [ 3 4 ] ] SHAPEは[ 2 2 ]、[ 1 2 3 ] SHAPEは[ 3 ]。ベクトルでない値は空の形を持つ。5 DEPTHが0であるのと同じく、5 SHAPEは[ ](ランク0)。これはLANG.COLLECTIONS.LIFTがすでにオペランドを揃えるのに使っている形を、観測できるようにしたものである。不揃いなベクトルには形がないのでNIL(domainMiss)を投影する。[ [ 1 2 ] [ 3 ] ] SHAPEはエラーではなく理由付きの不在である。そのベクトルは問いが当てはまらない整ったデータだからである。最も外側の軸だけならLENGTHが答える。
- 構文
[ [ 1 2 ] [ 3 4 ] ] SHAPE- スタック効果
- [ x ] -> [ shape ]
- スタック
- 入力 1 → 出力 1
- オペランド
data(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthroughThenProject; 投影:raggedNesting→domainMiss- 部分性
projecting- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- 条項
LANG.VALUES.VECTOR,LANG.FAILURE.PROJECT
RESHAPE Semantic Kernel
ベクトルの葉を順に、新しい形のもとへ組み直す。[ 1 2 3 4 5 6 ] [ 2 3 ] RESHAPEは[ [ 1 2 3 ] [ 4 5 6 ] ]で、矩形のベクトルにSHAPE RESHAPEを施せば元に戻る。葉とは、どれだけ深く入れ子になっていても、FLATTENが答えるものすべてである。形はSHAPEが答えるもの、つまり空のものを含む非負の整数のベクトルで、その積は葉の数と等しくなければならない。[ 5 ] [ ] RESHAPEは5、[ ] [ 2 0 ] RESHAPEは[ [ ] [ ] ]。それ以外の形はERROR(invalidShape)。合わせるために何かを詰めたり繰り返したりはしないからである。具体化するには大きすぎる整った形(要素が多すぎる、または軸が入れ子の上限を超える)は、RANGEやFILLと同じくNIL(spaceExhausted)を投影する(LANG.COLLECTIONS.BUDGET)。
- 構文
[ 1 2 3 4 5 6 ] [ 2 3 ] RESHAPE- スタック効果
- [ vec ] [ shape ] -> [ reshaped ]
- スタック
- 入力 2 → 出力 1
- オペランド
data,data(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthroughThenProject; 投影:materializationBudgetExceeded→spaceExhausted- 部分性
projecting- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonVector,invalidShape- 条項
LANG.VALUES.VECTOR,LANG.COLLECTIONS.BUDGET,LANG.MACHINE.LIMITS,LANG.FAILURE.PROJECT
FLATTEN Semantic Kernel
すべての軸を1本に畳む。[ [ 1 [ 2 3 ] ] [ 4 ] ] FLATTENは[ 1 2 3 4 ]で、どれだけ深く入れ子になっていても葉をインデックス順に並べる。CONCATは2つのベクトルをつないで1段だけ平らにし、FLATTENは1つのベクトルを受けてすべての段を平らにする。これはユーザー定義では書けない。深さは前もって分からず、再帰も際限のないループもない言語は深さの分からない構造を辿れないからである(LANG.DICTIONARY.ACYCLIC)。
- 構文
[ [ 1 [ 2 3 ] ] [ 4 ] ] FLATTEN- スタック効果
- [ vec ] -> [ flat ]
- スタック
- 入力 1 → 出力 1
- オペランド
data(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonVector- 条項
LANG.VALUES.VECTOR,LANG.DICTIONARY.ACYCLIC
DEPTH Semantic Kernel
ベクトルの入れ子の深さ。ベクトルでないもの(数、テキスト、真偽値、Symbol、Record)は0、平らなベクトルは1、ベクトルは最も深い要素より1つ深い。[ 1 [ 2 [ 3 ] ] ] DEPTHは3、[ ] DEPTHは1。FLATTENと同じくユーザー定義では書けない。測る対象そのものが、再帰しないプログラムには辿れないものだからである。
- 構文
[ 1 [ 2 [ 3 ] ] ] DEPTH- スタック効果
- [ x ] -> [ n ]
- スタック
- 入力 1 → 出力 1
- オペランド
data(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- 条項
LANG.VALUES.VECTOR,LANG.DICTIONARY.ACYCLIC
SORT Standard · operational
数のベクトルを昇順に並べた複製。[ 3 1 2 ] SORTは[ 1 2 3 ]。順序は無理数を含めて厳密。順序があるのは数だけで(nonNumeric)、ベクトルでないものはnonVector。並べ替えの置換を答えるのはORDER。
- 構文
[ 3 1 2 ] SORT- スタック効果
- [ vec ] -> [ sorted ]
- スタック
- 入力 1 → 出力 1
- オペランド
data(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonVector,nonNumeric- 条項
LANG.VALUES.VECTOR,LANG.MACHINE.LIMITS
ORDER Standard · operational
数のベクトルを昇順に並べるインデックス。[ 30 10 20 ] ORDERは[ 1 2 0 ]なので、それで収集すれば並べ替えになる。[ 30 10 20 ] 'V' BIND V V ORDER GETは[ 10 20 30 ]。同点は元の順序を保つ。[ 2 1 2 1 ] ORDERは[ 1 3 0 2 ]。順序があるのは数だけで(nonNumeric)、ベクトルでないものはnonVector。
- 構文
[ 30 10 20 ] ORDER- スタック効果
- [ vec ] -> [ permutation ]
- スタック
- 入力 1 → 出力 1
- オペランド
data(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonVector,nonNumeric- 条項
LANG.VALUES.VECTOR,LANG.MACHINE.LIMITS
UNIQUE Standard · operational
ベクトルの重複のない要素。それぞれ最初に現れた位置に置く。[ 3 1 3 ] UNIQUEは[ 3 1 ]。重複しないとは1つの値でないことなので(LANG.VALUES.DENOTATION)、テキスト、入れ子のベクトル、NILにも同じように働き、[ 2 4/2 ] UNIQUEは[ 2 ]。ベクトルでないものはnonVector。
- 構文
[ 'a' 'b' 'a' ] UNIQUE- スタック効果
- [ vec ] -> [ distinct ]
- スタック
- 入力 1 → 出力 1
- オペランド
data(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonVector- 条項
LANG.VALUES.VECTOR,LANG.MACHINE.LIMITS
ZIP Standard · operational
同じ長さのベクトルを位置ごとに束ねる。[ [ 1 2 ] [ 3 4 ] [ 5 6 ] ] ZIPは[ [ 1 3 5 ] [ 2 4 6 ] ]なので、行列は転置され、ZIP ZIPで元に戻る。長さの違う行はshapeMismatch、ベクトルのベクトルでないオペランドはnonVector。
- 構文
[ [ 1 2 ] [ 3 4 ] ] ZIP- スタック効果
- [ [ vec... ] ] -> [ [ tuple... ] ]
- スタック
- 入力 1 → 出力 1
- オペランド
data(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonVector,shapeMismatch- 条項
LANG.VALUES.VECTOR,LANG.MACHINE.LIMITS
PUT Semantic Kernel
コンテナに書く。ベクトルならインデックスの要素を置き換えた複製、Recordならキーを設定した複製。[ 1 2 3 ] 1 9 PUTは[ 1 9 3 ]、R 'z' 3 PUTは'z'を3にしたR。Recordにすでにあるキーは位置を保って新しい値を取り、ないキーは末尾に足されるので、キーの順序はキーが加わった順を記録する。ベクトルの位置はその長さで決まっているので、両端を越えたインデックスはどの枠も名指さず、GETと同じくNIL(indexOutOfBounds)を投影する。呼び出し側が保ちたかったベクトルは、自分で書いたそのベクトルである。値は運ばれるだけなのでNIL(格納された不在)を含め何でもよい。どちらのオペランドも変更されない。コンテナは値だからである。読み取り側はGET。第1オペランドがベクトルでもRecordでもなければERROR(nonContainer)、整数でないインデックスはinvalidInteger。
- 構文
[ 1 2 3 ] 1 9 PUT- スタック効果
- [ container ] [ key ] [ value ] -> [ container ]
- スタック
- 入力 3 → 出力 1
- オペランド
data,leaf,element(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthroughThenProject; 投影:indexOutOfBounds→indexOutOfBounds- 部分性
projecting- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonContainer,invalidInteger- 条項
LANG.VALUES.VECTOR,LANG.RECORDS.STRUCTURE,LANG.COLLECTIONS.LIFT,LANG.FAILURE.PROJECT,LANG.MACHINE.LIMITS
INDEX-OF Standard · namedPattern
値と等しい最初の要素のインデックス。[ 10 20 30 ] 20 INDEX-OFは1。値は1つの要素として丸ごと比較されるので、ベクトルを探せば等しいベクトルを探す。ベクトルが含まない値はNIL(notFound)を投影する。同じ問いを真偽値で問うのはMEMBER?。
- 構文
[ 1 2 ] 2 INDEX-OF- スタック効果
- [ vec ] [ x ] -> [ idx ]
- スタック
- 入力 2 → 出力 1
- オペランド
data,element(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthroughThenProject; 投影:notFound→notFound- 部分性
projecting- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonVector- 条項
LANG.VALUES.VECTOR,LANG.MACHINE.LIMITS,LANG.FAILURE.PROJECT
MEMBER? Standard · namedPattern
値がベクトルの中に現れるかどうか。[ 1 2 3 ] 2 MEMBER?はTRUE。所属は値の等価性、つまりUNIQUEやINDEX-OFが使う等価性なので、数だけでなくテキスト、入れ子のベクトル、NILにも働く。探す値は1つの値で、読まれるのではなく比較されるので、ベクトルを探せば1つの要素として探す。[ [ 1 ] 2 ] [ 1 ] MEMBER?はTRUE。これはINDEX-OF NIL? NOTである。
- 構文
[ 1 2 3 ] 2 MEMBER?- スタック効果
- [ vec ] [ x ] -> [ TRUE | FALSE ]
- スタック
- 入力 2 → 出力 1
- オペランド
data,element(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonVector- 条項
LANG.VALUES.VECTOR,LANG.MACHINE.LIMITS
BSEARCH Standard · algorithm
昇順のベクトルの中の各キーのインデックスを、区間を半分にしながら探す。[ 1 3 5 7 ] [ 5 ] BSEARCHは[ 2 ]で、単独のキーは単独のインデックスを答え、ないキーのレーンはNIL(notFound)。ベクトルは昇順でなければならず、そうでなければunsortedInputを送出する。順序のないデータの上の二分探索は、何も答えないのではなく何かを答えてしまうからである。順序の確認はベクトルを1回走査するだけで、その後の各キーはO(log n)なので、m個のキーの費用はINDEX-OFのO(m·n)に対してO(n + m log n)。区間が空になるまで半分にし続けるのはデータに長さが依存するループであり、際限のないループを持たない言語には書けない。
- 構文
[ 1 3 5 7 ] [ 5 ] BSEARCH- スタック効果
- [ sorted ] [ keys ] -> [ indices ]
- スタック
- 入力 2 → 出力 1
- オペランド
data,leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthroughThenProject; 投影:notFound→notFound- 部分性
projecting- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonVector,unsortedInput,nonNumeric- 条項
LANG.VALUES.VECTOR,LANG.COLLECTIONS.LIFT,LANG.MACHINE.LIMITS,LANG.FAILURE.PROJECT
2.4.2.3 キーによる対応(RECORD, GET)
名前の付いたデータ——価格表、設定、1行のレコード——を、AjisaiはRecordという第7の値領域で持ちます。Recordはキー列と値列を位置で対応付けたもので、キーは与えた順序を保ち、同じキーは一度しか現れません。キーには任意の値を使えます(ふつうは文字列)。ベクトルではないので、ベクトルのワードはRecordを受け取らず、Recordのワードはベクトルを受け取りません。暗黙の変換はどちら向きにもありません。
RecordはRECORDで作ります。キー列と値列を受け取り、位置で対応付けます。キーに対応する値が無ければ長さの不一致、同じキーが2回あればduplicateKeyのエラーになります。
表示は[ 'x' 'y' ] [ 1/1 2/1 ] RECORDの形です。これはそのRecordを組み立てるプログラムそのもので、そのまま書けば同じ値になります。ベクトルの中にあるRecordは、[ 'a' ] [ 1/1 ] RECORD 1 COLLECTのように、要素を並べてCOLLECTで集める形で表示されます。[ ]の中に書いた名前はSymbolになるので、そこにRECORDを書いてもRecordにはならないからです。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 'x' 'y' ] [ 1 2 ] RECORD |
[ 'x' 'y' ] [ 1/1 2/1 ] RECORD |
キー列と値列を位置で対応付ける。 |
[ 'a' ] [ 1 ] RECORD [ 'b' ] [ 2 ] RECORD 2 COLLECT LENGTH |
2/1 |
Recordはベクトルの1つの要素になる。 |
[ 'v' 'r' ] [ 1 2 ] [ 'k' ] [ 3 ] RECORD 2 COLLECT RECORD |
[ 'v' 'r' ] [ 1/1 2/1 ] [ 'k' ] [ 3/1 ] RECORD 2 COLLECT RECORD |
値列にRecordを入れれば入れ子になる。 |
| ワード | 例 | 結果 |
|---|---|---|
RECORD | [ 'x' 'y' ] [ 1 2 ] RECORD | [ 'x' 'y' ] [ 1/1 2/1 ] RECORD — キー列と値列を位置で対応付ける。長さが違えばエラー、同じキーが2回あればduplicateKeyのエラー(後勝ちで黙って捨てない) |
KEYS | [ 'x' 'y' ] [ 1 2 ] RECORD KEYS | [ 'x' 'y' ] — キー列。与えた順序のまま |
VALUES | [ 'x' 'y' ] [ 1 2 ] RECORD VALUES | [ 1/1 2/1 ] — 値列。KEYSと位置が揃う。ここから先はベクトルの語彙がそのまま使える |
GET | [ 'x' 'y' ] [ 1 2 ] RECORD 'y' GET | 2/1 — キーの下の値。ベクトルを位置で読むGETが、Recordはキーで読む。ないキーはNIL(notFound) |
PUT | [ 'x' ] [ 1 ] RECORD 'y' 2 PUT | [ 'x' 'y' ] [ 1/1 2/1 ] RECORD — 1つのキーに値を与えた新しいRecord(ベクトルの位置を書き換えるPUTと同じワード)。既にあるキーは位置を動かさず値だけ替わり、ないキーは末尾に足される |
WITHOUT | [ 'x' 'y' ] [ 1 2 ] RECORD 'x' WITHOUT | [ 'y' ] [ 2/1 ] RECORD — 1つのキーを除いた新しいRecord。ないキーを除こうとするとNIL(notFound) |
HAS? | [ 'x' ] [ 1 ] RECORD 'x' HAS? | TRUE — キーがあるかどうか。値を読まずに答えるので、「キーがない」と「値がNIL」を区別できる |
MERGE | [ 'x' 'y' ] [ 1 2 ] RECORD [ 'y' 'z' ] [ 9 3 ] RECORD MERGE | [ 'x' 'y' 'z' ] [ 1/1 9/1 3/1 ] RECORD — 2つのRecordの合併。同じキーは右が勝ち、左の順序を保ち、右にしかないキーは末尾に足される。既定値に上書きを重ねる形 |
Recordの同一性と順序
Recordの見分けはキー列と値列だけで付きます(同一性モデル)。同じキーを同じ順序で持ち、対応する値が同じなら、どう組み立てたかによらず同じ値です。順序は観測できる構造なので、[ 'x' 'y' ] [ 1 2 ] RECORDと[ 'y' 'x' ] [ 2 1 ] RECORDは別の値です。EQはこの同一性を答え、UNIQUEやTALLYはRecordを要素として数えられます。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 'x' 'y' ] [ 1 2 ] RECORD [ 'x' 'y' ] [ 1 2 ] RECORD EQ |
TRUE |
2度組み立てても1つの値。 |
[ 'x' 'y' ] [ 1 2 ] RECORD [ 'y' 'x' ] [ 2 1 ] RECORD EQ |
FALSE |
キーの順序が違えば別の値。 |
算術と比較だけが値の側へ持ち上がる
Recordに対して働く既存のワードは算術と比較だけです。数やベクトルをRecordと組み合わせると、キーはそのままで、値のそれぞれに演算が及びます。2つのRecord同士はキー列が等しいときだけ値ごとに組み合わさり、違えばshapeMismatchのエラーです。それ以外のワード——LENGTH、MAP、AND、CHARSなど——は、Recordを受け取るとそのワード自身のオペランドエラーを起こします。Recordをベクトルとして扱いたいならVALUESで明示的に渡ってください。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 'x' 'y' ] [ 1 2 ] RECORD 10 MUL |
[ 'x' 'y' ] [ 10/1 20/1 ] RECORD |
キーは変わらず、値だけが10倍される。 |
[ 'x' 'y' ] [ 1 2 ] RECORD 1 GT |
[ 'x' 'y' ] [ FALSE TRUE ] RECORD |
比較も同じ規則で、真偽値のRecordになる。 |
[ 'x' 'y' ] [ 1 2 ] RECORD [ 'x' 'y' ] [ 1 2 ] RECORD ADD |
[ 'x' 'y' ] [ 2/1 4/1 ] RECORD |
キー列が等しい2つのRecordは値ごとに足される。 |
ないキーの扱い
ないキーをGETで読むのは、形は正しいが答えのない問いです。ですからエラーではなく、理由notFoundを持つNILが返り、回復の書き方がそのまま効きます。存在そのものを問いたいときはHAS?を使ってください。WITHOUTも同じ規律で、ないキーを除こうとするとNILを返します。GET・TAKE・PUTが範囲外の位置を投影するのと同じで、綴り間違いが黙って通ることはありません。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 'x' 'y' ] [ 1 2 ] RECORD 'z' GET 'S' BIND 0 S S NIL? SELECT |
0/1 |
ないキーには代わりの値0が選ばれる。 |
[ 'x' 'y' ] [ 1 2 ] RECORD 'z' GET NIL-REASON |
'notFound' |
理由はプロトコル文字列なので分岐できる。 |
TALLYとGROUPはRecordを返します。TALLYは「要素→件数」、GROUPは「キー→そのキーの値のベクトル」で、どちらもキーは最初の出現順です。KEYSを取ればUNIQUEの答えそのもの、VALUESを取れば以前の件数ベクトル・グループのベクトルが得られるので、失われた力はありません。表(table)は「列名→列ベクトル」のRecordとして持てば、'price' GETで1列を取り出し、算術で列ごとに計算できます。
契約: Record
TALLY Standard · operational
重複のない各要素が何回現れるかを、要素から件数へのRecordで答える。[ 'b' 'a' 'b' ] TALLYは[ 'b' 'a' ] [ 2/1 1/1 ] RECORDで、キーは最初の出現順。KEYSはまさにUNIQUEの答えであり、VALUESは位置の揃った件数のベクトルなので、各件数が何を数えているかを知るためにUNIQUEを別に呼ぶ必要はない。数に限らずどの値にも働く。ベクトルでないオペランドはERROR。
- 構文
[ 'a' 'b' 'a' ] TALLY- スタック効果
- [ vec ] -> [ record ]
- スタック
- 入力 1 → 出力 1
- オペランド
data(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonVector- 条項
LANG.RECORDS.STRUCTURE,LANG.VALUES.VECTOR,LANG.MACHINE.LIMITS
GROUP Standard · operational
値を同じ位置のキーで束ね、キーからその値のベクトルへのRecordで答える。[ 'a' 'b' 'a' ] [ 1 2 3 ] GROUPは[ 'a' 'b' ] [ [ 1/1 3/1 ] [ 2/1 ] ] RECORDで、キーは最初の出現順、どの値もちょうど1回ずつ保たれる。クラスごとの集計、重心の更新、層別の分割の中核である。1つのグループはR 'a' GETで名前によって読めるので、UNIQUEやINDEX-OFで探す必要はない。RECORDと同じくキーが先に来る。両オペランドとも同じ長さのベクトルでなければならない。
- 構文
[ 'a' 'b' 'a' ] [ 1 2 3 ] GROUP- スタック効果
- [ keys ] [ values ] -> [ record ]
- スタック
- 入力 2 → 出力 1
- オペランド
data,data(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonVector,shapeMismatch- 条項
LANG.RECORDS.STRUCTURE,LANG.VALUES.VECTOR,LANG.MACHINE.LIMITS
RECORD Semantic Kernel
キーのベクトルと値のベクトルを位置ごとに対応付けて、Record(キーによる対応。第7の値領域)を組み立てる。[ 'x' 'y' ] [ 1 2 ] RECORD 'y' GETは2。キーは与えられた順序を保ち、KEYSとVALUESがそれを読み戻す。長さが違う、あるいは同じキーが2回現れるのはプログラムの誤りなので、どちらも黙って後勝ちにするのではなくERRORになる。
- 構文
[ 'x' 'y' ] [ 1 2 ] RECORD- スタック効果
- [ keys ] [ values ] -> [ record ]
- スタック
- 入力 2 → 出力 1
- オペランド
data,data(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonVector,shapeMismatch,duplicateKey- 条項
LANG.RECORDS.STRUCTURE,LANG.VALUES.DISJOINT,LANG.VALUES.VECTOR
KEYS Semantic Kernel
Recordのキーを、そのRecord自身の順序でベクトルとして答える。だからKEYSとVALUESは位置ごとに揃う。[ 'x' 'y' ] [ 1 2 ] RECORD KEYSは[ 'x' 'y' ]。キーの順序はRecordの観測できる構造の一部なので、このベクトルは任意の順の集合ではなく厳密に1つのものである。ベクトルやその他RecordでないオペランドはERROR。Recordはベクトルではなく、両者の間に暗黙の変換はない。
- 構文
[ 'x' 'y' ] [ 1 2 ] RECORD KEYS- スタック効果
- [ record ] -> [ keys ]
- スタック
- 入力 1 → 出力 1
- オペランド
data(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonRecord- 条項
LANG.RECORDS.STRUCTURE,LANG.VALUES.DISJOINT
VALUES Semantic Kernel
Recordの値を、KEYSと揃ったベクトルとして答える。[ 'x' 'y' ] [ 1 2 ] RECORD VALUESは[ 1/1 2/1 ]。これはRecordの領域からベクトルの領域へ戻る橋で、ここから先はどのベクトルのワードも使える。逆向きの橋はRECORDなので、R KEYS R VALUES RECORDはRを組み立て直す。RecordでないオペランドはERROR。
- 構文
[ 'x' 'y' ] [ 1 2 ] RECORD VALUES- スタック効果
- [ record ] -> [ values ]
- スタック
- 入力 1 → 出力 1
- オペランド
data(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonRecord- 条項
LANG.RECORDS.STRUCTURE,LANG.VALUES.DISJOINT
WITHOUT Semantic Kernel
Recordから1つのキーを除いた複製。[ 'x' 'y' ] [ 1 2 ] RECORD 'x' WITHOUT KEYSは[ 'y' ]。Recordが持たないキーを除こうとすると、何もしないのではなく不在notFoundになる。GET・TAKE・PUTがベクトルの外の位置に対して守るのと同じ規律で、綴り間違いのキーが黙って通ることはない。他のキーは順序を保つ。第1オペランドがRecordでなければERROR。
- 構文
[ 'x' 'y' ] [ 1 2 ] RECORD 'x' WITHOUT- スタック効果
- [ record ] [ key ] -> [ record ]
- スタック
- 入力 2 → 出力 1
- オペランド
data,leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthroughThenProject; 投影:notFound→notFound- 部分性
projecting- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonRecord- 条項
LANG.RECORDS.STRUCTURE,LANG.VALUES.DISJOINT,LANG.FAILURE.PROJECT,LANG.COLLECTIONS.LIFT
HAS? Semantic Kernel
Recordがキーを持つかどうか。[ 'x' ] [ 1 ] RECORD 'x' HAS?はTRUE、[ 'x' ] [ 1 ] RECORD 'y' HAS?はFALSE。値に触れずに存在を問うので、ないキーと、格納された値がNILであるキーとを区別できる。GETだけではどちらもNILと答える。NIL?と同じく述語なので?で終わる。第1オペランドがRecordでなければERROR。
- 構文
[ 'x' ] [ 1 ] RECORD 'x' HAS?- スタック効果
- [ record ] [ key ] -> [ TRUE | FALSE ]
- スタック
- 入力 2 → 出力 1
- オペランド
data,leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonRecord- 条項
LANG.RECORDS.STRUCTURE,LANG.VALUES.DISJOINT,LANG.COLLECTIONS.LIFT
MERGE Semantic Kernel
2つのRecordの合併で、右が勝つ。[ 'x' 'y' ] [ 1 2 ] RECORD [ 'y' 'z' ] [ 9 3 ] RECORD MERGE VALUESは[ 1 9 3 ]。左のRecordのキーは順序を保ち、両方が持つキーでは右の値を取る。右だけが持つキーは右の順序で末尾に足される。既定値に上書きを重ねるのがこのワードの用途である。左を勝たせたいならオペランドを入れ替える。どちらかがRecordでなければERROR。
- 構文
[ 'x' 'y' ] [ 1 2 ] RECORD [ 'y' 'z' ] [ 9 3 ] RECORD MERGE- スタック効果
- [ record ] [ record ] -> [ record ]
- スタック
- 入力 2 → 出力 1
- オペランド
data,data(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonRecord- 条項
LANG.RECORDS.STRUCTURE,LANG.VALUES.DISJOINT
2.4.2.4 文字列操作(CHARS, JOIN)
ベクトルへ渡る: CHARSとJOIN
文字列はベクトルではないため、コレクション系のワードは文字列を受け付けません。LENGTH、GET、REVERSE、CONCATはいずれも文字列に対してはエラーを送出します。まずCHARSでベクトルの定義域へ渡ってください。'abc' CHARS LENGTHは3になります。連結はJOINで行います。これは文字列のベクトルを1つの文字列に変えるためのワードで、[ 'ab' 'cd' ] JOINは'abcd'です。書いたものではなく計算した値を連結したい場合は、'a' 'b' 2 COLLECT JOINのように、まずCOLLECTで集めてください。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
'hello' CHARS |
[ 'h' 'e' 'l' 'l' 'o' ] |
CHARSは文字列の定義域からベクトルの定義域へ渡る。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 'hel' 'lo' ] JOIN |
'hello' |
文字列のベクトルを連結する。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
' hi ' TRIM |
'hi' |
両端の空白を取り除く。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
'a,b,c' ',' TOKENIZE |
[ 'a' 'b' 'c' ] |
区切り文字で分割する。JOINで断片を元通りに繋げる。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
'hello world' 'world' SEARCH |
6/1 |
最初に現れる位置。CHARSと同じく文字で数える。見つからなければNIL(notFound)。 |
'a-b-c' '-' '+' REPLACE |
'a+b+c' |
すべての出現を置き換える。左から順に、重ならずに。 |
CHARSは文字列をその文字へ分割し、TOKENIZEは区切り文字によって分割します。どちらも文字列のベクトルを生成し、JOINはそれをテキストへ組み立て直します。'hello' CHARS REVERSE JOINは'olleh'になります。
数との相互変換: STRとNUM
| サンプルコード | 期待値 | 注記 |
|---|---|---|
42 STR |
'42' |
数から文字列へ。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
'1/3' NUM |
1/3 |
文字列から厳密な数へ。パースできないテキストはNILになる。 |
大文字と小文字: UPPERとLOWER
Unicodeの既定の大小文字対応で、文字列全体を大文字・小文字にします。ロケールに依存する規則(トルコ語の点なしi、ギリシャ語の語末シグマ)は適用しないので、同じテキストはどこで走らせても同じ答えになります。対応表はUnicodeのものであり、CHARSとJOINで自分の定義として書けるものではないので、語として持ちます。ßのように長さの変わる対応もそのまま適用されます。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
'Ajisai' UPPER |
'AJISAI' |
大文字へ。 |
'Ajisai' LOWER |
'ajisai' |
小文字へ。 |
'straße' UPPER |
'STRASSE' |
長さの変わる対応もUnicodeどおり。 |
42 UPPER |
error | 文字列以外はnonText。 |
桁数を決めて描く: FORMAT
算術はどこでも丸めず、STRは厳密な字句を持たない数を断ります。それでも1/3を小数3桁で見たいことはあります。FORMATがその唯一の場所です。値と桁数を受け、ROUNDと同じ規則(端数がちょうど半分なら0から遠い側へ)で丸めた十進テキストを返します。丸め規則は言語に一つです。出てくるのはテキストであって数ではないので、丸めた量が算術に戻ることはありません。丸め方は固定です——選べるようにすると語が増えるだけだからです。桁数は非負の整数でなければならず(invalidInteger)、値はスカラーでなければなりません(nonNumeric)。無理数でも体の順序が最後の桁まで決めるので、桁を推測することはありません。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
1/3 5 FORMAT |
'0.33333' |
丸めは表示境界でだけ起きる。 |
5/2 0 FORMAT |
'3' |
ちょうど半分は0から遠い側へ。ROUNDと同じ、言語に一つの丸め規則。 |
2 SQRT 3 FORMAT |
'1.414' |
無理数も体の全順序で最終桁が決まる。 |
2 SQRT 3 SQRT ADD 4 FORMAT |
'3.1463' |
項が複数の無理数も、最終桁は厳密に決まる。 |
1/3 2 FORMAT 1 ADD |
error | 答えはテキスト。数に戻すならNUMを通す。 |
JSONとの相互変換: JSON-DECODEとJSON-ENCODE
ホストから届く構造化データはJSONで来ます。JSON-DECODEはJSONテキストを値にします。オブジェクトはメンバー名をその順に鍵とするRecord、配列はベクトル、文字列は文字列、数はそれが綴る厳密な有理数(0.1は厳密に1/10)、true/falseは真偽値、nullはNILです。ひとつのJSON値になっていないテキスト(壊れている、空、末尾に余分がある、同じ名前のメンバーが二度ある)はinvalidEncodingのNILを投影します。入れ子の深さはテキストだけが決めるので、この語はユーザー定義では書けません——言語の反復は、すでにあるベクトルの上でしか回らないからです。
JSON-ENCODEはその逆です。文字列を鍵とするRecordはオブジェクト、ベクトルは配列、NILはnullになります。有限の十進表記を持つ数(分母が2と5の積)だけがJSONの数として書かれ、それ以外の有理数は"1/3"のようにAjisaiの字句を文字列に入れて書きます。1/3を0.333と書けば、それは別の数だからです。Symbolや無理数、文字列でない鍵を持つRecordのようにJSONに像を持たない値はdomainMissのNILを投影します。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
'{"a": 1, "b": [true, null]}' JSON-DECODE |
[ 'a' 'b' ] [ 1/1 [ TRUE NIL ] ] RECORD |
オブジェクトはRecord、配列はベクトル。 |
'0.1' JSON-DECODE 10 MUL |
1/1 |
数は厳密な有理数として読まれる。 |
[ 'a' ] [ 1/4 ] RECORD JSON-ENCODE |
'{"a":0.25}' |
有限の十進表記を持つ数はJSONの数として書かれる。 |
1/3 JSON-ENCODE |
'"1/3"' |
そうでない数は字句を文字列に入れて書く。丸めない。 |
2 SQRT JSON-ENCODE NIL-REASON |
'domainMiss' |
JSONに像を持たない値は理由付きのNIL。 |
[ 'a' ] [ 1/4 ] RECORD 'R' BIND R R JSON-ENCODE JSON-DECODE EQ |
TRUE |
像を持つ値は往復で元に戻る。 |
契約: 文字列
CHARS Semantic Kernel
テキストを1文字ずつのテキストに分割する。'héllo' CHARSは[ 'h' 'é' 'l' 'l' 'o' ]、'' CHARSは[ ]。元に戻すのはJOIN。テキストのベクトルは持ち上がり、テキストでないものはnonText。
- 構文
'hi' CHARS- スタック効果
- [ text ] -> [ chars ]
- スタック
- 入力 1 → 出力 1
- オペランド
leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonText- 条項
LANG.VALUES.DISJOINT,LANG.FAILURE.TRICHOTOMY,LANG.COLLECTIONS.LIFT
JOIN Semantic Kernel
テキストのベクトルを端と端でつないで1つのテキストにする。[ 'h' 'i' ] JOINは'hi'、[ ] JOINは''なので、CHARS JOINでテキストが元に戻る。すべての要素はテキストでなければならず(nonText)、ベクトルでないものはnonVector。
- 構文
[ 'h' 'i' ] JOIN- スタック効果
- [ texts ] -> [ text ]
- スタック
- 入力 1 → 出力 1
- オペランド
data(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonVector,nonText- 条項
LANG.VALUES.DISJOINT,LANG.FAILURE.TRICHOTOMY
TRIM Standard · algorithm
テキストの両端の空白を取り除く。' a b ' TRIMは'a b'で、内側の空白は残る。テキストのベクトルは持ち上がり、テキストでないものはnonText。
- 構文
' hi ' TRIM- スタック効果
- [ text ] -> [ text' ]
- スタック
- 入力 1 → 出力 1
- オペランド
leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonText- 条項
LANG.VALUES.DISJOINT,LANG.FAILURE.TRICHOTOMY,LANG.COLLECTIONS.LIFT
UPPER Standard · algorithm
Unicodeの既定の(ロケールに依存しない)大小文字対応で、すべての文字を大文字に写したString。'Ajisai' UPPERは'AJISAI'、'straße' UPPERは'STRASSE'。大文字の形を持たない文字はそのまま残るので、答えはオペランドより長くなることはあっても短くなることはない。StringでないオペランドはERROR(nonText)。対応表はUnicodeのもので、CHARSとJOINの上の定義では運べないので、このワードはネイティブである。
- 構文
'Ajisai' UPPER- スタック効果
- [ text ] -> [ text' ]
- スタック
- 入力 1 → 出力 1
- オペランド
leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonText- 条項
LANG.VALUES.DISJOINT,LANG.FAILURE.TRICHOTOMY,LANG.COLLECTIONS.LIFT
LOWER Standard · algorithm
Unicodeの既定の(ロケールに依存しない)大小文字対応で、すべての文字を小文字に写したString。'Ajisai' LOWERは'ajisai'、'ΣΑΣ' LOWERは'σασ'。語末シグマの規則をはじめ、言語固有の規則は適用しない。同じテキストはどこで実行しても同じように小文字になる。StringでないオペランドはERROR(nonText)。対応表はUnicodeのもので、CHARSとJOINの上の定義では運べないので、このワードはネイティブである。
- 構文
'Ajisai' LOWER- スタック効果
- [ text ] -> [ text' ]
- スタック
- 入力 1 → 出力 1
- オペランド
leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonText- 条項
LANG.VALUES.DISJOINT,LANG.FAILURE.TRICHOTOMY,LANG.COLLECTIONS.LIFT
TOKENIZE Standard · algorithm
区切り文字が現れるたびに文字列を分け、部分文字列のベクトルにする。'a,b,c' ',' TOKENIZEは[ 'a' 'b' 'c' ]。空の区切り文字はCHARSと同じくすべての文字の間で分ける。空の区切り文字に対する逆はJOIN。
- 構文
'a,b,c' ',' TOKENIZE- スタック効果
- [ text ] [ sep ] -> [ parts ]
- スタック
- 入力 2 → 出力 1
- オペランド
leaf,leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonText,shapeMismatch- 条項
LANG.VALUES.DISJOINT,LANG.FAILURE.TRICHOTOMY,LANG.COLLECTIONS.LIFT
SEARCH Standard · algorithm
あるテキストが別のテキストの中に最初に現れる位置(文字単位)。'hello world' 'world' SEARCHは6で、CHARSと同じ数え方をする。'hello' 'z' SEARCHはNIL(notFound)。空の探すテキストは0で見つかる。これはテキスト版のINDEX-OFで、CHARSの上で書けば位置ごとに窓を比較することになるが、このワードは1回の走査で行う。
- 構文
'hello world' 'world' SEARCH- スタック効果
- [ text ] [ needle ] -> [ index ]
- スタック
- 入力 2 → 出力 1
- オペランド
leaf,leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthroughThenProject; 投影:notFound→notFound- 部分性
projecting- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonText,shapeMismatch- 条項
LANG.VALUES.DISJOINT,LANG.FAILURE.TRICHOTOMY,LANG.COLLECTIONS.LIFT,LANG.FAILURE.PROJECT
REPLACE Standard · algorithm
あるテキストのすべての出現を別のテキストで置き換える。'a-b-c' '-' '+' REPLACEは'a+b+c'。出現は左から順に、重ならずに見つける。空のfromは何にも一致しないので、テキストは際限なく伸びるのではなく変わらずに戻る。CHARSとJOINの上で書けば位置ごとの窓による走査になるが、このワードはそれを1回の走査で行う。
- 構文
'a-b-c' '-' '+' REPLACE- スタック効果
- [ text ] [ from ] [ to ] -> [ text' ]
- スタック
- 入力 3 → 出力 1
- オペランド
leaf,leaf,leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonText,shapeMismatch- 条項
LANG.VALUES.DISJOINT,LANG.FAILURE.TRICHOTOMY,LANG.COLLECTIONS.LIFT
NUM Semantic Kernel
テキストを、ソースのリテラルを読むのと同じ文法で数として解析する。'3/4' NUMは3/4、'0.25' NUMは1/4。数を綴らないテキスト('abc'、'.5'、'1_000'、'1/0')はNIL(invalidEncoding)を投影し、指数を含めて数値リテラルの上限がソースのリテラルに許すより多くの桁を綴るテキスト('1e99999999')はNIL(spaceExhausted)を投影する。StringでないオペランドはERROR(nonText)。Stringのベクトルは持ち上がる。
- 構文
'42' NUM- スタック効果
- [ text ] -> [ n ]
- スタック
- 入力 1 → 出力 1
- オペランド
leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthroughThenProject; 投影:parseFailure,materializationBudgetExceeded→invalidEncoding,spaceExhausted- 部分性
projecting- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonText- 条項
LANG.VALUES.DISJOINT,LANG.FAILURE.TRICHOTOMY,LANG.COLLECTIONS.LIFT,LANG.FAILURE.PROJECT
STR Semantic Kernel
スカラー、String、真偽値を、読み戻すとそれになるテキストとして書く。42 STRは'42'、1/3 STRは'1/3'、TRUE STRは'TRUE'。オペランドは葉なので、ベクトルやRecordは持ち上がる。[ 1 2 ] STRは[ '1' '2' ]。STRは要素ごとにNUMの逆であり、1/3 STR NUMは1/3、x STR NUMは字句を持つすべての数を返す。テキストは封じられた数の文法のアルファベットなので、その中に字句を持たない数(2 SQRTのような厳密な無理数)には忠実なテキストがなく、有理数の似姿で答えるのではなくNIL(domainMiss)を投影する。これはJSON-ENCODEがJSONに像を持たない値に投影する理由と同じである。近似を明示して描きたいときはFORMATを使う。
- 構文
42 STR- スタック効果
- [ x ] -> [ text ]
- スタック
- 入力 1 → 出力 1
- オペランド
leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthroughThenProject; 投影:noExactLexemeForValue→domainMiss- 部分性
projecting- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- 条項
LANG.VALUES.DISJOINT,LANG.FAILURE.TRICHOTOMY,LANG.COLLECTIONS.LIFT,LANG.FAILURE.PROJECT
FORMAT Standard · operational
厳密なスカラーを、小数点以下の桁数を指定した十進テキストとして描く。ちょうど半分はROUNDとまったく同じく0から遠い側へ丸める。1/3 5 FORMATは'0.33333'、5/2 0 FORMATは'3'、2 SQRT 3 FORMATは'1.414'。値が丸められる唯一の場所であり、そこから出ていくのはテキストであって決して数ではない。算術は丸めを行わず、STRは厳密な字句を持たない数を断るので、十進の近似が欲しいプログラムは表示の境界であるここで精度を名指す。桁数は非負の整数(そうでなければinvalidInteger)、値はスカラー(そうでなければnonNumeric)。体の順序は決して同点にならないので、無理数を含めすべての桁が厳密に決まる。
- 構文
1/3 5 FORMAT- スタック効果
- [ x ] [ digits ] -> [ text ]
- スタック
- 入力 2 → 出力 1
- オペランド
leaf,leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonNumeric,invalidInteger,shapeMismatch- 条項
LANG.VALUES.EXACT,LANG.VALUES.DISJOINT,LANG.FAILURE.TRICHOTOMY,LANG.COLLECTIONS.LIFT
JSON-DECODE Standard · algorithm
JSONテキストを値として読む。'[1, 2]' JSON-DECODEは[ 1 2 ]。オブジェクトはメンバー名をその順にキーとするRecord、配列はベクトル、文字列はString、数はそれが綴る厳密な有理数('0.1' JSON-DECODEは厳密に1/10)、true/falseは真偽値、nullはNILになる。1つのJSON値でないテキスト(壊れている、空、末尾に余分がある、オブジェクトが同じメンバーを2回名指す)はinvalidEncodingを投影する。これはNUMが数を綴らないテキストに投影する理由と同じである。入れ子の深さはワードではなくテキストが決めるので、すでにあるベクトルの上でしか反復しないこの言語では書けない。入れ子の上限を超えた値や、数値リテラルの上限より多くの桁の数はspaceExhaustedを投影する。上限を超えるあらゆる具体化の結末である(LANG.MACHINE.LIMITS)。StringでないオペランドはERROR(nonText)。
- 構文
'{"a": 1, "b": [true, null]}' JSON-DECODE- スタック効果
- [ text ] -> [ value ]
- スタック
- 入力 1 → 出力 1
- オペランド
leaf(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthroughThenProject; 投影:textIsNotJson,materializationBudgetExceeded→invalidEncoding,spaceExhausted- 部分性
projecting- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonText- 条項
LANG.VALUES.DISJOINT,LANG.RECORDS.STRUCTURE,LANG.VALUES.EXACT,LANG.COLLECTIONS.LIFT,LANG.FAILURE.PROJECT
JSON-ENCODE Standard · algorithm
値をJSONテキストとして書く。JSON-DECODEの逆である。StringをキーとするRecordはキー順のオブジェクト、ベクトルは配列、Stringは文字列、真偽値はtrue/false、NILはnullになる。有限の十進表記を持つ有理数(分母が2^a·5^bの形)はJSONの数として厳密に書かれ(1/4は0.25)、それ以外の有理数はAjisaiの字句を文字列に入れて書かれる(1/3は"1/3")。どの桁も丸めて捨てられることはない。エンコーダは値が黙って精度を失う場所ではない。JSONに像を持たない値(Symbol、無理数、Stringでないキーを持つRecord)はdomainMissを投影する。このワードが書いたものを解読すると与えた値が戻り、字句として書かれた有理数はそのStringとして戻るので、NUMで数を取り戻せる。
- 構文
[ 'a' ] [ 1 ] RECORD JSON-ENCODE- スタック効果
- [ value ] -> [ text ]
- スタック
- 入力 1 → 出力 1
- オペランド
element(LANG.FAILURE.PASSTHROUGH)- NIL方針
consumeNil; 投影:valueHasNoJsonImage→domainMiss- 部分性
projecting- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- 条項
LANG.VALUES.DISJOINT,LANG.RECORDS.STRUCTURE,LANG.VALUES.EXACT,LANG.FAILURE.PROJECT
2.4.2.5 選択と反復(SELECT, MAP)
条件分岐(SELECT)
SELECTは3つの引数を取ります。trueのときの値、falseのときの値、そしてどちらを選ぶかを決める真偽値です。[ whenTrue ] [ whenFalse ] [ mask ] SELECTと並べます。真偽値を最後に置くのは、何をするかを決めるオペランドを最後に置くという他のワードと同じ並びだからです。
SELECTは何も評価しません。2つの候補は、プログラムが既に組み立て終えた値です。つまりどちらの候補も、SELECTに届く前に一度ずつ、書かれた順に計算され終わっています。選ばれなかった側が「実行されない」のではなく、選ぶ時点で両方とも既に値になっている、ということです。ブロックを受け取って中身を走らせるワード(MAPやEXEC)とは、ここが根本的に違います。
したがってPRINTのような作用は候補の中には書けません。書いても両方printされます。答えを選んでから、一度だけ作用させてください。[ 'big' ] [ 'small' ] [ 5 ] [ 3 ] GT SELECT PRINTのように、SELECTの後ろに置きます。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 'negative' ]
[ 'positive' ]
[ -5 ] [ 0 ] LT
SELECT |
[ 'negative' ] |
2つの候補を積んでから、選ぶ真偽値を積む。 |
要素ごとに選ぶ
真偽値がベクトルなら、選択も要素ごとに起こります。比較ワードも要素ごとに持ち上がるので、[ 1 2 3 ] [ 2 ] GTは[ FALSE FALSE TRUE ]という真偽値のベクトルを答えます。これをそのままSELECTに渡せば、ベクトル全体の分岐が1つの式で書けます。長さ1の候補は、もう一方の長さに合わせて繰り返し使われます。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 0 ] [ -3 5 -1 ] [ -3 5 -1 ] [ 0 ] LT SELECT |
[ 0/1 5/1 0/1 ] |
負の要素だけを0に置き換える。長さ1の[ 0 ]が3要素に行き渡る。 |
複数の条件を組み合わせるときは、真偽値のベクトルどうしをAND・NOTでつなぎます。これらも要素ごとに持ち上がります。[ 3 6 9 ] [ 5 ] GT [ 3 6 9 ] [ 8 ] LT ANDは[ FALSE TRUE FALSE ]です。
3つ以上に分ける
SELECTが選ぶのは常に2つのうちの1つです。3つ以上に分けたいときはSELECTを入れ子にします。上から順に試して最初に当たったものが勝つ、という読み方はしません。どちらが優先かは入れ子の形としてそのまま見えています。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 'zero' ]
[ 'negative' ] [ 'positive' ] [ 0 ] [ 0 ] LT SELECT
[ 0 ] [ 0 ] EQ
SELECT |
[ 'zero' ] |
内側のSELECTが外側のfalse側の候補になる。 |
どちらも選ばれないとき
真偽値の位置に不在(NIL)が来たら、それはUNKNOWNです(三値論理)。UNKNOWNはどちらの候補も選ばず、読み取ったその不在を理由ごと答えます。ですからNIL-REASONで「なぜ分岐できなかったのか」を後から尋ねられます。
「どの節も当たらなかった」という状態はありません。候補は常に2つあり、真偽値はTRUE・FALSE・UNKNOWNのいずれかなので、SELECTは常に答えを持っています。else節を書き忘れて実行時に落ちる、ということが起こらないのはこのためです。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 'y' ] [ 'n' ] NIL SELECT |
[ NIL ] |
UNKNOWNはどちらも選ばず、その不在を答える。 |
真偽値の位置に裸の数値は書けません(nonTruthValue)。1や0は真偽値ではないからです(領域は互いに素)。数値で判定したいなら比較を書いてください。[ 1 ] [ 0 ] EQ NOTのようにです。
反復(MAP・FILTER・FOLD・SCAN)
コードブロック[ ]は第一級の値です。高階ワードは、ブロックや、定義済みワードの引用された名前を、ベクトルの各要素に適用します。
この4つが反復のすべてです。ワードが自分自身を呼び出して繰り返す書き方はありません。DEFが循環する定義を拒否するからです。したがって反復の回数は、必ず手元にある有限なベクトルの長さとして書かれており、ブロックを読む前から見えています。
ブロックが残したものが、そのまま写像後の要素になります。要素数1のベクトルは、他のどんな値とも同じく要素数1のベクトルです。したがって[ 1 2 ] [ 1 COLLECT ] MAPは[ [ 1/1 ] [ 2/1 ] ]であり、[ [ 1 ] [ 2 3 ] ] [ REVERSE ] MAPは[ [ 1/1 ] [ 3/1 2/1 ] ]です。結果がたまたま短いからといって、何かがそれを展開して取り出すことはありません。点が1つしかないクラスタや、出力が1つしかない行も、最後までその形を保ちます。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 1 2 3 ] [ 2 MUL ] MAP |
[ 2/1 4/1 6/1 ] |
各要素にブロックを適用する。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 2 MUL ] 'DBL' DEF [ 1 2 3 ] [ DBL ] MAP |
[ 2/1 4/1 6/1 ] |
定義済みのワードは、その引用された名前で渡せる。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 1 2 3 4 ] [ 2 DIV 'M' BIND M FLOOR M EQ ] FILTER |
[ 2/1 4/1 ] |
述語がtrueになる要素を残す(ここでは偶数)。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 1 2 3 4 ] [ 0 ] [ ADD ] FOLD |
[ 10/1 ] |
初期累積値付きで畳み込む:0 + 1 + 2 + 3 + 4。初期値の形が結果の形を決める:ここでは[ 0 ]というベクトルで始めたので、答えもベクトルの[ 10/1 ]になる。裸の0で始めれば答えも裸の10/1。 |
途中経過を残す畳み込み(SCAN)
SCANはFOLDと同じ歩き方をして、最後の累積値だけでなく、各要素の時点での累積値をすべて答えます。ブロックが見るものも残すものもFOLDと同一で、違うのは何が返るかだけです。
答えは入力と同じ長さになります。初期累積値は「種」であってレーンではないので、答えには含まれません。長さが揃うので、元のベクトルとZIPしたり、比較してSELECTのマスクにしたりできます。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 1 2 3 4 ] 0 [ ADD ] SCAN |
[ 1/1 3/1 6/1 10/1 ] |
累積和。同じ式をFOLDで書くと最後の10/1だけが返る。 |
これが、この言語で「前の結果を次に持ち越す」唯一の書き方です。ワードは自分自身を呼べず、ループもないので、走る途中の状態を運ぶ手段は累積値しかありません。FOLDはその状態を最後に1つだけ見せ、SCANは途中も見せる、という関係です。累積最大、残高の推移、列の上を進む状態機械、漸化式——順番に効いていく計算はすべてこの形になります。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 3 1 4 1 5 ] 0 [ MAX ] SCAN |
[ 3/1 3/1 4/1 4/1 5/1 ] |
累積最大。各時点までに現れた最大値。 |
空のベクトルは空のベクトルを答えます(レーンが0本なので答えも0本。種は返りません)。不在(NIL)のベクトルは、その不在を理由ごと答えます。ここはFOLDと違う点で、FOLDはどちらの場合も種を答えます——畳み込むものが何も無いなら答えは種だからです。答えの形が問いの形に従う、という同じ規則の裏表です。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 1 3 5 8 ] [ 2 DIV 'M' BIND M FLOOR M EQ ] MAP |
TRUE |
「少なくとも1つの要素が述語を満たすか」。MAPで真偽値のベクトルにし、FALSEから論理和で畳み込む。特定の値を含むかどうかだけなら[ 1 2 3 ] 2 MEMBER?(TRUE)のほうが簡単。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 2 4 6 8 ] [ 2 DIV 'M' BIND M FLOOR M EQ ] MAP |
TRUE |
「すべての要素が述語を満たすか」。TRUEからANDで畳み込む。 |
EXECはスタックから取ったコードブロックを実行する。
2つの引数を取るワードを書く
複数の値はスタック上ではなくベクトルの中を運ばれます。2つの引数を必要とするワードは、要素数2のベクトルを1つ取ります。呼び出す側は、[ 3 4 ]のように直接書くか、計算済みの値からCOLLECTで組み立てます。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 0 [ ADD ] FOLD ] 'PSUM' DEF
[ 3 4 ] PSUM |
7/1 |
引数はベクトルそのもの。展開するのではなく畳み込む。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ [ 1 -1 ] MUL 0 [ ADD ] FOLD ] 'PDIFF' DEF
2 5 MUL 3 2 COLLECT PDIFF |
7/1 |
非対称なワードは、ブロードキャストで位置に重みを付けてから畳み込む。2 COLLECTは計算済みの2つの値をまとめる。 |
畳み込みではなく1つの位置が必要なときは、GETで読み取ります。0 GETは最初の要素、-1 GETは最後の要素です。したがって[ SORT -1 GET ] 'PMAX' DEFは、2つの値の大きい方を答えます。REVERSEは2要素の組の順序を入れ替え、TAKEとCONCATを組み合わせれば、1つのベクトルの部分から別のベクトルを組み立て直せます。
複製のための道具はBINDです。値に名前を付ければ、名前を書くたびにその値が積まれるので、どんな種類の値でも必要な回数だけ使えます。
契約: 反復
MAP Semantic Kernel
ブロックをベクトルの各要素に順に適用し、結果のベクトルを答える。[ 1 2 3 ] [ 2 MUL ] MAPは[ 2 4 6 ]。ブロックの結果は、ベクトルを含め、最上位に残したもの(LANG.COLLECTIONS.HIGHER)。[ 1 2 ] [ 1 COLLECT ] MAPは[ [ 1 ] [ 2 ] ]。不在のベクトルはその不在を答える。ベクトルでないものはnonVector、ブロックでないものはnotExecutable、何も残さないブロックはblockContractViolation。
- 構文
[ 1 2 3 ] [ 2 MUL ] MAP- スタック効果
- [ vec ] [ body ] -> [ mapped ]
- スタック
- 入力 2 → 出力 1
- オペランド
data,control(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
partial- 純粋性 / 決定性
conditional/stateRelative- 作用
- なし
- ERROR条件
nonVector,notExecutable,blockContractViolation- 条項
LANG.COLLECTIONS.HIGHER
FILTER Standard · operational
述語ブロックがTRUEと答えるベクトルの要素を、順に答える。[ 1 2 3 4 ] [ 2 GT ] FILTERは[ 3 4 ]。ブロックの答えは真偽値として読まれ、UNKNOWNはFALSEと同じく要素を落とす。ベクトルでないものはnonVector、ブロックでないものはnotExecutable、何も残さないブロックはblockContractViolation、真偽値でない答えはnonTruthValue。
- 構文
[ 1 2 3 ] [ 2 EQ ] FILTER- スタック効果
- [ vec ] [ pred ] -> [ kept ]
- スタック
- 入力 2 → 出力 1
- オペランド
data,control(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
partial- 純粋性 / 決定性
conditional/stateRelative- 作用
- なし
- ERROR条件
nonVector,notExecutable,blockContractViolation,nonTruthValue- 条項
LANG.COLLECTIONS.HIGHER
FOLD Semantic Kernel
種から始め、累積値と各要素を順に組み合わせるブロックで、ベクトルを1つの値に畳む。[ 1 2 3 ] 0 [ ADD ] FOLDは6、[ 1 2 3 ] 10 [ SUB ] FOLDは4。空のベクトルは種を答える。[ ] 0 [ ADD ] FOLDは0。最後の累積値ではなくすべての累積値を答えるのはSCAN。ベクトルでないものはnonVector、ブロックでないものはnotExecutable、何も残さないブロックはblockContractViolation。
- 構文
[ 1 2 3 ] 0 [ ADD ] FOLD- スタック効果
- [ vec ] [ init ] [ combine ] -> [ result ]
- スタック
- 入力 3 → 出力 1
- オペランド
data,element,control(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
partial- 純粋性 / 決定性
conditional/stateRelative- 作用
- なし
- ERROR条件
nonVector,notExecutable,blockContractViolation- 条項
LANG.COLLECTIONS.HIGHER
SCAN Standard · operational
ベクトルを一歩ずつ畳み、最後の累積値だけでなく各要素の後の累積値を答える。[ 1 2 3 4 ] 0 [ ADD ] SCANは[ 1/1 3/1 6/1 10/1 ]。答えは入力のレーン1本につき1本のレーンを持つ。初期累積値は種であってレーンではないので含まれない。これがあるから、SCANの結果を元のベクトルと組にできる。ブロックが見るのはFOLDと同じく累積値と現在の要素で、残したものが次の累積値であり、そのレーンの答えでもある。空のベクトルは空のベクトルを、不在のベクトルはその同じ不在を答える。
- 構文
[ 1 2 3 4 ] 0 [ ADD ] SCAN- スタック効果
- [ vec ] [ init ] [ combine ] -> [ steps ]
- スタック
- 入力 3 → 出力 1
- オペランド
data,element,control(LANG.FAILURE.PASSTHROUGH)- NIL方針
passthrough; 投影: なし- 部分性
partial- 純粋性 / 決定性
conditional/stateRelative- 作用
- なし
- ERROR条件
nonVector,notExecutable,blockContractViolation- 条項
LANG.COLLECTIONS.HIGHER
契約: 制御
EXEC Semantic Kernel
ブロックをその場で実行し、結果をスタックに残す。[ 1 2 ADD ] EXECは3。他のコードと同じくスタックを読み書きするので、2 [ 3 MUL ] EXECは6。ブロックでないものはnotExecutable。
- 構文
[ 1 2 ADD ] EXEC- スタック効果
- [ code ] -> [ result... ]
- スタック
- 入力 1 → 出力 variable
- オペランド
control(LANG.FAILURE.PASSTHROUGH)- NIL方針
rejectNil; 投影: なし- 部分性
partial- 純粋性 / 決定性
conditional/stateRelative- 作用
- なし
- ERROR条件
notExecutable- 条項
LANG.MACHINE.TRANSFORMERS,LANG.SOURCE.CODE
FAIL Semantic Kernel
プログラムが述べるERRORを起こす。'width must be positive' FAILは分類declaredFailure、そのテキストをメッセージとして評価を止める。これはABSENTがユーザーワードに与えるものの残り半分で、三分法の3つ目の結末、つまりうまくいかなかったデータではなく誤った呼び出しのためのものである。他のERRORと同じく伝播し、捕まえられない。回復できる値が欲しい呼び出し側はABSENTを求める。テキストでないオペランドはnonText。
- 構文
'width must be positive' FAIL- スタック効果
- [ message ] -> [ ]
- スタック
- 入力 1 → 出力 0
- オペランド
control(LANG.FAILURE.PASSTHROUGH)- NIL方針
rejectNil; 投影: なし- 部分性
partial- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonText,declaredFailure- 条項
LANG.FAILURE.TRICHOTOMY,LANG.FAILURE.ERROR
2.4.2.6 不在を扱う(NIL?, ABSENT)
NILを調べる: NIL?とNIL-REASON
この2つのワードは、NILを調べます。NIL?はスタック最上位の値がNILかどうかを尋ね、NIL-REASONはそれが持つ理由を読み取ります。どちらも他のワードと同じく調べた値を消費し、その場所に答えを積みます。調べたあとも値を使いたいなら、先にBINDで名前を付けてください。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
1 0 DIV NIL? |
TRUE |
NIL?は最上位の値を消費し、その場所に答えを積む。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
1 0 DIV NIL-REASON |
'divisionByZero' |
理由はプロトコル文字列なので、プログラムは値が欠けている理由によって分岐できる。 |
自分で結末を宣言する: ABSENTとFAIL
不在もエラーも、組み込みワードだけのものではありません。プログラム自身が「ここには値がない」「ここは誤用だ」と宣言できます。ABSENTは文字列を1つ受け取り、その文字列を理由にしたNILを積みます。理由の識別子はuserDeclaredですが、NIL-REASONが答えるのは渡した文字列そのものです。文字列は値の一部なので、違う文字列で作った2つのABSENTは別の値です。
FAILも文字列を1つ受け取り、その文字列をメッセージにしたERRORを起こします(分類はdeclaredFailure)。ERRORなので評価はそこで止まり、どのワードにも捕まえられません。FAILはABSENTの「強い版」ではなく、不在と誤用の区別をプログラムの側から引くためのワードです。回復させたいならABSENT、呼び出し側の誤りとして止めたいならFAILを使ってください。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
'rate not quoted' ABSENT NIL-REASON |
'rate not quoted' |
宣言した文字列がそのまま理由になる。 |
'rate not quoted' ABSENT 'S' BIND 0 S S NIL? SELECT |
0/1 |
普通のNILなので、下の回復の書き方がそのまま効く。 |
1 'width must be positive' FAIL 2 |
error | メッセージはwidth must be positive。評価は止まり、後続の2は実行されない。オペランドの1と文字列はスタックに残る。 |
代わりの値を与える: BIND・NIL?・SELECT
不在の回復に専用の構文はありません。調べる値にBINDで名前を付け、SELECTとNIL?を並べるだけです。
NIL?は他のワードと同じく調べた値を消費し、それが不在だったかどうかの真偽値を積みます([ x ] -> [ bool ])。そこで調べる値に一度だけ名前を付け、2回読みます。調べる値 'S' BIND 代わりの値 S S NIL? SELECTと書けば、積まれる順番がSELECTの求める順番(真のときの候補、偽のときの候補、真偽値)とちょうど一致し、「値があればその値、なければ代わりの値」になります。
代わりの値は普通のオペランドなので、他のオペランドと同じくSELECTより前に計算され終わります。値がある場合でも代わりの値の計算は行われる、ということです(SELECTの節を参照)。重い計算やPRINTのような作用を代わりの値の位置に書かないでください。
NIL?が尋ねるのは値全体についてです。ベクトルの中の1レーンがNILでも、そのベクトル自体は不在ではないのでFALSEと答えます。[ 1 NIL ] 'S' BIND [ 0 0 ] S S NIL? SELECTは[ 1/1 NIL ]であり、レーンの不在は回復されません。レーンを回復したいなら、ベクトルの外側ではなく内側で回復してください。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
1 0 DIV 'S' BIND 99 S S NIL? SELECT |
99/1 |
不在だったので代わりの値が選ばれる。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
7 2 DIV 'S' BIND 99 S S NIL? SELECT |
7/2 |
結果が実際の値であるとき、代わりの値は選ばれない。 |
契約: 不在(NIL)
NIL Semantic Kernel
プログラムに書かれた不在。NIL NIL?はTRUEで、理由はliteral。NIL NIL-REASONは'literal'(LANG.VALUES.NIL)。プログラム自身の理由を持つ不在はABSENTで書く。
- 構文
NIL- スタック効果
- -> [ NIL ]
- スタック
- 入力 0 → 出力 1
- NIL方針
preserveReason; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- 条項
LANG.VALUES.NIL,LANG.FAILURE.RECOVERY
NIL? Semantic Kernel
値が不在かどうか。1 0 DIV NIL?はTRUE、5 NIL?はFALSE。値全体について問うので、NILを持つベクトルは存在している。[ 1 NIL ] NIL?はFALSE。SELECTと組んで代わりの値を選ぶ(LANG.FAILURE.RECOVERY)。
- 構文
1 0 DIV NIL?- スタック効果
- [ x ] -> [ TRUE | FALSE ]
- スタック
- 入力 1 → 出力 1
- オペランド
element(LANG.FAILURE.PASSTHROUGH)- NIL方針
consumeNil; 投影: なし- 部分性
total- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- 条項
LANG.VALUES.NIL,LANG.FAILURE.RECOVERY
NIL-REASON Semantic Kernel
不在が持つ理由をテキストで答える。1 0 DIV NIL-REASONは'divisionByZero'で、ABSENTが作ったNILは与えられたテキストを答える。理由はNILの観測できる内容のすべてなので(LANG.VALUES.NIL)、これがプログラムがそれを読む方法である。NILでない値は、問いの定義域の外にある整ったオペランドなので、SQRTに対する負の数と同じくNIL(domainMiss)を投影する。
- 構文
1 0 DIV NIL-REASON- スタック効果
- [ x ] -> [ text ]
- スタック
- 入力 1 → 出力 1
- オペランド
element(LANG.FAILURE.PASSTHROUGH)- NIL方針
consumeNil; 投影:valueIsNotNil→domainMiss- 部分性
projecting- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- 条項
LANG.VALUES.NIL,LANG.FAILURE.RECOVERY,LANG.FAILURE.PROJECT
ABSENT Semantic Kernel
プログラムが理由を述べるNIL。'rate not quoted' ABSENT NIL?はTRUE、'rate not quoted' ABSENT NIL-REASONは'rate not quoted'を答える。登録された理由はuserDeclaredで、テキストがNIL-REASONの答える理由になる。だからユーザーワードも、Coreワードの契約とまったく同じように答えがない理由を言え、呼び出し側も同じ書き方subject 'S' BIND fallback S S NIL? SELECTで回復できる。テキストは値の一部なので(LANG.VALUES.NIL)、テキストの違う2つの不在は2つの値である。テキストでないオペランドはプログラムの誤り。
- 構文
'rate not quoted' ABSENT- スタック効果
- [ reason ] -> [ NIL ]
- スタック
- 入力 1 → 出力 1
- オペランド
control(LANG.FAILURE.PASSTHROUGH)- NIL方針
createsNil; 投影:always→userDeclared- 部分性
projecting- 純粋性 / 決定性
pure/deterministic- 作用
- なし
- ERROR条件
nonText- 条項
LANG.VALUES.NIL,LANG.FAILURE.TRICHOTOMY,LANG.FAILURE.RECOVERY,LANG.FAILURE.PROJECT
2.4.2.7 出力(PRINT)
PRINTは、スタック最上位の値を出力領域に書き出します。他のどのワードとも同様、オペランドを消費します。値をスタックにも残したい場合は、BINDで名前を付けてから名前を2回書きます。Playgroundは常に最終的なスタックを表示するので、PRINTは結果を見るためではなく、出力チャンネルのためのものです。
引用符は表示上の便宜
文字列は、数値ベクトルと見分けられるように、スタック上では'TEST'のようにシングルクォートで囲まれて表示されます。この引用符はStack表示に属するものであり、値には属しません。PRINTは生のテキストを書き出すので、'TEST'はTESTとして出力されます。ただしテキストの一部である引用符文字はそのまま保たれるので、内容がT'ES'Tである文字列はT'ES'Tとして出力されます。数と真偽値は、スタック上に現れるのとまったく同じ姿で出力されます。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
42 'N' BIND N N PRINT |
42/1 |
値は出力領域に送られ、名前で積んだもう1つがスタックに残る。 |
| サンプルコード | 期待値 | 注記 |
|---|---|---|
42 PRINT |
— | 既定モードはオペランドを消費する:スタックは空になり、値は出力領域にのみ現れる。 |
| サンプルコード | 期待される出力 | 注記 |
|---|---|---|
'TEST' PRINT |
TEST |
スタックは文字列を'TEST'として表示する。出力は表示用の引用符を落とし、生のテキストを書き出す。 |
契約: 出力
PRINT Semantic Kernel
値を消費して出力に書く。42 PRINTは42/1を書き、何も残さない。テキスト以外の値はスタックの表示どおりに書かれる。テキストはスタックが示す引用符なしに生の文字として書かれ('TEST'はTESTとして出力される)、ベクトルの中に入れ子になったテキストは引用符を保つ。出力は機械の外へ出る唯一の作用である(LANG.EFFECTS.OUTPUT)。
- 構文
42 PRINT- スタック効果
- [ x ] -> [ ]
- スタック
- 入力 1 → 出力 0
- オペランド
element(LANG.FAILURE.PASSTHROUGH)- NIL方針
consumeNil; 投影: なし- 部分性
total- 純粋性 / 決定性
effectful/hostRelative- 作用
consoleWrite- 条項
LANG.EFFECTS.OUTPUT,LANG.MACHINE.ORDER
付録 A よくあるパターン
Ajisaiの語彙は78個のワードで、意図的に小さく保たれています。ユーザー定義が同じコストで表現できるワードは、Coreには属しません。そのため、最小値の位置を求めるargminや緩和ループのような処理は、短いフレーズの組み合わせとして書きます。よく使うものを以下にまとめます。それぞれ検証済みであり、2回以上使うなら自分のDEFの本体として上出来です。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 1 2 3 4 ] 1 [ MUL ] FOLD |
24/1 |
積。和は0 [ ADD ] FOLDであり、同じFOLDの形でMINやMAXを使えば極値になる。 |
[ 1 2 3 ] [ 4 5 6 ] MUL 0 [ ADD ] FOLD |
32/1 |
内積:要素ごとに掛けてから合計する。 |
[ [ 1 2 ] [ 3 4 ] ] 'A' BIND [ 5 6 ] 'V' BIND |
[ 17/1 39/1 ] |
行列とベクトルの積:行ごとに1つの内積。 |
[ 4 1 7 1 ] ORDER 0 GET |
1/1 |
argmin:昇順の最初の位置。代わりに-1 GETを使えばargmaxになる。 |
[ 1 2 3 4 5 ] 'XS' BIND |
2/1 |
分散:中心化し、二乗し、平均する。標準偏差はさらにSQRTを1回。 |
キーによる順序付け: 同点も含めて
値をソートしてから元の位置を調べ直すのは、順序付けを得る自明な方法ですが、まさに重要な場面で間違った答えを出します。距離が等しい2つがあると、SORTしてからINDEX-OFすると同じ位置を2回見つけてしまい、「最も近い3つ」が2つの近傍を返したり4つを返したりし得ます。ORDERはSORTが捨ててしまう問い、すなわちどの位置がどの順かに答え、しかも安定に答えます。したがって等しいキーは、書かれていた順序を保ちます。
| サンプルコード | 期待値 | 注記 |
|---|---|---|
[ 18 13 1 1 13 2 ] ORDER 3 TAKE |
[ 2/1 3/1 5/1 ] |
6つの距離のうち最も近い3つ。うち2つは1で同点。3 TAKEを外せば順序全体、[ 2/1 3/1 5/1 1/1 4/1 0/1 ]が得られる。ペイロードを一緒に運ぶには、置換を束縛してそれで収集する:XS ORDER 'P' BIND YS P GET。 |
付録 B 同一性モデル
AjisaiではEQ、LT、GTのような普通の比較ワードを書けますが、言語は舞台裏でいくつもの「同じか?」という問いを別々に保っています。
| 層 | 利用者にとっての意味 | 重要な理由 |
|---|---|---|
| 値の等価性 | これらは同じ数学的な値か? | 比較ワードが使うのはこれ。 |
| 構造的同一性 | 内部で同じ方法で格納されているか? | 利用者からは観測できない。実装は表現を自由に最適化してよい。 |
| 表示上の等価性 | 描画された2つの文字列は同じに見えるか? | 表示は人間の助けにはなるが、等価性の証明にはならない。 |
| 適合等価性 | 実装は同じ観測結果を生むか? | 移植版が同じAjisai言語であることを示す方法。 |
比較ワードは最上位の層でのみ働きます。あらゆるAjisaiの値にわたって厳密かつ全域的であり、数学的な問いに答えます。それより下の層は値のモデルには関与しないので、表現や表示が比較の結果に影響することは決してありません。