letマップ付きグラフ
式を名前に束縛することは極めてよくある。letマップとは、この束縛機構を表すデータ構造だとする。ラムダ計算のlet式に相当する。再帰(自己言及)向けのletをletrecとして分けることが多いが、ここでは、letのなかにletrecも含めて考える。
letマップのひとつの項目をletエントリーと呼ぶことにする。letエントリーは、名前とその名前が指す対象物へのポインターリンクから構成される。
例題として次のスキーマを考える。
type Friend = {
"name": string,
"email": string?,
"addr" : string?,
"bestFriend": bestFriendChain
};type bestFriendChain = {
"name": string,
"bestFriend": bestFriendChain,
}?;
名前 Friend, bestFriendChain に対応する2つのletエントリーも含めて図示すると次のようになる。

ピンクと赤が、名前による参照に関わるのノードと辺。ピンク楕円のノードは、名前参照ノードで、構文的には「単なる名前の出現」に相当する。出現した名前は、いったんletエントリーを経由して他の式を参照することになる。letマップは名前空間と言っても同じである。
インライン・ネーミング
fetch問い合わせ式では、より簡略な表現ができる。
{
"name": string,
"email": string?,
"addr" : string?,
"bestFriend": @= bestFriendChain {
"name": string,
"bestFriend": bestFriendChain,
}?
}
bestFriendChainという名前による参照があるが、参照される先の名前をインラインで定義できる。インライン・ネーミングの構文が @= で、直後の式に名前を与える。この例では、次の部分式に名前bestFriendChainが与えられている。
{
"name": string,
"bestFriend": bestFriendChain,
}?
この部分式内に名前参照bestFriendChainが出てくるので、結果的に再帰構造となる。
インライン・ネーミングの場合も、暗黙のletマップを備えたグラフ構造なのだと考えることができる。式全体には名前が与えられてないが、空白の(あるいはデフォルトの)letエントリーが式全体を指しているとする。以下の図となる。

サイクリックグラフ
空白のletエントリーは省略して、letマップの枠線も省略すると次の図になる。

bestFriendChainという名前により2つのノードが結ばれている。bestFriendChainという名前は、ローカルに定義したもので、別の名前、例えば foo にリネームしてもかまわない。論理の限量子束縛変数や、ラムダ計算のラムダ変数と同じく、名前の綴りはなんでもよくて自由にリネーム(アルファ変換)できる。なので、名前の綴りも無視してしまうと次の図になる。

図のピンクと赤の部分は、自分自身に戻る辺=サイクルを表現するための方便に過ぎない。テキスト構文でサイクル(やシェアリング)を表現するためには、中間に名前を挟む必要がある。しかし、この一時的な名前は単なる便宜的な手段なので、図では不要なのだ。
結局、letマップと名前参照ノードを使って表現したかったスキーマのデータ構造は、次のようなサイクリックグラフだったわけだ。

