Cognitionプログラミング言語の紹介
問題提起
- Lisp プログラマは、S式コードと関数的なマクロシステムによってメタプログラミングと汎用化されたシステムを作れると主張している
- ただし Lisp には根本的な問題がある
- メタプログラミングとプログラミングは同一でないため、Lisp には常に厳密な構文が存在する(括弧や look-ahead 用の特定文字)
- 左括弧は、Lisp に対して右括弧が現れるまで読み続けることを指示する
- これにより、左括弧と右括弧は言語内で不変になってしまう(概念的にはそうでないが、一部の実装では不可能)
- さらに重要なのは、こうしたトークンの区切り順序を文字列処理なしで後から変更することができないこと
- 他の言語でも、特定のトークンを見てこれから読む内容を決定する別の方法を持つ
- Cognition は、完全な後置記法(postfix)を使うアンチシンタックス(antisyntax)でこれを異なる形で扱う
- これは連鎖的なプログラミング言語に似ているが、連鎖的な言語にも二つの主要な問題がある
- 左括弧/右括弧文字の導入(事実上プレフィックス記法)
- 文字列用のクォート文字
- そのため一般的な言語としては不適切
- Lisp の C 文法実装でも同じ問題が確認できる(エスケープ文字の乱用、特定トークンの開始/終了を区別するために空白文字が必要)
- Racket はマクロシステムを持つが、実行時には動的でなく、プリプロセッサを利用する
Cognition紹介
Baremetal Cognition
- Baremetal Cognition は Brainfuck に似ているが、かなり高度なメタプログラミングが可能
- ブートストラップコードの例:
ldfgldftgldfdtgldf dfiff1 crank f
- 空白と改行は重要
- 2行目は
df の後に空白がある
- 3行目には空白文字がある
- これにより、区切り文字 (delimiter) と無視 (ignore) という2つの新しい概念を導入できる
Tokenization
- 区切り文字(Delimiter)は、トークナイザがトークンの開始と終了を区別できるようにする
- 単一文字トークナイザのリストは公開されており、Cognition 内で修正・読み取り可能
- 無視される文字 (Ignored character) は、read-eval-print ループの最初の段階でトークナイザによって完全に無視される
- つまり、トークン収集開始時に設定された無視文字集合をスキップする
- デフォルトでは、すべての文字が区切り文字で、無視文字は存在しない
- 区切り文字と無視文字のリストで指定された文字に対してブラックリスト/ホワイトリストのトグルが可能(簡潔性と実用性を提供)
Falias
- Falias は、スタックに置かれたときに実行される単語のリスト
- すべての Falias は、スタック最上位を実行する(Stem の
eval に相当)
f はデフォルトの Falias で、スタックに置かれず、スタック最上位の d を実行する
d は区切り文字リストを単語の文字列値として変更する(つまり、l 文字だけを区切り文字の対象外にする)
- デフォルト環境では、特殊な Falias を除いてどの単語も実行されない
区切り文字の注意点
- 区切り文字には興味深い規則がある
- トークナイズループで文字を無視しない場合、区切り文字は現在のトークンの一部として含まれ、そのまま進む
- これはシングレット(singlet)とは対照的(トークンに自分自身を含めてスキップし、トークン収集を終了する)
- ブラックリストかホワイトリストかも設定可能
- 区切り文字、シングレット、無視文字リストをブラックリスト/ホワイトリストできる
- デフォルトでは、ブラックリスト化された区切り文字、ホワイトリスト化されたシングレット、ホワイトリスト化された無視文字はない
- その他のすべての文字は現在のトークンの一部として収集され、区切り文字またはシングレットの規則でループが停止されるまで新しい文字を継続的に収集する
ブートストラップコード続き
ldf
gldftgldfdtgldf dfiff1 crank f
d が区切り文字なので gl がスタックに置かれ、Falias f が呼び出されて gl が区切り文字でない文字になる
tgl がスタックに置かれ、df によって区切り文字でない文字になる
dtgl がスタックに置かれ、\\ndf によって改行(\n)が、唯一区切り文字でない文字になる(改行は実コードに含まれる)
- 区切り文字の規則により、空白文字と
\\n がスタックに置かれる(3行目に空白が含まれる)
- さらにもう1つの
\\ \\n という単語がトークン化される
- 現在のスタックは次のようになっている(下から上へ):
3. dtgl
2. [空白文字]\n
- [空白文字]\n
df は \\ \\n を区切り文字でない文字に設定する
if は \\ \\n を無視文字として設定する(トークン化開始時に無視)
f は dtgl を実行して、区切り文字のホワイトリスト/ブラックリスト判定を保持する dflag をトグルする
- これで、区切り文字でない文字が区切り文字になり、区切り文字が区切り文字でない文字になる
- 最後に、空白と改行がトークンの区切りになり、トークン開始時に無視される
- 次に
1 がトークン化されてスタックに置かれ、crank 単語がトークン化された後 f によって実行される(この場合 1 トークンは数字として扱われるが、Cognition ではすべて単語である)
- ブートストラップシーケンス完了!
crank が何をするかは次のセクションで説明する
ブートストラッピング概要
- Cognition ではトークン化方式をプログラマブルに、実行時に動的に変更できる
- 他の言語では不可能なこと
- 外部言語向けのトークナイザを Cognition 内でプログラミングし、意図どおりにトークン化できる
- 後置記法を使い look-ahead をしないため可能
- 式を評価する前に、1つ以上のトークンを構文解析する必要がない
- Falias を使い、接頭辞単語やビルトイン単語を実行せずに単語実行を行える
Crank
- metacrank システムは、スタック上でトークンを実行する基本手段を設定できる
crank 単語は数値を引数として受け取り、スタックに n 個の単語が置かれるたびにスタック最上位を実行する
- 使用例 (
crank 1 が設定された状態):
5 crank 2
crank 2 crank
1 crank unglue swap quote prepose def
crank 1 環境では、トークン評価時に f の使用を停止できる
- トークン化された1つのトークンごとに1件評価される
- 改行と空白で区切られた構文をプログラムしているため、コードを直感的に解釈しやすい
- コードは
5 の評価を試みて開始する(ビルトインでないため自分自身を評価)
crank は「5個のトークンが置かれるたびに実行される」として評価されるよう設定される
2crank、2、crank、1 はすべてスタックに置かれる(crank 5 が設定されているので crank はビルトインだが実行されない):
4. 2crank
3. 2
2. crank
- 1
crank は5番目の単語なので実行される(crank 1 に設定)
unglue はビルトインとして、スタック最上位の単語(1 によって使用)の値を取得する
- つまり、
crank ビルトインと紐づく関数ポインタを取得する
- スタックは次のようになる:
3. 2crank
2. 2
- [CLIB]
- CLIB は
crank ビルトインを指す関数ポインタ
swap 実行:
3. 2crank
2. [CLIB]
- 2
quote(スタック最上位をクオートするビルトイン)実行:
3. 2crank
2. [CLIB]
- [2]
prepose(stem の compose に似ているが、前に置いて VMACRO と呼ぶものに置く)実行:
2. 2crank
- ([2] [CLIB])
def 呼び出し
2 をスタックに入れ、crank ビルトインを指す関数ポインタを呼び出す 2crank 単語を定義する
- VMACRO が何か、Cognition スタックと Stem スタックの違いを説明する必要がある
Stemとの違い
- Stem スタックでは単語を直接スタックに置くことができる
- Cognition では、単語は評価されずにコンテナに入れられてスタックに置かれる
- Stem では、
compose のような単語が単語(または単語1つを含むコンテナ)とは別のコンテナで動作する
- これにより Cognition の API がより一貫性を持つ
cd のような単語でもこの概念を利用する
マクロ
- Stem のクォートと Cognition コンテナのもう1つの違い
- マクロ評価時に、マクロ内部のすべてが評価され、crank は無視される
- 単語にバインドされると、その単語の評価時
1件のコメント
Hacker Newsの意見
幾つかの主要な意見を要約すると、