2009年12月30日水曜日

Common Lispのレキシカルスコープを調べる

2chのLisp系のスレで変数参照の議論があることをg000001さんから教えていただきました。要約するとこんな感じです。

(setq x 1)
(うんにゃら "x")

というシーケンスがあり、2つ目の式の値を変数xの値(すなわち1)とするには、
'うんにゃら'はどう書けばいいの?

回答案として、

(symbol-value (find-symbol (string-upcase "x")))

という類のものがありました。私なりに書き直していますが趣旨は同じものです。

しかしこのやり方だと、変数xについてレキシカルな束縛が存在するところにシーケンスがある場合にダイナミック変数の方の値が取れちゃうのですね。なので私はこの'うんにゃら'が文脈によらず機能するように書くのは、「ANSI CLの範囲では無理」とg000001さんに話していました。

そこでふと疑問がわきました。いや、これ「ANSI CLの範囲」では無理、という一言で済む類のことではないのではないか、と。

問題は二段階に整理できます。

  • STEP 1: レキシカルスコープの中で、シンボルxではなく文字列"x"から変数xの束縛の値を取得できるか? ということ。例は次のとおりです。

    (setq x 0)
    (let ((x 0))
    (seq x 1)
    (うんにゃら "x")) → 1

  • STEP 2: レキシカルスコープの外で、名前で変数xの束縛の値を取得できるか?ということ。例は次のとおりです。

    (setq x 0)
    (let ((x 1))
    (かんにゃら))
    (うんにゃら "x") → 1


前者は不可能なのかどうか。そして、それ以上に、後者ができてしまったら、それはもうレキシカルスコープのレキシカルスコープたる本分を破戒していることになるのではないか。

ああ、、、これがレキシカルスコープという概念を破戒しているかどうかを判断できる程、私はCommon Lispを理解していない。。。

というのが事のはじまりです。久しぶりにCommon Lispの基礎を復習してみよう、と思い立ちました。

結論を先に言ってしまうと、

  • STEP 1: ANSI CLの範囲でもマクロを使えば可能。
  • STEP 2: ANSI CLの範囲ではやはり無理であり、参照できたとしたらレキシカルスコープの本分を破戒していると言ってよさそうだ。

ということがわかりました。この長文エントリは、その検討の際のメモをそのまま掲載するものです。結構長いので、投稿するかどうか悩みました。しかし、もしかしたら、Common Lisp初心者の方がスコープ関係や環境関係を復習するに参考になるかもしれないと考えて、今この前置きを書いている次第です。すでにLisperの方にとっては、アタリマエのことを何くどくどと書いてるんだ? という内容だと思いますので、その筋の方は読まないことをおすすめします。

なお、Emacsのorg-modeなどで読むと、色付けや階層化されて読みやすいです。



      *** 以下、検討メモ ***


* レキシカルスコープの理解を深める

- ふと、レキシカルスコープについて理解が甘いこと
に気付いた。
- 気付いたことを仮説として検証してみることにした。



** レキシカルスコープに関する仮説

- 関数呼出しの文脈にて変数を名前で参照するというこ
とがレキシカルスコープではできないということが、
「CLの仕様が限定的である」ことによるものだと思っ
ていたが、それは誤解である。(名前を使うかにかか
わらず)その文脈で「直接参照できたら」、それはも
うレキシカルスコープ「ではない」のだ。すなわち、
CLの仕様はレキシカルスコープがレキシカルスコープ
であることを維持するために、そのような機構をわざ
と持たないのだ。



** 現象論とその意味からの確認

*** レキシカルスコープの振る舞い

CL-USER> (setq x 10)
10
CL-USER> (defun hoge ()
(let ((x 0))
(piyo)))
HOGE
CL-USER> (defun piyo ()
(+ 1 x))
PIYO
CL-USER> (hoge)
11
CL-USER>

- piyoの中の'x'にて、hogeの中の'x'は参照されていな
い(できない)。
- 'x'という名前によって参照できるということが、
defunの評価時に、すなわち関数の生成時には可能だ
が、そのコードブロックを抜けると消失してしまう。
ただし、その束縛自体は、hogeが参照する関数オブジェ
クトについての全ての参照が無くなって、その関数オ
ブジェクトGCされるまでは残り続ける。これがレキシ
カルスコープの意味または定義。
- 簡潔に言えば、「有限スコープ」かつ「無限エクス
テント」。
- ちなみに一般的には、有限ではなく静的と言う。

*** ダイナミックスコープの振る舞い
- レキシカルスコープを確認した後で、proclaimでダイ
ナミックに切り換える。