テキスト構文として書き下す都合で、名前はどうしても必要だ。名前のメカニズムがletマップ(名前の定義)と参照ノード(名前の出現)だが、図=グラフとしては不要であり、中間的・一時的な名前を排除すると事情が単純になる。
以下にグラフ描画のソース。ほとんどコピペで作ったが全部載せておく。
/* -*- coding: utf-8 -*- */
/* letMapppedGraph-1.caty */
[
[
gv:node --shape=box --style=filled --fillcolor=pink --label="bestFriendChain" e_bestFriendChain,
gv:node --shape=box --style=filled --fillcolor=pink --label="Friend" e_Friend,
] | gv:cluster --label="let map" letMap,
gv:edge --color=red e_bestFriendChain bestFriendChain,
gv:edge --color=red e_Friend Friend,
/*
type Friend = {
"name": string,
"email": string?,
"addr" : string?,
"bestFriend": bestFriendChain
};
*/
gv:node --label="{}" Friend,
gv:node --label="string" Friend_name,
gv:node --label="string?" Friend_email,
gv:node --label="string?" Friend_addr,
gv:node --style=filled --fillcolor=pink --label="→" Friend_bestFriend,
gv:edge --label="name" Friend Friend_name,
gv:edge --label="email" Friend Friend_email,
gv:edge --label="addr" Friend Friend_addr,
gv:edge --label="bestFriend" Friend Friend_bestFriend,
gv:edge --color=red Friend_bestFriend e_bestFriendChain,
/*
type bestFriendChain = {
"name": string,
"bestFriend": bestFriendChain,
}?;
*/
gv:node --label="{}?" bestFriendChain,
gv:node --label="string" bestFriendChain_name,
gv:node --style=filled --fillcolor=pink --label="→" bestFriendChain_bestFriend,
gv:edge --label="name" bestFriendChain bestFriendChain_name,
gv:edge --label="bestFriend" bestFriendChain bestFriendChain_bestFriend,
gv:edge --color=red bestFriendChain_bestFriend e_bestFriendChain,
] | gv:graph letMappedGraph-1/* -*- coding: utf-8 -*- */
/* letMappedGraph-2.caty */
[
[
gv:node --shape=box --style=filled --fillcolor=pink --label="bestFriendChain" e_bestFriendChain,
gv:node --shape=box --style=filled --fillcolor=pink --label=" " e_Friend,
] | gv:cluster --label="let map" letMap,
gv:edge --color=red e_bestFriendChain bestFriendChain,
gv:edge --color=red e_Friend Friend,
/*
{
"name": string,
"email": string?,
"addr" : string?,
"bestFriend": @= bestFriendChain {
"name": string,
"bestFriend": bestFriendChain,
}?
}
*/
gv:node --label="{}" Friend,
gv:node --label="string" Friend_name,
gv:node --label="string?" Friend_email,
gv:node --label="string?" Friend_addr,
gv:edge --label="name" Friend Friend_name,
gv:edge --label="email" Friend Friend_email,
gv:edge --label="addr" Friend Friend_addr,
gv:edge --label="bestFriend" Friend bestFriendChain,
gv:node --label="{}?" bestFriendChain,
gv:node --label="string" bestFriendChain_name,
gv:node --style=filled --fillcolor=pink --label="→" bestFriendChain_bestFriend,
gv:edge --label="name" bestFriendChain bestFriendChain_name,
gv:edge --label="bestFriend" bestFriendChain bestFriendChain_bestFriend,
gv:edge --color=red bestFriendChain_bestFriend e_bestFriendChain,
] | gv:graph letMappedGraph-2/* -*- coding: utf-8 -*- */
/* letMappedGraph-3.caty */
[
gv:node --shape=box --style=filled --fillcolor=pink --label="bestFriendChain" e_bestFriendChain,
gv:edge --color=red e_bestFriendChain bestFriendChain,
/*
{
"name": string,
"email": string?,
"addr" : string?,
"bestFriend": @= bestFriendChain {
"name": string,
"bestFriend": bestFriendChain,
}?
}
*/
gv:node --label="{}" Friend,
gv:node --label="string" Friend_name,
gv:node --label="string?" Friend_email,
gv:node --label="string?" Friend_addr,
gv:edge --label="name" Friend Friend_name,
gv:edge --label="email" Friend Friend_email,
gv:edge --label="addr" Friend Friend_addr,
gv:edge --label="bestFriend" Friend bestFriendChain,
gv:node --label="{}?" bestFriendChain,
gv:node --label="string" bestFriendChain_name,
gv:node --style=filled --fillcolor=pink --label="→" bestFriendChain_bestFriend,
gv:edge --label="name" bestFriendChain bestFriendChain_name,
gv:edge --label="bestFriend" bestFriendChain bestFriendChain_bestFriend,
gv:edge --color=red bestFriendChain_bestFriend e_bestFriendChain,
] | gv:graph letMappedGraph-3/* -*- coding: utf-8 -*- */
/* letMappedGraph-4.caty */
[
gv:node --shape=point --color=pink e_bestFriendChain,
gv:edge --color=red e_bestFriendChain bestFriendChain,
/*
{
"name": string,
"email": string?,
"addr" : string?,
"bestFriend": @= bestFriendChain {
"name": string,
"bestFriend": bestFriendChain,
}?
}
*/
gv:node --label="{}" Friend,
gv:node --label="string" Friend_name,
gv:node --label="string?" Friend_email,
gv:node --label="string?" Friend_addr,
gv:edge --label="name" Friend Friend_name,
gv:edge --label="email" Friend Friend_email,
gv:edge --label="addr" Friend Friend_addr,
gv:edge --label="bestFriend" Friend bestFriendChain,
gv:node --label="{}?" bestFriendChain,
gv:node --label="string" bestFriendChain_name,
gv:node --style=filled --fillcolor=pink --label="→" bestFriendChain_bestFriend,
gv:edge --label="name" bestFriendChain bestFriendChain_name,
gv:edge --label="bestFriend" bestFriendChain bestFriendChain_bestFriend,
gv:edge --color=red bestFriendChain_bestFriend e_bestFriendChain,
] | gv:graph letMappedGraph-4/* -*- coding: utf-8 -*- */
/* letMappedGraph-5.caty */
[
/*
{
"name": string,
"email": string?,
"addr" : string?,
"bestFriend": @= bestFriendChain {
"name": string,
"bestFriend": bestFriendChain,
}?
}
*/
gv:node --label="{}" Friend,
gv:node --label="string" Friend_name,
gv:node --label="string?" Friend_email,
gv:node --label="string?" Friend_addr,
gv:edge --label="name" Friend Friend_name,
gv:edge --label="email" Friend Friend_email,
gv:edge --label="addr" Friend Friend_addr,
gv:edge --label="bestFriend" Friend bestFriendChain,
gv:node --label="{}?" bestFriendChain,
gv:node --label="string" bestFriendChain_name,
gv:edge --label="name" bestFriendChain bestFriendChain_name,
gv:edge --label="bestFriend" bestFriendChain bestFriendChain,
] | gv:graph letMappedGraph-5