非局所制御フロー

引数解決

非局所制御フロー構文を除くと、引数解決の要因の優先順位は以下の順になる。

  1. 記憶装置の読み出し
  2. サブルーチン
  3. プリミティブ演算
  4. 呼び出し元ルーチンのサブルーチン固有ベクトルへの移行
  5. 真偽値型の false

この順序には一応の理由がある。記憶装置の読み出しが最上位なのは、プリミティブ演算 erase によって、引数解決をより下位の要因に譲ることができるからである。サブルーチンとプリミティブ演算は、いずれも引数解決を下位の要因に譲ることは困難である。強いて言えば、サブルーチンはパステルステッチのコードを書き換えることで述語を変更できるが、プリミティブ演算の述語を変換するにはホスト言語の取り扱いが必要なので、前者の方が容易である。

呼び出し元ルーチンのサブルーチン固有ベクトルへの移行とは、あるサブルーチンでローカル化されているベクトルを、その親のルーチンでローカル化されるようにすることである。これは、呼び出し元のルーチンでのローカル化された記憶装置の値が、サブルーチンに継承される、とも解釈できる。

非局所制御フロー構文

非局所制御フロー構文は、要因が同じならば、後に記述されているものほど優先される。これは、先に「本則」を、後に「細則」を記述すると自然であるためである。細則は本則よりも、適用される条件を厳しくするべきである。すると、細則は優先順位が高いかわりに適用される条件が厳しいので、その場の状況によって本則と細則のどちらが適用されることもあり得る。

非局所制御フロー構文の引数 next

非局所制御フロー構文の引数 next には 2 種類の用途がある。第 1 に、同一の述語ベクトルに対して記憶装置、サブルーチン、プリミティブ演算が重なってしまった場合に、これらのうち特定の要因を取り出すことができる。第 2 に、こちらの方が重要であるが、プログラムの末尾に非局所制御フロー構文を追加することで、既存のプログラムの動作を変更することができる。

第 1 の用途では有名ベクトル storage, procedure, primitive を使用する。例えば、サブルーチンとプリミティブ演算が同じ述語ベクトルに割り当てられているとき、引数 next に有名ベクトル primitive を与えると、プリミティブ演算を取り出すことができる。

第 2 の用途では非局所制御フロー構文 suggest と force を組にして使用する。前者には引数 next に omit force または omit force sequentially を与える。以下の例では、アドレス probability から値を読み出すとき、記憶装置に記録されている値が 0 未満であれば、かわりに 0 を出力する。

code Omit force
suggest: if ($verb = provability + before keep positive)
> evaluate [provability] next (omit force)
force: if ($verb = provability)
> evaluate [provability + after keep positive]
procedure provability + after keep positive
 return: (float 0)
if (float 0) ≤ [provability + before keep positive]
  return: [provability + before keep positive]
endcode

この例に続けて以下のコードを実行すると、順に 0, 0.5, 1.5 が出力される。

code Omit force (usage)
write: to (provability) value (float −0.5)
print: [provability]
write: to (provability) value (float 0.5)
print: [provability]
write: to (provability) value (float 1.5)
print: [provability]
endcode

さらに、非局所制御フロー構文 suggest の引数 next を omit force sequentially にすると、1 個の述語ベクトルに対して複数の suggest と ovrwrite の組を連続して適用できる。以下の例では、述語ベクトル provability に対して、値を 0 以上に引き上げる処理と 1 以下に抑える処理をこの順に連続して行う。

code Omit force sequentially
suggest: if ($verb = provability + before keep positive)
> evaluate [provability] next (omit force sequentially)
force: if ($verb = provability)
> evaluate [provability + after keep positive]
procedure provability + after keep positive
 return: (float 0)
if (float 0) ≤ [provability + before keep positive]
  return: [provability + before keep positive]
end
end
suggest: if ($verb = provability + before keep under 1)
> evaluate [provability] next (omit force sequentially)
force: if ($verb = provability)
> evaluate [provability + after keep under 1]
procedure provability + after keep under 1
 return: (float 1)
if [provability + before keep under 1] ≤ (float 1)
  return: [provability + before keep under 1]
end
end
endcode

この例に続けて以下のコードを実行すると、順に 0, 0.5, 1.0 が出力される。

code Omit force sequentially (usage)
write: to (provability) value (float −0.5)
print: [provability]
write: to (provability) value (float 0.5)
print: [provability]
write: to (provability) value (float 1.5)
print: [provability]
endcode

ただし、このテクニックは複雑なので、omit force sequentially という長い名前が規定されている。

1 個の述語ベクトルに対して複数の suggest と force の組を適用するにあたって、既存の処理よりも前に新しい処理を追加するには、前述の omit force sequentially を使う方法では実現できない。かわりに以下のようにする必要がある。

code Compound omit force
suggest: if ($verb = provability + before keep positive)
> evaluate [provability] next (omit force)
force: if ($verb = provability)
> evaluate [provability + after keep positive]
procedure provability + after keep positive
 return: (float 0)
if (float 0) ≤ [provability + before keep positive]
  return: [provability + before keep positive]
end
end
suggest: if ($verb = provability + before keep under 1)
> evaluate [provability + before keep positive]
> next (omit force)
force: if ($verb = provability + before keep positive)
> evaluate [provability + after keep under 1]
procedure provability + after keep under 1
 return: (float 1)
if [provability + before keep under 1] ≤ (float 1)
  return: [provability + before keep under 1]
end
end
endcode

この例の実行結果はその前の例と同じである。ただし、述語 provability に対して読み出し演算を行ったとき、値を 1 以下に抑える処理が値を 0 以上に保つ処理よりも先に行われる。

非局所制御フローのヒエラルキー

非局所制御フロー構文の引数を計算する過程で別の非局所制御フロー構文を有効にするには、それぞれの非局所制御フロー構文を別のグループに所属させたうえで、それらのグループのヒエラルキーを制御構文 hierarchy で指定する必要がある。

非局所制御フロー構文はデフォルトでは main グループに所属している。そのため、基礎的なライブラリーを作成したときは、そのライブラリーを構成する非局所制御フロー構文をグループに所属させて、そのグループのヒエラルキーを main グループよりも下位にするべきである。

一方、そのようなライブラリーを使用する立場では、非局所制御フロー構文のグループのヒエラルキーを main よりも上位にするべきである。また、ライブラリーを使用する立場では、グループを指定せず、暗黙のうちに main グループを使用してもよい。

引数解決に特有の変数

非局所制御フロー構文の引数を計算する過程でも、コンテキスト変数 #offset は実行中のサブルーチンのサブルーチン固有ベクトルを表す。そのため、非局所制御フロー構文の引数 if で、#offset と有名ベクトルの和を $verb と比較すると、ローカル化された記憶素子の値を設定することができる。ただし、あらゆるサブルーチンに対してローカル化された記憶素子の値を書き換えると、たいていの場合は迷惑なので、コンテキスト変数 #verb を用いて対象となるサブルーチンを限定するべきである。ここで $verb と #verb の違いを理解することが重要である。

以下の例では “A” ではなく “B” が表示される。なお、加算記号 + で右辺を省略すると、右辺に #offset があるものとして解釈される。

code Overwrite to localized storage
procedure print something or A1
 write: to (something +) value ⟨A⟩
 print: [something +]
end
force: if (#verb = print something or A1 ^and $verb = something +)
> evaluate [echo: ^[B]]
print something or A1
endcode

上記の例で、echo は引数をそのまま戻り値とするサブルーチンである。非局所制御フロー構文の引数 evaluate には読み出し演算を記述する必要があるので、単に値を設定したい場合には、このサブルーチン echo を補う。

ローカル化された記憶素子の値を非局所制御フロー構文で設定するとき、特定のサブルーチンのローカル化された記憶素子だけでなく、そのサブルーチンから呼ばれたサブルーチンのローカル化された記憶素子に値が継承された場合にも影響を及ぼすには、#offset のかわりに、プリミティブ演算 get offsets を用いて任意のサブルーチン固有ベクトルにヒットするようにする。

以下の例では “AA” ではなく “BB” が表示される。以前の例とはサブルーチン print something の定義が異なることと、そのために姓を変更していることを注意しておく。

code Overwrite to localized and inherited memory
procedure print twice something or A2
 write: to (something +) value ⟨A⟩
 print something2
 print something2
end
procedure print something2
 print: [something +]
end
force: if ([get argument: argument (verb')
> offset [distribute: vector ($verb) set [get offsets] auxiliary set (something)]]
> = print twice something or A2)
> evaluate [echo: ⟨B⟩]
print twice something or A2
endcode

あるサブルーチンを実行するとき、そのサブルーチンの固有ベクトルでローカル化されている記憶装置の値を変更するには、仮変数 $offset と、非局所制御フロー構文の引数 side effect を組み合わせればよい。仮引数 $offset には、もし引数解決の要因がサブルーチンであれば、そのサブルーチンの固有ベクトルになる無名ベクトルが入っている。そのため、仮引数 $offset でローカル化された記憶装置に値を書き込めば、サブルーチンの固有ベクトルでローカル化されている記憶装置の値を変更したことになる。

以下の例では、サブルーチン print something twice3 を実行するとき、ローカル化された記憶装置 under + print something twice3 + の値を true にする。

code Set local storage by non-local control flow
force: if ($verb = print something twice3
> ∧ ¬ [under + print something twice3 +]
> ∧ ¬ [under + print something twice3 + $offset])
> side effect [write: to (under + print something twice3 + $offset) value (true)]
> evaluate [$verb]

ここで ¬ [under + print something twice3 + $offset] を条件に入れなければ無限ループになることを注意しておく。

あるサブルーチンの下で実行されているときには動作を変えるようなコードを書くには、まずローカル化された記憶素子の値を上記の方法で設定し、続いて、その値で動作を変えるようにする。以下の例では “ABB” が表示される。

code Overwrite when under the subroutine
procedure print something twice3
 print something3
 print something3
end
procedure print something3
 write: to (something +) value ⟨A⟩
 print: [something +]
end
force: if (#verb = print something3
> ∧ [under + print something twice3 +]
> ∧ $verb = something +)
> evaluate [echo: ⟨B⟩]
print something3
print something twice3
endcode