CL-USER> (setq x 10)
10
CL-USER> (defun hoge ()
(let ((x 0))
(piyo)))
HOGE
CL-USER> (defun piyo ()
(+ 1 x))
PIYO
CL-USER> (hoge)
11
CL-USER> (symbol-function 'hoge)
#<Interpreted Closure HOGE>
CL-USER> (proclaim '(special x))
T
CL-USER> (hoge)
1
CL-USER> (symbol-function 'hoge)
#<Interpreted Function HOGE>
CL-USER>

- piyoの中の'x'にて、hogeの中の'x'を参照している
(できる)。
- 'x'という名前によって参照できるということが、
defunの評価時のみならず、piyoの呼出し時にも可能
であり、その参照可能状態は、hogeが参照する関数オ
ブジェクトについての全ての参照が無くなって、その
関数オブジェクトGCされるまでは残り続ける。逆に、
hogeの中で'x'と0の束縛は、そのコードブロックを
抜けると消失する。これがダイナミックスコープの
意味または定義。
- 簡潔に言えば、「無限スコープ」かつ「有限エクス
テント」。
- ちなみに一般的には、有限ではなく静的と言う。

*** 仮説の検証

- この現象論レベルの意味で言えば、コードブロックを
抜けたら名前で参照できなくなるのがレキシカルスコー
プなのだから仮説は正しい。

- ちなみに、dynamic scopeという用語はANSI CLでは定
義されていないようだ(用語集にはあるけど、本文に出て
こない)。レキシカルやダイナミックで言うと、本文で定
義されているのは次のもののようだ。

- Lexical Variables
- Dynamic Variables
- Lexical Scope
- Lexical Environment
- Dynamic Environment


** 環境(Environment)の観点で考える

- ANSI CLの意味論は、環境モデルを中心に構成されて
いる。例えばスコープという言葉もあまり出てこな
い。環境の振舞の結果としてスコープが実現される
からだろう。
- そこで、環境の観点にて仮説はどうなのか、考えてみ
る。
- このあたり、ANSI CLとCLtL2で差異がありそうなので、
ANSI CLであるかCLtL2であるかを常に明記することに
する。まず、ANSI CLをやる。余力があれば、CLtL2も
やる。

*** ANSI CL

- ANSI CLは、readerとevaluatorとcompilerとが概念分
離されている。
- それぞれで環境が使われている。
- レキスカルスコープ/ダイナミックスコープを定義し
ているのは、evaluatorの章である。
- 仮説の検証においては、evaluatorだけでも話は済む
かもしれないが、ここではせっかくなので、readerと
compilerでの環境も確認する。

**** reader

***** readerにおける環境とは?
- readerは、streamを入力として、そのstreamから得ら
れる文字の並びをパースし、lisp objects を返すも
のである。
- readerの動作は、次のものを骨格として定義されて
いる。
- the reader algorithm

これはプログラマが変更することはできないもの。
処理系実装者の世界。というかANSI CLの仕様で確
定されているもの。

ANSI CLの構文のほとんどは、このアルゴリズム
「以外」の構成物で定義されている。そしてそれら
はプログラマが変更可能である。よって、CLは構文
を変更可能というよりも「構文を持たない」という
方が実態をよく表していると思う。ANSI CLは「構
文が不確定、意味論が確定」している言語と言える。
(意味論が確定、といってもあくまでメタな確定で
あり、プログラマが変更可能である)

ただし、意味論を説明するには、何がしか構文が必
要なので、仕様化には、意味論の説明用のとりあえ
ずの構文が必要。それが日頃使われているあの構
文であり、標準構文(the standard syntax)という
名前を持つ。

- readtable

文字と構文タイプとを紐付けるテーブル。これはプ
ログラマが変更することができる。構文タイプには
次のものがある。

- constituent
- whitespace
- terminating macro char
- no-terminating macro char
- single escape
- multiple excape

精確には、constituent traits の定義も
readtableにあるがここでは割愛。また、
dispatching macro characterの表の定義も
readtableにあるがここでは割愛。

- tokens 解釈仕様

構文の基礎となるものについては、tokensの解釈
が仕様で決まっている。よって、readtableでなん
でもかんでも自由自在に定義できるわけではない
ということ。決まっているものは次のとおり。

- numbers
- the consing dot
- symbols

- reader macro function

readerがthe reader algorithmにしたがって、a
readtableを使いながら処理していくときに、
readtableにてmacro charとなっている文字を読み
込んだときに呼ばれる関数のこと。

- standard macro characters

説明のための構文のために定義されたreader
macro functionが割り当てられた文字達。

- left parenthesis
- right parenthesis
- single quote
- semicolon
- double quote
- backquote
- comma
- sharpsign

- さて、これらが骨格であるとして、その中には、プロ
グラマが変更可能というものもあった。すると「プロ
グラマが変更」とは具体的にどのようにやるのだろう
か、変更するということはどこかに格納されているは
ずではないか、ということになる。この格納先が、環
境だ。

- 違う観点でみてみよう。プログラマが変更可能なのは、
readerが使うもの達だ。なぜならreaderの挙動を変更
可能であるというとき、reader自体のソースコードを
いじるわけではないからだ。するとreaderは使うもの
達の名前を知っている、もしくはreaderは使うもの達
を「名前で」参照する、ことになる。よって、
readerが使う変数はdynamic variablesでなければな
らないし、その束縛を保持する環境はdynamic
environmentsだ(environmentsの導入はevaluator の
ところで)。実際、ANSI CLの仕様ではreaderが名前で
参照する変数の一覧が定義されており、それらは
dynamic variablesであると決められている。一覧は
次のとおり。

- *package*
- *read-default-float-format*
- *readtable*
- *readbase*
- *read-suppress*

この文書の話題で重要なのは*package*と
*readtable*である。それらについて説明する。

- *readtable*

- これにはa readtable objectが束縛される。
*readtable*に束縛されているa readtable
objectをthe current readtableと呼ぶ。

- Lisp imageが起動したときにthe current
readtable となるreadtableのことを、the
initial readtable と呼ぶ。the initial
readtableはプログラマが変更可能である。

- 処理系には、the standard readtableが埋め込ま
れており、処理系出荷時はthe standard
readtableがthe initial readtableになっている
ことが多い。しかし、(仕様的には)そうでなけれ
ばいけないわけではない。

- *package*

- a packageは名前(names)とシンボル(symbols)の
紐付けを保持するものである。ただし、印字名
(a print name)自体は、a symbol objectの中に
も保持されている。よって名前とシンボルの対応
は、packagesとsymbolsの双方に存在しているこ
とになる。この両者は役割が異なるのだ。
packagesが提供しているのは、名前空間の分離で
ある。すなわち、readerがtokens からsymbolsを
引き当てるときに、(package)修飾子なし名前に
ついてどのsymbolを選ぶかを決めているのが
package 機構である。引き当てだけでなく、新し
いsymbolの登録先の選定にも使われている。
package の基本について、これ以上の説明は割愛
して先に進む。

- *package*に束縛されたa package objectのことを、
the current packageと呼ぶ。

- readerがthe current pakcageをどのように使う
かというと、やはり次のものにすぎない。

- symbolsを表わすtokensについて、それが
package修飾子を持たない場合に、the
symbolsのintern先となる。(ちなみにreaderは
uninternはしない)

- 標準構文のためのpackagesが定義されている。そ
れを standarized packagesと言う。それは次の
3つである。
- common-lisp
- keyword
- common-lisp-user

common-lisp-userは、標準構文に関する要素は
含んでいないが、standarized packagesである
として定義されている。

- 'common-lisp' packageと'keyword' packageを
use-packageすることによって、標準構文で定義
済みの名前(defined names)を、仕様書に記載さ
れているそのままで使うことができるようになる。

***** 初めにthe global environmentありき

- さて、ではLisp imageが起動したときにどのようなa
dynamic environmentが存在するのだろうか。残念な
がらこれはANSI CL には明示的には書かれていないよ
うだ。しかし暗示的には書かれている。

- それが、the global environemt。the global
environmentが保持する束縛は、'indefinite scope'
かつ'indefinite extent'であるという。であるなら、
起動直後から存在していなければならない。そして、
この両方の特性をもっているのは、the global
environmentだけなので、起動時に存在しているもの
はこれだ(け)といってよいだろう。

- ちなみに、the global environmentは'indefinite
scope'かつ'indefinite extent'なので、レキシカル
スコープでもダイナミックスコープでもない。あく
までthe global scope。ただし、どちらかと言えば、
ダイナミックスコープよりではある。

- このthe global environmentが、いろいろなものの
束縛を保持している。
- reader macro functionsについて、名前と関数
objectsの束縛。
- *readtable*や*package*に関する束縛。
- 標準構文のための関数やマクロやコンパイラーマク
ロやタイプやクラスなどに関する束縛。

束縛だけでなく次の情報も保持している。

- proclamations(global environmentにおける宣言
情報)

***** readerは処理途中に環境を変えられるのか?

- さて、疑問がひとつでてきた。readしている段階で、
環境を変えることができるのだろうか?

- 答えは、「できる」だ。「できる」方法が2つ、いっ
けんできそうだが、実際は「できない」方法が1つあ
る。

- 「できる」方法、その1。ANSI CLではファイルに
programを格納しておき、これを読み込むことがで
きる。このとき、ファイルのtop-levelのformsは、
順次評価される。すると、先行して評価される
formsにてthe global environmentの束縛を代入に
て変更した場合、the global environment自体、
indefinite scope, indefinite extentなので、そ
れ以降のformsのread には束縛の新しい値が使われ
る。例は次のとおり。

CL-USER> (setq zvar 123)
123
CL-USER> (defparameter *my-rt* (copy-readtable))
*MY-RT*
CL-USER> (set-syntax-from-char #\z #\' *my-rt* *readtable*)
T
CL-USER> zvar
123
CL-USER> (setf *readtable* *my-rt*)
#<readtable @ #x1000e200e2>
CL-USER> zvar
VAR
CL-USER> (setf *readtable* (copy-readtable nil))
#<readtable @ #x1000d041a2>
CL-USER> zvar
123
CL-USER>

- 「できない」方法。ANSI CLの実行モデルでは、
top-levelにあるa lisp objectの評価方法は、その
lisp objectに含まれる内部構造(lisp objects)を
順番に評価していくというものだ。この評価順で先
行しているもの評価結果(または行為)は後続のもの
の評価に影響を与えうる。よって先行している部分
でreader にかかわる。dynamic variablesの束縛を
変更すれば、後続の部分はその新しい束縛をもとに
動作する。という形で変更可能におもえるが、それ
は間違い。というのはtop-levelにあるS式は、まる
ごとreadされてa lisp ojectにした上で、evalに渡
されるから。なので後続のevalの動作には影響を及
ぼしうるが、readはもう終わっているので影響しよ
うがない。例は続き。

CL-USER> zvar
123
CL-USER> (let* ((*readtable* *my-rt*))
zvar)
123
CL-USER>

- 「できる」方法、その2。できない方法の内部構造
にて'read'が存在していれば、その'read'の動作は、
内部構造で先行しているものから影響を受けること
ができる。例はつづき。

CL-USER> (let* ((*readtable* *my-rt*))
(read-from-string "zvar"))
'VAR
4
CL-USER> zvar
123
CL-USER>

**** evaluator
- ANSI CLにおいて、Evaluationとは、programの実行手
順というか実行のあり様を指す言葉である。ここでい
う'program'の形態は主に2種類あり、ひとつは
readerが返すlisp objects(これをevaluationの観点
ではformsと呼ぶ)、もうひとつは、compilerにて処理
済みのcompiled codeである。
- Common Lispの意味論は、lisp objects(forms) に対
して定義されている。それをベースにて、compiler
やcompiled codeの意味論が記述されている。
- なお、ANSI CLにはevaluatorという用語はめったに出
てこない(稀に本文内に出現する)。もっぱら
evaluationが使われている。しかし、個人的には、
reader、compiler、というように並べると、
evaluatorが自然に思える。もしかしたら、
evaluatorと言わずにevaluationと言うところに機微
があるのかもしれない。いやあるのだろう。そのこと
も今回探ってみたい。

***** そもそも環境ってどんなもの?
****** 環境と環境オブジェクト
- ANSI CLには、環境(environments)と環境オブジェク
ト(environment objects)が登場する。
- 処理系の内部では、環境は環境オブジェクトによっ
て実現されていると思うかもしれない。しかし仕様
はそこまでは求めていない。もちろん、そうしても
よいのだが。
- 環境オブジェクトはマクロ関連の関数で引数として
関数を渡す際の形態として使われるだけである。
- よって、環境という概念は「ANSI CL programの意味
を説明するための概念」にすぎない。違う言い方をす
ると、意味論の用語、である。
- なお、ANSI CLにおいて、環境オブジェクト
は、'lexical environments'を表現するものとして
定義されている。dynamic environmentsを表現でき
るかどうかについては記述はない。
****** 束縛って?
- 束縛(a binding)とは、名前(a name)と、その名前が
指すもの(that which the name denotes)とのつなが
り(an association)のこと。
- 束縛(bindings)がどこに存在するかというと、それは
a lexical environmentまたはa dynamic
environmentの中に、である。
それらenvironmentsにおいて、束縛は「確立される」
(are estabilished)と言う。
- special operators(の一部)のみが、束縛の確立を実
行できる。
****** 環境、束縛と名前空間
- 環境は、まず名前空間で区分けされる。名前空間が異
なるなら、ひとつの環境の中に同じ名前に関する束縛
が複数存在してもよい。ただし、ひとつの名前空間
でとある名前に関する束縛は単一でなければならな
い。
- 名前空間はパッケージで定義する。

***** evaluatorにおける環境の分類
****** the global environment
- "the global environment for evaluation is that
part of an environment that contains ..." すなわ
ち、環境の一部として存在するのだな。
- the global environmentにおける束縛は、'indefinite
scope'かつ'indefinite extent'。
- 主に次のものを含む。
- bindings
- dynamic variables
- constant variables
- functions
- macros
- special operators
- compiler macros
- type names
- class names
- proclamations
****** dynamic environments for evaluation
- "A dynamic environment for evaluation is that
part of an environment that contains ..." すなわ
ち、これまた環境の一部として存在するのだな。
- (ここ重要) dynamic environmentsにおける束縛の特
徴。
- 束縛を確立するlisp formを実行(execute)すると
きに、
- 束縛の開始点と終了点の間だけその束縛は存在す
る。
- 原文: "A dynamic environment for evaluation
is that part of an environment that contains
bindings whose duration is bounded by points
of establishment and desestablishment within
the execution of the form that established
the binding."
- 主に次のものを含む。
- bindings
- dynamic variables
- active catch tags
- exit points (established by unwind-protect)
- active handlers and restarts
****** lexical environments for evaluation
- "A lexical environment for evaluation at some
position in a program is that part of the
environment that contains information having
lexical scope within the forms containing that
position." レキシカルもこれまた環境の一部なのだ
が、the globalやdynamicsとはちょっと趣が違う。
- the globalやdynamicsと趣が違うのは、このレキシカ
ルの定義が束縛(bindings)という言葉を含んでいない
からだ。そしてスコープという、the globalや
dynamicsの定義に含まれていない用語を使っている。
- (ここ重要) lexical environmentsにおける束縛の特
徴。
- まず、lexical scopeの定義。
- その前にscopeの定義。a scopeとは、
- コードの文字面の領域である。
- ただし、すべての文字面の領域をa scopeと呼
ぶのではなく、
- その中で、lisp objects、bindings、exit
points、tagsへの参照が発生するようなもの
ことを呼ぶ。(参照は、通常、名前を使って
実施される)
- 前項を別の言葉で言うと、「環境が発生す
るようなもの」である。
- では、lexical scopeは?
- scopeの一種である。
- ただしその範囲は、束縛を確立するformの文
字面の範囲に限られる。
- では、lexical environmentsにおける束縛の特徴は?
と言うと、このlexical spcopeの定義がそのままあ
てはまる。
- ちょっと疑問。これはstatic scopeということは言っ
ているが、indefinite extentということは言って
いないような。それともこれだけでそれがimplyさ
れるのかなぁ。
- 主に次のものを含む。
- bindings
- lexical variables
- symbol macros
- functions
- macros
- block tags
- go tags
- declarations

******** the null lexical environment
- 'the null lexical environment'は、'the global
environment'と同義である。
- 環境を求める文脈では、'nil'はthe null lexical
environmentを指す。

***** evaluatorにおける環境の利用
- では、evaluatorによって環境はどのように利用され
ているのだろうか。それをThe Evaluation Modelに
したがって確認していこう。

***** The Evaluation Modelに沿って
- ANSI CLにて「programを実行する」とは、lisp
formsを評価することである。評価にあたっては、レ
キシカル、ダイナミックまたはグローバル環境を考
慮する(with respect to)。
- lisp formsは3つに分類できる。symbols、consesと
self-evaluating objectsである。これら毎に評価手
順が定義されている。

****** Symbols
- a symbolは、a symbol macroかa variableであると解
釈され、評価結果を返す。
- a symbol macroであるかどうかは、the current
lexical environmentにて、そのsymbolがa symbol
macroとしての束縛を持つ場合である。
- a symbol macroではない場合は、the name of a
variableと解釈されて、the value of a variableを
返す。
- ところで、variableって何だ、というと、それは束縛
のうちで"variable"名前空間に属するもののことだ。
なので、束縛の一部と考えてもよい。ただし、
"bindings of lexical variables"という表現もある
ことに注意。
- symbolsの評価の観点では、variablesは3つに分類で
きる。lexical variables 、dynamic variablesと
constant variablesだ。
******** symbol macrosの例
CL-USER> (let ((a '(1 2 3))
(sec 'hoge))
(print sec)
(symbol-macrolet ((sec (second a)))
(print sec)
(let ((x 1))
(print sec)
(values))))

HOGE
2
2 ; No value
CL-USER>

******* Lexical Variables
- 特徴を列挙する。
- 確立
- 方法は一種類。
- なんらかのspecial formによって、lexical
variablesは確立される。(ここでは、確立の役
割を担うspecial formをthe formと呼ぶ)。
- 束縛の生成
- the formは、評価されるたびに、freshな束縛
(lexical variables)を生成する。確立と併わせ
て別の言い方をすると、lexical variablesは確
立と束縛が同時に実行され、束縛は確立毎にユ
ニークである。
- a lexical variableは常に値を持つ。'unbound'
という状態は存在しない。別の言い方をすると、
束縛を生成しない限りlexical variablesは存在
しない。
- extent
- lexical variablesの定義には「lexical
bindings は indefinite extentである」ことの
記述は存在しない。それ以前に、extentに関す
る記述が存在しない。それは'Closures and
Lexical Binding'の節で説明されている。
- 参照認定
- a variableがa lexical variableであると認定
されるのは次の条件を満すときである。
- the formのlexical scopeの内側にて、a
symbol がa variableであると解釈されるとき、
the symbol のnameがthe lexical variable
nameと同じならば、the lexical variableへの
参照とみなされる。これが基本。
- shadowing
- the formのlexical scopeの内部にて、他の
form(another formと呼ぶ)によって同じnameに
ついてshadowされているということがある。こ
のときそのnameはthe formのbindingではなく、
another formのbindingを参照していると認定
される。shadowを実現するformは二種類存在
する。
- lexical variablesを確立するforms。
- locally declares the name specialする
forms。
- 環境
- lexical variablesの束縛はlexical
environments に格納される。

******** 例

CL-USER> ; lexical variablesを確立するspecial
; operatorsの例。
; No value
CL-USER> (let ((x 0))
x)
0
CL-USER> ; lexical variablesでは、毎回 freshな束縛
; が生成される例。
; No value
CL-USER> (defun hoge ()
(let ((x 0))
(values #'(lambda () x)
#'(lambda (y) (incf x y)))))
HOGE
CL-USER> (multiple-value-bind (g s)
(hoge)
(setf (symbol-function 'g1) g)
(setf (symbol-function 's1) s))
#<Interpreted Closure (:INTERNAL HOGE) @ #x1000e4fc62>
CL-USER> (multiple-value-bind (g s)
(hoge)
(setf (symbol-function 'g2) g)
(setf (symbol-function 's2) s))
#<Interpreted Closure (:INTERNAL HOGE) @ #x1000e6c1f2>
CL-USER> (g1)
0
CL-USER> (s1 3)
3
CL-USER> (g1)
3
CL-USER> (g2)
0
CL-USER> (s2 100)
100
CL-USER> (g2)
100
CL-USER> (g1)
3
CL-USER> ; dynamic variablesでは、毎回 freshな束縛
; は生成されない例。
; No value
CL-USER> (setq x 0)
0
CL-USER> (defun hoge ()
(progv '(x) '(0)
(values #'(lambda () x)
#'(lambda (y) (incf x)))))
HOGE
CL-USER> (multiple-value-bind (g s)
(hoge)
(setf (symbol-function 'g1) g)
(setf (symbol-function 's1) s))
#<Interpreted Closure (:INTERNAL HOGE) @ #x1000ea7cc2>
CL-USER> (multiple-value-bind (g s)
(hoge)
(setf (symbol-function 'g2) g)
(setf (symbol-function 's2) s))
#<Interpreted Closure (:INTERNAL HOGE) @ #x1000ec4192>
CL-USER> (g1)
0
CL-USER> (s1 1)
1
CL-USER> (g1)
1
CL-USER> (g2)
1
CL-USER>


******* Dynamic Variables
- 特徴を列挙する。
- 確立
- dynamic variablesに確立の概念は無い。
- a dynamic variableは、どのprogramからでも何
時でも参照できる。textualな限定は無いのだ。
- 束縛の生成
- なんらかのspecial formによって、dynamic
variablesの束縛は生成される。そのspecial
formをthe formと呼ぶ。
- 束縛が存在するのは、the formを評価している期
間(区間)だけである。これをdynamic extent と
呼ぶ。別の言い方をすると、あるnameに関する
dynamic bindingは、program 実行のどの時点に
おいても常にせいぜいひとつだけ存在する。
- 前項の確立と併せて別の言い方をすると、
dynamic variablesはどのprogramからでも何時で
も参照可能であるが、参照したときに束縛が存
在するとは限らない。束縛が存在しないこと
を'has no value'とか'unbound'などと言う。
- extent
- dynamic bindings は dynamic extent であるこ
とが、dynamic variablesの定義にて明示されて
いる。
- 参照認定
- a variable が a dynamic variableであると認
定されるのは次の2つの条件のいずれかが成立す
る場合である。(ここではa variableが持つ名前
をthe nameと呼ぶ)
- the nameについてa dynamic bindingを生成す
るformの内側にいる(textually within)とき。
(このformをthe formと呼ぶ)
- the nameがspecial宣言されている
- locally declare
- globally proclaim
- shadowing
- the form の内側にて、the nameについてa
lexical bindigを生成するformが存在する場合
(これをanother formと呼ぶ)、another formの
内側(textually within)においては、the
formのa dynamic bindingではなく、another
formのa lexical bindigを参照していると認
定される。

- 環境
- dynamic bindingsが発生するのは、dynamic
envrionmentsやthe global environmentである。
- the global envionmentにて束縛が発生している
とき、それを'global variables'と呼ぶことがあ
るが、それはdynamic variablesと何ら変わりな
いものである。(違う呼び方をすることがあるだ
けだよ、という意味だろう)

******** 例

CL-USER> ; locally declaredの例
; No value
CL-USER> (setq x 0)
0
CL-USER> (let ((x 1))
(print x)
(locally (declare (special x))
(print x)))

1
0 0
CL-USER> ; globally proclaimedの例
; No value
CL-USER> (setq *x* 10
x 10)
10
CL-USER> (let ((*x* 100)
(x 100))
(print (symbol-value '*x*))
(print (symbol-value 'x)))

100
10 10
CL-USER> ; special formsによって確立する例。
; No value
CL-USER> (boundp 'z)
NIL
CL-USER> (progv '(z) '(0)
(boundp 'z))
T
CL-USER> (boundp 'z)
NIL
CL-USER>

******** 疑問
- 疑問
- 'progv'はネストできる。そのときも、dynamic
bindingはひとつだけ存在すると言えるのだろう
か。

CL-USER> (progv '(z) '(1)
(print z)
(progv '(z) '(2)
(print z))
(print z))

1
2
1 1
CL-USER>

- そうか、言えるのだ。内側の'progv'の中で、外
側の'progv'の束縛を参照する方法は存在しない。
そのことと束縛はひとつしか存在しないという
ことは等価である。それがわかりやすい例。

CL-USER> (progv '(z) '(1)
(progv '(fn) (list #'(lambda () z))
(funcall fn)))
1
CL-USER> (progv '(z) '(1)
(progv '(fn) (list #'(lambda () z))
(progv '(z) '(2)
(funcall fn))))
2
CL-USER>


******* Constant Variables
- 'constant variables'と言うと、矛盾した名称と思
えるが、別の言い方をすると'named constant'のこ
とである。
- constant variablesはthe global environmentに格
納される。

******* 仮説の検証 その2
- lexical variablesの定義にて、lexical variables
はその束縛を確立したformのlexical scopeの内側で
のみ参照可能とあるので、仮説は正しい。
- これでANSI CL的には「the evaluation modelとして
も、仮説は正しい」と言ってもよいだろう。なので、
ここでやめるという手もある。しかし、まだthe
evaluation modelの理解の途中である。一通り理解
した上で判断しよう。

****** Conses
- consesは、4つに分類できる。special forms、macro
forms、function forms、そしてlambda formsだ。
- consesのcarによって、種類を判定する。判定ルール
は次のとおり(疑似コードにて表記)。

(cond ((carがsymbol)
(cond ((carがspecial operator)
(special formと認定))
((carがmacro function)
(macro formと認定))
((carがfunction)
(function formと認定))))
((carが複合form)
(cond ((carがlambda式)
(lambda formと認定))
(t エラー)))
(t エラー))

- ここで、carがa symbolのときに'carがほにゃらら'と
言う部分がどの環境のどこを見ているかという
と、'the current lexical environment' の
'Function'名前空間である。
- 4種類ひとつずつ確認していく。

******* Special Forms
- special formsはまさに環境を操作するための道具で
である。次のものがspecialformsをつくるspecial
operatorsの一覧である。環境に対する操作(参照や変
更)を明示的に含むものにチェックを入れた。
- [X] block
- [X] lexical
- [X] dynamic
- [-] catch
- [ ] lexical
- [X] dynamic
- [ ] eval-when
- [ ] lexical
- [ ] dynamic
- [-] flet
- [X] lexical
- [ ] dynamic
- [-] go
- [X] lexical
- [ ] dynamic
- [ ] if
- [ ] lexical
- [ ] dynamic
- [-] labels
- [X] lexical
- [ ] dynamic
- [X] let
- [X] lexical
- [X] dynamic
- [X] let*
- [X] lexical
- [X] dynamic
- [ ] load-time-value
- [ ] lexical
- [ ] dynamic
- [X] locally
- [X] lexical
- [X] dynamic
- [-] macrolet
- [X] lexical
- [ ] dynamic
- [ ] mutiple-value-call
- [ ] lexical
- [ ] dynamic
- [ ] multiple-value-prog1
- [ ] lexical
- [ ] dynamic
- [ ] progn
- [ ] lexical
- [ ] dynamic
- [-] progv
- [ ] lexical
- [X] dynamic
- [ ] quote
- [ ] lexical
- [ ] dynamic
- [X] return-from
- [X] lexical
- [X] dynamic
- [X] setq
- [X] lexical
- [X] dynamic
- [-] symbol-macrolet
- [X] lexical
- [ ] dynamic
- [-] tagbody
- 基本lexicalだが、特殊。'lexical scope'だ
が'dynamic extent'な振る舞いが定義されてい
るため。
- [X] lexical
- [ ] dynamic
- [ ] the
- [ ] lexical
- [ ] dynamic
- [-] unwind-protect
- [ ] lexical
- [X] dynamic
- 集計
- 環境に明示的に関連している。[18/23]
- レキシカル [11/23]
- ダイナミック [9/23]
- special operatorsの仕様はANSIで固定されている。
special operatorsをグローバル環境で変更すること
は、ANSI CLでは禁止されている。
- special operatorsは、処理系組込みで実装してもよ
いし、マクロで実装してもよい。マクロで実装する
場合は、同種の機能について別の組込み機能を実装
している場合などだろう。
- いずれにしても、special operatorsがspecial
operatorsたる所以は、その動作が関数やマクロでは
実現できないような特殊な振舞をそれぞれ固有に含
んでいることだろう。
- なので、環境との兼ね合いがどうなのかは、special
operatorsを個別に調べるしかない。
- special operatorsの振舞自体は、日頃のプログラミ
ングにて周知のことと思うのでここでは割愛。

******* Macro Forms
- macro formsについての評価手順。
- the current lexical environmentからcarの
symbolのnameに束縛されている関数を取得する。
これには'macro-function'を使う。
- そこで得られた関数は2引数であり、最初の引数は、
macro form全体を受け取り、2つめの引数は、the
current lexical environmentに対応したan
environment objectを受け取る。
- この関数のことを展開関数(the expansion
function)と言う。
- 展開関数は直接呼出されるのではなく、マクロ展
開フックという関数を経由して呼出される。
- マクロ展開フックの関数オブジェクトは、
*macroexpand-hook*に束縛されている(ゆえにプロ
グラマが変更可能)。
- マクロ展開フック(の関数オブジェクト)の引数は、
展開関数の引数に、展開関数の名前を加えたもの
であり、3つである。
- マクロ展開フックが返すのは、マクロ展開関数が
macro form全体をthe current lexical
environmentを考慮しながら変換したformである。
- そのformを再度(というか、再帰的というか再入的
というか)に評価する。
- 余談
- このMacroを'evaluator macro'と呼ぶことにする
と、'reader macro'、'evaluator macro' 、
'compiler macro'と3つ揃い踏みとなり、分かりや
すい。
******** 疑問 その1
- さて、ここで疑問がひとつ。何故、マクロの場合は、
環境オブジェクトを渡してあげるのか? 'eval'は渡
さないじゃん、という比較にて疑問がある。特
に'eval'はformをthe current dynamic environment
とthe null lexical environmentにて評価する。こ
の違いは何だろう。
- まず、'eval'がthe current lexical environment
にてformを評価するということは、そのeval form
の中ではthe current lexical environmentを名前
でいじれるということになのだが、この言明自体、
冒頭のlexicalとdynamicの例のような局面では意味
がないということだろう。例を考えてみよう。

CL-USER> (setq y 10)
10
CL-USER> (defun hoge ()
(let ((y 0))
(piyo)))
HOGE
CL-USER> (defun piyo ()
(eval '(+ 1 y)))
PIYO
CL-USER> (piyo)
11
CL-USER> (hoge)
11
CL-USER>

- 詳細は、次の項目であるfunction formsに譲るが、
(hoge)を評価するとhogeに束縛されている関数オ
ブジェクト(クロージャ)が実行されるが、そのク
ロージャが包んでいる環境のスコープは、defun
formの評価時であり、(hoge)の実行時には束縛に
対する名前での参照は消失している。よっ
て、'eval'に渡せるthe current lexical
environmentは、ここではthe null lexical
environmentただひとつに過ぎない。

- では、the current lexical environmentが存在
するコード領域にて'eval'を呼んでみよう。

CL-USER> (setq y 10)
10
CL-USER> (let ((y 0))
(eval '(+ 1 y)))
11
CL-USER>

- やはりこれもthe null lexical environmentを見
ている。これまでthe nullにするのはなぜか?
それはFunction formsをやった後に確認する。

- さて、マクロ展開関数の第二引数はどのように指
定するのだろう。というのは、ANSI CLの範囲では、
環境を返す仕組みが、special operators、
macros,functions,のいずれにしても存在しない
からだ。ANSI CLの例の抜粋は次のとおり。

CL-USER> (defmacro alpha (x y) `(beta ,x ,y))
ALPHA
CL-USER> (defmacro beta (x y) `(gamma ,x ,y))
BETA
CL-USER> (defmacro expand-1 (form &environment env)
(multiple-value-bind (expansion expanded-p)
(macroexpand-1 form env)
`(values ',expansion ',expanded-p)))
EXPAND-1
CL-USER> (macroexpand-1 '(alpha a b))
(BETA A B)
T
CL-USER> (expand-1 (alpha a b))
(BETA A B)
T
CL-USER> (macrolet ((alpha (x y) `(delta ,x ,y)))
(macroexpand-1 '(alpha a b)))
(BETA A B)
T
CL-USER> (macrolet ((alpha (x y) `(delta ,x ,y)))
(expand-1 (alpha a b)))
(DELTA A B)
T
CL-USER>

- これ、macroexpand-1もenvironmentを&optional
で引数にとるのに、何でマクロでくるんであげ
る必要があるのだろう?
- そうか、次の演繹が成り立つのだろう。
- environment objectsを取り出す方法は存在し
ない。
- それが引数として渡されるのは、マクロ展開関
数を呼出すとき(に勝手に処理系内部で行われ
る)だけである。
- macroexpand-1は関数である。ゆえに
macroexpand-1の呼出しにはマクロ展開関数呼
出し機構は関与しない。
- 関与させるには、マクロとしてラップしてあ
げればよい。
- 付随的な知見として、開発時にREPL上で
macroexpand-1でマクロを展開させているのは、
ソースの中で実際に行われる展開とは異なる文脈
(lexical scope)なので、展開結果は必ずしも一
致しないということを理解した。アタリマエ、な
んだけど、明確には捉えていなかった。

******** 疑問 その2
- この評価手順ならば、macro formsの展開は、評価時
に順次行われるということであり、macroexpand
hook がマクロ展開関数に渡す環境も評価時の
lexical environmentである。すると、いわゆる「マ
クロ展開時(macroexpansion-time)は実行時
(run-time)とは使える情報(環境)が異なるので注意が
必要」というのはThe Evaluation Modelには存在しな
いのだろうか。
- おそらく、異なるというのはcompilerにおいてなの
だろうとは思う。しかし、ANSI CLの意味論の基礎が
The Evaluation Modelにあるとしたら、それと
compiled codeに差異があっては意味が二重になって
しまうのでは?という不安がある。
- これはcompilerのところで確認しよう。

******* Function Forms
- function formsのルールは単純。
- functionの束縛はthe current lexical environment
のものを使うこと。
- 引数を先に評価すること。
- 引数は左から右に評価すること。

- 一点、処理系依存となっている曖昧さがある。それは、
functionの束縛を参照するのが、引数を評価する前
なのか後なのか、ということだ。例は次のとおり。

CL-USER> (defun foo (x) (+ x 3))
FOO
CL-USER> (defun bar () (setf (symbol-function 'foo) #'(lambda (x (+ x 4)))))
BAR
CL-USER> (foo (progn (bar) 20))
23
CL-USER>

- これは23になる処理系と24になる処理系がある(あっ
てよい)。ここではAllegro CLを使用している。

******* Lambda Forms
- lambda formのルールも単純。
- 基本的には、lambda式をa lexical closureにした後
で、funcallしていると考えればよい。その後は
function formsと同じ。(lexical closuresについて
は、'Closures and Lexical Binding'にて導入。
- lambda式をa lexical closureにするところで、
lambda-listに従って束縛が生成され、それはlambda
式の本体を評価する際のthe current lexical
environmentとなる。

****** Self-evaluating Objects
- symbolsでもconsesでも無いformはself-evaluation
objectsである。別の言い方をすると、評価対象が、
symbolsでもconsesでもself-evaluation objectsで
も無ければエラーとなる。
- まあ、これは環境とは関係ないですね。



****** Closures and Lexical Binding
- まず、a lexical closure と a closure は同義であ
る。
- では、a lexical closure とは何か。
- a lexical closure の定義
- 関数の一種である。
- 関数定義(bodyのこと)を文面上内包する
(textually include) lexical bindingsが存在する。
ここでは、そのlexical bindingsをthe bindingsと
呼ぶことにする。
- closureはthe bindingsの値を参照したり変更した
りすることができる。ここ重要。「the bindingsの
名前については言及していない」
- ANSI CLのかきぶりは、「lexical environments
が'indefinite extent'である」というものではなく、
「Closures が lexical bindings を参照/変更でき
る」というものである。
- まあ、lexical scopeの外側でlexical bindingsを参
照/変更する方法がclosuresしかないならば、意味す
るところは同じなのですが。

***** 仮説の検証 その3

- Macro formsの機構によれば、macroexpansion hookを
つかって環境をlexical scope外にひきずりだすこと
ができる。

CL-USER> (defmacro extract (form &environment env)
(setf *env* env))
EXTRACT
CL-USER> (let ((x 1))
(extract 'a))
#<:Augmentable INTERPRETER environment 1>
CL-USER>

- また、macro expanderが環境を捕捉するなら、
lexical scopeの中で'symbol-value'のかわりにマク
ロをつかって名前で値を取得でき
るのではないか。

CL-USER> (setq x 0)
0
CL-USER> (let ((x 1))
(symbol-value (find-symbol (string-upcase "x"))))
0
CL-USER> (let ((x 1))
(macrolet ((lex-var-value (not-used)
(find-symbol (string-upcase "x"))))
(lex-var-value t)))
1
CL-USER>

- お、できる。
- これはきっかけのお題の解の一部ではある。しかし、
仮説の反例にはなっていない。
- この2つを組みあわせて、lexical scopeを越えて、名
前でアクセスできるか?

CL-USER> (let ((*macroexpand-hook*
#'(lambda (expander form env)
(funcall expander form *env*))))
(macrolet ((lex-var-value (not-used)
(find-symbol (string-upcase "x"))))
(lex-var-value t)))
0
CL-USER>

- できない。。。*env*がうまくわたせているのかどう
かをAllegroの環境IF(処理系機能)で確認すると。

CL-USER> (let ((*macroexpand-hook*
#'(lambda (expander form env)
(funcall expander form *env*))))
(macrolet ((lex-var-value (not-used &environment env)
(multiple-value-bind (scope locative-cons)
(sys:variable-information 'x env)
(car locative-cons))))
(lex-var-value t)))
1
CL-USER>

- *env*に変更はできているようだ。しかしマクロ展開
関数に使われないという不思議な状況。
- ANSI CLを確認すると、'3.8.15
*macroexpansion-hook*'に次のような記述があった。

'The environment object has dynamic extent; the
consequences are undefined if the environment
object is referred to outside the dynamic
extent of the macro expansion function.'

ということは、Allegroでは、macro expansion
functionの呼出しを使って、specialたる*env*に
Environment Objectを束縛してひきづり出せたが、こ
のこと自体、ANSI CLでは保証されていないということ
だし、それをextent外で使用した場合の動作は不定
ということだろう。

- というわけで、引き続き仮説は正しい。


さて、この後、compiler、CLtL2、処理系独自対応などを調べているところで力尽きました。。。
いや、正確には、飽きました。。。

それらの話題は、またモチベーションが上がったときに書いてみたいと思います。


こつこつ。

0 件のコメント: