ラベル Common Lisp の投稿を表示しています。 すべての投稿を表示
ラベル Common Lisp の投稿を表示しています。 すべての投稿を表示

2010年2月3日水曜日

Lisp Quote Backquote (5)

「マクロ展開関数の中での関数呼出しにおいて、引数は評価されない」

ということがちょっとひっかかっています。というのは、前回の記事を書くときに、ANSI Common Lisp 仕様のマクロ展開の部分はざっと確認したのですが、なんとなくそこの記述が曖昧な気がしたのですね。それを確認しようと思います。こういうのを重箱の隅をつつくような、というのかなぁ。まあ、仕事ではなく、好きでやっているので、やりたいようにやっちゃおう。

さて、ANSI Common Lisp の該当部分、

3.1.2.1.2.2 Macro Forms

に書かれていることの骨子は次のとおりです。


  • a form の第一要素たるsymbolについて、macro-functionがtrueならば、そのformを a macro form とみなす。
  • macro-functionがtrueであるとき、macro-functionが返すのはa macro functionである。

    CL-USER> (macro-function 'a-mac)
    #<Interpreted Function A-MAC @ #x1000c669d2>
    CL-USER> (macro-function 'a-fun)
    NIL
    CL-USER>

  • まず、the macroexpand hookを呼び出す。その引数は、次の3つ。

    • the macro function
    • the entire macro form
    • an environment object (lexical)

  • the macroexpand hook は、the macro functionを呼び出す。その引数は、次の2つ。

    • the entire macro form
    • an environment object (lexical)

  • the macro funcitionの値は、a formとして扱う。
  • the macroexpand hookは、the formをパススルーする。
  • the form が the original formの代わりに評価される。


ふむ。曖昧、というか、少くともここには、macro functionsの引数評価方法が通常の関数呼出しとは違うとは、字句としては書かれていない
そこで、続いて、macroexpand hookを調べてみよう。

3.8.15 *macroexpand-hook*

ふむ。ここには、新しい情報は無いな。。。

上で書いた骨子は、「評価モデル」の話です。評価モデルは、意味(意味論, semantics)の一部を説明するためのものであり、それがすべてではありません。そこで、Compilationでは何をどう述べているのか確認してみましょう。

3.2.2.2 Minimal Compilation

この2つめの項目で言及されているのは、compile time にマクロの展開は全部やっちゃいますよ、ということです。このことはよく知っているつもりでしたが、もしかして、このことに集約されてしまうのかな?

コンパイルにおけるcompile timeの定義というか振舞いは次の部分に記述されています。

3.2.1 Compiler Terminology

うーん。ここでは、各種環境の定義とその利用についてが書かれているだけですね。

とすると、評価モデルの説明のところにて、字句としては書かれていませんが、何かimplicationがあるのでしょうか。。。




あ゛、、、、




評価モデルのところ(3.1.2.1.2.2 Macro Forms)のこの文、

`The expansion function is invoked by calling the macroexpand hook with the expansion function as its first argument, the entire macro form as its second argument, and an environment object (corresponding to the current lexical environment) as its third argument.'

で第二引数として`the entire macro form'を渡すと言っているところに含意があるんだ。だって、そこで評価しちゃったらマクロの展開は無限ループだ。。。だから、quoteして渡されるんですね。ここで勝負が着いているのだな。

勝負が着いているというのは、前回の記事に書いた「関数呼出しを入れ子にしてもマクロ展開関数内では評価されない」というのは見た目としては通常の関数呼出しでも同じあり、しかも「評価されない」のではなく、「評価された結果としてそう振舞う」ということなんですね。この点も前回の記事は間違っています。確認してみましょう。

CL-USER> (defun fun-1 (x)
(fun-2 x))
FUN-1
CL-USER> (defun fun-2 (x)
(list x))
FUN-2
CL-USER> (fun-1 'a)
(A)

これ、(fun-1 'a)をまず読み込みます。a cons form で operator が function なので、先に引数が評価されます。それは a symbol object を返します。その値にて関数本体を実行します。fun-1の関数本体は(fun-2 x)です。さて、ここで、(fun-2 <a symbol object>)となると考えてしまうのが間違えなんですね。引数は束縛なので、xという名前がa symbol objectに束縛されているというレキシカル環境ができます。そこで、(fun-2 x)が評価されるので、eagerに評価された結果はその束縛によりa symbol objectなわけですね。これも基礎中の基礎、だな。。。

まとめると、

「関数の振舞いは、マクロ展開関数の中での呼出しも、通常の呼出しも、同じである。ただし、マクロ展開フックがマクロ展開関数を呼び出すときに、引数に置くフォームをquoteする(もしくはquote済みである)」

ということですね。

うむ。重ねて恥しい。しかし、すっきりした。

こつこつ。

2010年2月2日火曜日

Lisp Quote Backquote (4)

日々ちょこちょこSteeleのbackquote impl.をながめているのですが、なんだかすっと頭に入ってこないのです。こういうときは、大抵何か根本的な理解が欠落しています。丁寧にコードを追ってみました。そしたら、マクロの基本的な振舞いの理解が曖昧だったことがわかりました。私的には大収穫なのですが、こんな基本的なことが曖昧というのはとても恥ずかしい。でも、誰かの何かに役立つかもしれないので書いちゃいます。

わかっていなかったのは、マクロの中で呼ぶ関数の振舞いです。

まず、諸処定義。

CL-USER> (setf a 1 b 2)
2
CL-USER> a
1
CL-USER> b
2
CL-USER> (defmacro a-mac (x y)
(a-fun x y))
A-MAC
CL-USER> (defun a-fun (x y)
(+ x y))
A-FUN
CL-USER>

この関数を呼び出したとき、もちろん引数はeagerに評価されます。

CL-USER> (a-fun a b)
3
CL-USER>

あ、この例では、eagerもlazyもへったくれもないですが、まぁCommon Lispの関数呼出しの評価モデルはeagerです。

では、(a-mac a b) ではどうだろう、ということなんですね。a-mac自体はマクロなので、引数を評価しないので、それは(a-fun a b)になる。そしてこのように変換された後に評価されるから、(a-fun a b)は上の関数呼び出しと同じで3になるじゃん、と思ったんですね。これが間違い。エラーです。

CL-USER> (a-mac a b)
; Evaluation aborted.
CL-USER>

エラーはこんな感じ。

`A' is not of the expected type `NUMBER'
[Condition of type TYPE-ERROR]

ちょっと考えてみればアタリマエなんですが、a-macの中のa-fun呼出しはマクロ展開関数の一部ですから、そのときの動作は、通常の関数呼出しの振舞いの定義に従うのではなく、あくまでマクロ展開関数の振舞いの定義に従うのですね。で、マクロ展開関数の中では、そもそもマクロの引数として渡された lisp form は評価されずにそのまま取り扱われるのです。関数がどんなに入れ子になっていてもマクロ展開関数の中ならば、ざっくり言うと、引数は評価されません。そのまま取り扱います。最終的にマクロ展開関数が返す lisp form が初めて評価されるのです(実行時環境に対して)。

ちなみに、上で間違いといった解釈に多少合致したマクロは次のものです。

CL-USER> (defmacro b-mac (x y)
(list 'a-fun x y))
B-MAC
CL-USER> (b-mac a b)
3
CL-USER>

ここでは、マクロ展開関数が(a-fun a b)というlisp formを返します。そのlisp formを評価するときは通常の関数呼出しですから、eagerに(a-fun 1 2)となり、3となります。

Common Lispの関数の引数は常にeagerに評価されると思っていたのが間違い。マクロ展開関数の中で呼出されるときは評価しないんですね!
うひー、恥ずかしい。。。でもよかった。

こつこつ。

2010年1月25日月曜日

Lisp Quote Backquote (3)

Lispには入れたけど、まだ、仕様書を眺めるレベル。
次回は、ソースに入れるかも。

しかし、このブログは学習ブログなので「読者を念頭におかない!」ことが信条なのですが、だんだん、ゴミを撒き散らしているだけなのでは?という懸念が強くなってきました。WebのS/N比を低くしているだけだなぁ、と。まぁそんなこともぼちぼち考えながら、当面は続けていこうと思います。

こつこつ。


;;;; CLtL2におけるbackquoteの定義

;; Steeleの実装を読み解く前に、CLtL2のbackquoteの定
;; 義をおさらいしておこう。まず、

;; 「バッククォートは、テンプレートを用いることによっ
;; て複雑なデータ構造を構築するプログラムを簡単に書
;; けるようにする。」

;; という言葉が、アタリマエなのだが新鮮。そうか、バッ
;; ククォートはデータ構造のテンプレート記法なのだと。

;; さて前回見たとおり、文字apostropheはマクロ文字としては、
;; (quote foo)の略記にすぎない。'fooは、fooが何かに
;; 関わらず、(quote foo)として読み込まれる。

;; うむ。これはわかる。大丈夫。進もう。

;; 次はバッククォート。まず、バッククォートという言
;; 葉の使い方を確認しよう。通常の会話において、バッ
;; ククォートと言うとき、それは単体のマクロ文字を指
;; していることもあるし、バッククォート構文を指して
;; いることもある。大抵の場合、どちらを指しているか
;; は文脈によってあきらかである。なので、この文章で
;; も文脈を利用して「バッククォート」という単語でこ
;; の2つのものを指していくことも可能である。しかし、
;; ちょっと考えてみると、ここではどちらであるか明示
;; した方が書きやすいし、わかりやすそうだ。そこで、
;; この文章では「バッククォート」というように独立し
;; た単語としては使わずに、「マクロ文字バッククォー
;; ト」か「バッククォート構文」のいずれかを使うこと
;; にする。あとひとつ、先頭文字がバッククォートで始
;; まるLispフォームのことを「バッククォートフォーム」
;; と呼ぶことがある。これも必要に応じて利用する。

;; バッククォート構文の説明に移ろう。

;; バッククォート構文は、リーダマクロとして定義され
;; ている。複雑なデータ構造を簡便に構築するためのテ
;; ンプレート記法を目指したものである。

;; バッククォート構文で使われるマクロ文字は次の通り
;; である。

;; ` , # @ .

;; ここで、# は`の直後にあらわれたときのみバック
;; クォート構文のマクロ文字として機能し、@ と .
;; は、, の直後にあらわれたときのみそうである。

;; さて、一般的に、リーダマクロの定義は、マクロ文字
;; を含む印字表現をリーダが読んだときに、リーダの返
;; り値として、どのようなLisp objectを返すのか、と
;; いう変換として記述される。すなわち、定義がリーダ
;; で閉じているのだ。図示すると次のとおり。

;; <印字表現> -(reader)-> <Lispオブジェクト>

;; 例えば、

;; 'foo -(reader)-> (quote foo)を読み込んだときに返すLispオブジェクトとまったく同一。

;; である。すなわち、'fooをreaderが(quote foo)と読
;; み替えると考えてもまったく支障も懸念も無い。すな
;; わち、readerが返すLispオブジェクトの印字表現は、
;; (quote foo)であると言ってしまってよいのだ。

;; しかしバッククォート構文のマクロ文字については、
;; 少々事情が異なる。

;; バッククォート構文では、readerが返却する<Lisp オ
;; ブジェクト>が何であるかは実装依存としてしまって
;; いる。では、バッククォート構文の意味はどう定義す
;; るかというと、その実装依存な<Lisp オブジェクト>
;; をevaluatorが評価した結果得られる<Lispオブジェク
;; ト>が何かという、もう一段先で記述しているのだ。

;; <印字表現> -(reader)-> <実装依存> -(evaluator)-> <Lispオブジェクト>

;; これは後で再度説明する。

;; 準備は整った。ここからは、バッククォート構文の意
;; 味を形式的に定義しよう。

;; 用語1. form は、断りがない限り、任意のLispフォー
;; ムを表すものとする。

;; 用語2. basic は、form からリストと一般のベクタを
;; 除外したものである。

;; 用語3. 2つのLispフォームがあり、それらを評価した
;; 結果がequalにて等しいことを、評価等価と呼ぶ。こ
;; のとき、評価する回数は複数回も考えられる。A と
;; B が一回の評価において評価等価なとき、A = Bと書
;; く。二回の評価において評価等価なときは、A == B
;; と書く。

;; 1. `basic = 'basic.

;; 2. `,form = form.
;; ただし、ここでの form は、@ または . で始まる
;; Lispフォームを除外している。

;; 3. `,@form は誤りである。

;; 4. `(x1 x2 x3 ... xn . atom) = (append [x1] [x2] [x3] ... [xn] (quote atom))

;; ここで角括弧は次のようなものである。(上から順に判定)
;; - xjが,@で始まるとき、xjを,@formと書けば、[xj] = form。
;; - xjが,で始まるとき、xjを,formと書けば、 [xj] = (list form)。
;; - それ以外のとき、xjをformと書けば、 [xj] = (list `form)。
;; ここで、`formはバッククォートフォームである。
;; このフォームも再帰的に解釈をすすめる。

;; 5. `(x1 x2 x3 ... xn) = `(x1 x2 x3 ... xn . nil)

;; 6. `(x1 x2 x3 ... xn . ,form) = (append [x1] [x2] [x3] ... [xn] form)

;; 7. `(x1 x2 x3 ... xn . ,@form) は誤りである。

;; 8. `#(x1 x2 x3 ... xn) = (apply #'vector `(x1 x2 x3 ... xn))

;; 9. ,@ が使われているところはどこでも、,@ の代わ
;; りに ,. を使うことができる。この両者の違いは、,.
;; の場合は、後続のformが生成するリストを破壊しても
;; よいということである。

;; 10. バッククォート構文が入れ子になっている場合、
;; もっとも内側のマクロ文字バッククォートに属する
;; フォームが最初に展開される。

;; さて、先送りした話に戻ろう。

;; `((,a b) ,c ,@d)

;; を上記ルールに基づいて解釈(展開)すると、

;; (append (list (append (list a) (list 'b) 'nil)) (list ) d 'nil)

;; になる。しかし、バッククォート構文のルールが述べ
;; ているのは、readerがまさにこのLispオブジェクトを
;; 返さなければいけないということではなく、これと評
;; 価等価なLispオブジェクトを返せばよいということだ。
;; それは無数に存在する。例は次のとおり。

;; (append (list (cons a '(b))) (list c d)
;; (list* (cons a '(b)) c d)

;; readerが返すものは、これらのうちどれでもよいのだ。

;; さて、これでCLtL2のバッククォートの定義のおさら
;; いができた。次回は、ついにSteeleの実装をみていこ
;; う。

2010年1月24日日曜日

Lisp Quote Backquote (2)

体調が戻ってきたので、Lispいじりを再開。

と思っていたのですが、Lispにたどりつく前の調べもので終わってしまった。

でも、ずっと気になっていたところなので、スッキリ。


;;;; Quote, Unquote and Back quote

;; ところで、クォートとバッククォートって、印刷物で
;; みると、いつもどっちがどっちだかわからなくなって
;; しまう。Infoファイルとかだと、`hoge'のように、バッ
;; ククォートの方が先にくるんですね。だから、前がバッ
;; ククォートで、後がクォート。これ自体、なんかテレ
;; コに命名した方が自然じゃない?という気がする。

;; さらに、印刷のグリフだと後のグリフは、Writing
;; Directionに対して「バック」しているように見える
;; ので、それがバッククォートと思いたくなってしまう。
;; だけど、実はクォートなんですね。少なくとも大抵の
;; Lispの本ではそうです。

;; この違和感がどこから来るのか調べてみよう。

;; 英語の用法をしらべてみると、やっぱり普通は、引用
;; をはじめるところをquoteと言うようだ。そして、引
;; 用のおわりをunquoteと言う。unquoteは日本語の「括
;; 弧閉じ」かな。

;; 例:
;; MacArthur said, quote, I shall return, unquote.

;; quote unquote は日本語の「括弧」、「括弧閉じ」み
;; たいな言葉であり、総称的でもあるので、quote、
;; unquoteと言ったときの具体的な文字はいろいろあり
;; うるわけです。''もあり、""もあり、()もあり、{}も
;; あり、ですね。なので、"Lisp Quote Unquote"は、
;; 「Lisp () 」というのがひとつの解釈ですね。なるほ
;; ど。ついでなので、"Lisp Quote Unquote"の解釈につ
;; いてもう少し探ってみよう。

;; まず、そもそもquote-unquoteというのは、「いわゆ
;; る」という用法があるので、「いわゆるLispについて」
;; というものが別の解釈。

;; もうひとつがBBC-Radioの長寿クイズ番組"Quote
;; ... Unquote"。過去の名言や発言をパネラにお題とし
;; て出して、それが誰が言ったものかを当てたりする番
;; 組のようだ。この番組に因むのが3つ目の解釈。

;; 閑話休題。

;; では、バッククォートって何なのか、どこから来たの
;; か。それはどうやらASCIIコードと印字アプリ(Tex等)
;; がからんでいそうだ。これらを調べる準備として、
;; Unicodeではどうなっているか、確認しておこう。

;; まずUnicodeではQuotationは「同じグリフでも言語に
;; よって用法が違うもの」としている。その結果、ひと
;; つのグリフについて用法をオーバロードさせるという
;; ルールにしている。

;; それを前提として英語ではどうなっているか調べてみ
;; る。ここでは、wide characterは取り扱わない、とい
;; う制限をかけて進めよう。wide characterまで取り扱
;; うと結構なボリュームになるが、こと英語に関して得
;; られる知見は少ないと思うので。

;; まず、英語において推奨、というか、好ましい引用文
;; 字は、次の組み合わせだ。

;; U+2018 引用対象 U+2019

;; フォントがUnicodeに対応していれば、次の行でグリ
;; フを確認できます。

;; ‘ 引用対象 ’

;; 名前はそれぞれ次のよう。

;; ‘ : U+2018 : left single quotation mark
;; ’ : U+2019 : right single quotation mark

;; そうするとLispのquoteで使っているあの文字は何か
;; というと、

;; ' : U+0027 : apostrophe (= apostrophe-quote)

;; なんですね。では、Lispのback quoteで使っているあ
;; の文字は何かというと、

;; ` : U+0060 : grave accent

;; であり、quotation markではなくaccent markなんで
;; すね。ちなみにgrave accentって何ですか、というと、
;; 「抑音アクセント」のことのようです。

;; Unicodeには、どうやらback quoteという概念は無い
;; ようです。さて、ASCIIコードと印字アプリ(Tex等)に
;; 進もう。

;; まず、ASCIIコードにて、'と`は何なのか。

;; まず初版たるASA standard X3.4-1963によると、基本
;; 的には、printable charactersは、コードとグリフの
;; 対応だけがあり、名前は付いていない。ただし、
;; U+0027相当(0x27)については、(APOS)という注釈があ
;; るので、これはapostropheを意図したものなのだろう。
;; そしてなんと、U+0060相当はまだ未定義なんですね。
;; アルファベット小文字も未定義。ASCIIコード自体何
;; 度か改訂されて今のものになったようだ。そのあたり
;; の経緯は、

;; The Evolution of Character Codes, 1874-1968
;; http://www.pobox.com/~enf/ascii/ascii.pdf

;; に詳しい。この文書、とても面白い。一杯やりながら
;; 読みたいたぐい。

;; さて、ANS、ECMA、ISOでごちゃごちゃやって、1967年
;; に今の形にかたまったようだ。それは正式には、ANS
;; (Americal National Standard)ではなく、USAS (USA
;; Standard) として規格化されたらしい。(X3.4-1967)

;; X3.4-1967においては、Lispにおける
;; quoteとbackquoteは、

;; ' : 0x27 : apostrophe
;; ` : 0x60 : (no legend)

;; という状態です。

;; さて、先の歴史文書ではどうだろう。そこでは、
;; 0x27は、apostropheとacute accentがoverloadされて
;; おり、`はgrave accentとのみある。

;; ちなみに、acute accentは、「揚音アクセント」。本
;; 来のグリフは、grave accentと左右対称なものです。

;; これでASCIIコードを確認できた。

;; 次は印字アプリとの関係です。これはWikipediaに詳
;; しい。

;; http://en.wikipedia.org/wiki/Quotation_mark_glyphs

;; 要約すると次のとおり。

;; 昔のPCのフォントは、スクリーン用も印字用も0x27の
;; グリフは垂直ではなく、right single quote markの
;; ように傾いていた。すると引用が、’hoge’ のように
;; なり格好わるい。これをマシにするために、0x60が転
;; 用されたのだ。`hoge’とすると多少見栄えが改善され
;; たということ。

;; これで経緯はわかった。そして、経緯の中では、
;; 0x27をsingle quote markと呼ぶ例をみかけた。(あく
;; までapostropheにoverloadされたものとしてです)
;; 0x60を back quoteと呼ぶ例はみかけなかった。

;; ここからは推測だが、次のような経緯ではないか?

;; ASCIIコードの初版では、0x60 は未定義であり、
;; single quote markとして使えるのは、0x27しかなかっ
;; た。なのでこれを代用した。そしてこれを"Quote"と
;; 呼ぶようになった。しかし、もともと0x27は
;; apostropheなので、垂直ではなく’というように傾い
;; ているフォントが多かった。そんな状況で、0x60が
;; grave accentとして導入された。するとそれをleft
;; single quotation markとして転用するというアイデ
;; アがでた。傾きが逆なので具合がよい。さて、rightの
;; 方を0x27をつかってすでに"Quote"と呼ぶ慣習になっ
;; ていた。そのため新たに加えた0x60は、その逆傾斜である
;; ということで"Back Quote"と呼ぶ慣習となった。

;; ふう。writing directionがleft to rightなのに、な
;; ぜ、leftをback quote、rightをquoteと呼ぶのか?と
;; いうことについては、何となく合点がいった。

;; 違う観点で、もう少しだけ調べよう。

;; Common Lispでは、確か文字コードを指定していない
;; はずだ。その観点で、この話題を調べてみよう。
;; Common LispはCLtL2に限ることにする。

;; まず、CLtL2はASCIIというかISO6937/2というかを意
;; 識して、標準文字を定義している。これを文字レパー
;; トリと言う。しかし文字コードは実装の自由度として
;; おり、指定していない。

;; ところで、ISO6937/2では、全ての文字に文字ラベル
;; と記述(意味または備考と理解すればよさそう)が定義
;; されており、CLtL2もそれに準拠している。前述のと
;; おりCLtL2は文字コードは指定していないので、CLtL2
;; における文字レパートリの定義はグリフ中心となる。
;; ISO6937/2から文字コードを抜いたものと考えればよい。
;; 具体的には次のとおり。

;; SP05 ’ apostrophe
;; SD13 ‘ grave accent

;; せっかくなので、文字ラベルの読み方を確認しよう。

;; S P 0 5
;; | | | |
;; | | | |__ for alphabetic characters:
;; | | | | odd digit = small letter;
;; | | | | even digit = capital letter.
;; | | | |__ If N or S in first position:
;; | | | no special meaning.
;; | | |
;; | | |__ for alphabetic characters:
;; | | | 0 = etter without diacritical mark;
;; | | | 1 to 3 = letter with diacritical mark above it;
;; | | | 4 = letter with diacritical mark below it;
;; | | | 5 and 6 = special form.
;; | | |__ If N or S in first position:
;; | | no special meaning.
;; | |
;; | |__ For alphabetic characters:
;; | | A to Z = the respective letter of the Latin alphabet.
;; | |__ If N in first position:
;; | | D = digit;
;; | | F = fraction;
;; | | S = subscript or superscript.
;; | |__ If S in first position:
;; | A = arithmetic sign;
;; | C = currency sign;
;; | D = diacritical mark;
;; | P = punctuation mark;
;; | M = other symbol (miscellaneous)
;; |
;; |__ For all graphic characters:
;; L = Latin alphabetic character;
;; N = numeric graphic character;
;; S = special graphic character.

;; ちなみに、ISO6937/2では文字コードも定義されてい
;; るので、そのとおりに処理系を実装すれば、

;; SP05 ’ apostrophe => 0x27 '
;; SD13 ‘ grave accent => 0x60 `

;; となる。

;; さて、CLtL2の文字の定義においては、SP05がquoteで
;; あるとか、SD13がback quoteであるという話題はない。

;; それらがquoteであるとかback quoteであると成るの
;; は、「マクロ文字」の定義においてである。

;; ああ、そうなのだ。これは「文字」の定義の話ではなく
;; 「マクロ文字」の定義の話なのだ。
;;「#」を「シャープ」(文字定義)ではなく、「ディスパッチ」
;; と考えて(マクロ文字定義されて)、時にはその
;; ように呼ぶように、「’」を「アポストロフィ」ではなく
;; 「クォート」と考えて(定義されて)、時にはそのよう
;; に呼んだり、「‘」を「グレイブ アクセント」ではなく
;; 「バッククォート」と考えて(定義されて)、時にはそのよう
;; に呼んだりすると理解しておけばよいのだ。

こつこつ、再開。

2010年1月11日月曜日

Schemeコードバトンに参加しました

Schemeコードバトンに参加しました。g000001さんのをテンプレに、そのリポートを。

第1回 Scheme コードバトンのお知らせ - ひげぽん OSとか作っちゃうかMona-
Schemeコードバトンに参加しました - わだばLiperになる -

  • g000001さんからバトンを渡されました。
  • 渡されたバトン http://gist.github.com/273441
  • 次はquek(LETTER)さんへ
  • 渡したバトン http://gist.github.com/273567
  • やったことは、g000001さんがCL化したことを受けて、「とにかくCLらしく!」です。CL臭が結構出せたとしたら嬉しいです。
  • 変更点

    • シェルアプリではなくREPLアプリという流れを助長しました。エントリポイントは、mainではなく、(hige:pon)に変更。
    • 英単語を登録できるTop-level関数をつくりました。(hige:pin)
    • 辞書のCSV一覧表示のTop-level関数をつくりました。(hige:pun)
    • pinとpunは、実践Common Lisp の第三章の技法をそのまま使いました。CL入門者の方が担当されたときにいじりやすいかな、という狙いです。
    • このツールを使う名前空間(主にcl-userかな)と、このツールの名前空間の整理をきっちりしました。英単語はSymbolで表現し、case-sensitive かつ package-awareでread/writeするようにしました。(cl-userが英単語で汚れていかないように、ということ)
    • read-char,clear-inputのシーケンスが、私のTop-levelでは挙動があやしかったので、read-lineに変更しました。
    • ダイナミック変数を導入しました。ツールの利用のライフサイクルを通して固定されている束縛については、引数で渡していかなくてもよいだろうと。先々テストもしやすいかもしれません。
    • CLのイメージ指向を際立たせるためdocumentationストリングをまめに書きました。英語はいいかげんです。。。
    • 処理に無駄がないように、read time evaluationを必要なところで使いました。
    • 制御に関わる部分は命令的に書き、関数の性質がある部分は関数的に書きました。
    • 正答率でソートする部分は、学習効率を高めるためのストラテジの部分なので、関数として外に出して交換しやすくしました。現状は#'sort-dict-standardのみ提供。
    • とにかくCLらしく、ということで、(lambda もいちいち#'(lambdaにかえたり。

    • なんとも気持がいいので、goto、残しました。



やってみて気づいたのは、

Scheme と CL は思ってた以上に別物だ

ということです。自分一人で両方やっているときは、そういう風には思わなかったんです。距離感としては、C言語とJavaぐらいはあるように思います。優劣ではなく距離感として。

あ、もしかしたら今回のものがピュアR6RSだから?かもしれません。

こつこつ。

2010年1月10日日曜日

Lisp Quote Backquote (1)

On Lispの7 Macrosに入ったところで、quote backquoteが出てきました。簡単な説明はあるのですが、そういえば、backquoteについてちゃんと調べたことなかったなぁと思って調べはじました。これはそのメモです。

backquoteは結構難しいみたいなので、その3までいくかもしれません。


;;;;
;;;; Lisp Quote Backquote
;;;;

;; Common Lispのquoteとbackquoteを理解しようという
;; 試みです。

;; 前半は、基本的な動作を確認しています。振舞の確認
;; は、stefilによるテストとして記述しています。ただ
;; し、the reader の内部動作についてはテストとして
;; 書けないものもありました。それらはコメントに振舞
;; を記述しています。

;; 後半はbackquoteをちゃんと理解するために、CLtL2の
;; 付録Cをやっています。ちなみに付録Cにはバグという
;; かタイポがあります。ここに掲載しているのは修正版
;; です。

;; Steeleも書いているとおり、backquoteの理解は簡単
;; ではありません。

;; 特にbackquoteのsemanticsが、本質的には、readerと
;; evaluatorに跨っているところだと思います。すなわ
;; ち、backquoteが無くなるまで評価しきったところで、
;; 意味が定義されているのですが、マクロなどで
;; backquoteが残った状態で使われたりするのですね。

;; より根源的に言うと、入れ子になったbackquoteの仕
;; 様について、ANSI CLはほとんど定義していません。
;; これは大変残念なことです。


;;;; test facility
(asdf:operate 'asdf:load-op :stefil)
(use-package :stefil)
;;(in-root-suite)
;;(defsuite* test-for-understanding)


;;;; quote basics
(is (eq (quote 1)
1))
(is (eq (quote nil)
nil))
(is (eq (quote t)
t))
(is (not (eq (quote #\A)
(quote a))))
(is (not (eq (quote #\A)
(intern "#\A"))))
(is (eql (quote #\A)
#\A))
(is (equal (quote "ab")
"ab"))
(is (eq (quote a)
(intern "A")))
(is (not (eq (quote (quote a))
(quote a))))
(is (equal (quote (quote a))
(list (quote quote) (quote a))))


;;;; quote (standard macro character)
(is (eq 'a
(quote a)))
(is (equal '(a)
(quote (a))))
(is (equal (read-from-string "'a")
''a ))
(is (eq (read-from-string "a")
'a ))
(signals end-of-file (read-from-string "'"))
(signals end-of-file (read-from-string "' "))
(is (equal (read-from-string "' a")
''a))
(is (equal (list ' a 'b)
'(a b)))
(is (eq (car (read-from-string "'a"))
'quote))


;;;; quote and conses
(is (eq 'nil
nil))
(is (eq '()
nil))
(is (equal '(a . b)
(cons 'a 'b)))
(is (equal '(a b . c)
(cons 'a (cons 'b 'c))))

(is (equal '(a)
(cons 'a 'nil)))
(is (equal '(a)
(list 'a)))
(is (equal ''(a)
(cons 'quote
(cons
(cons 'a 'nil)
nil))))
(is (equal ''(a)
(quote (quote (a)))))
(is (equal ''(a)
(list 'quote '(a))))
(is (equal ''(a)
(list 'quote (list 'a))))
(is (equal '('a)
(quote ((quote a)))))
(is (equal '('a)
(list ''a)))
(is (equal '('a)
(list (list 'quote 'a))))


;;;; backquote and quote
(is (eq `nil
'nil))
(is (eq `a
'a))
(is (equal `(a . b)
'(a . b)))
(is (equal `(a)
'(a)))

(signals end-of-file (read-from-string "`"))
(signals end-of-file (read-from-string "` "))
(is (equal (read-from-string "` a")
'`a))
(is (equal (list ` a ' b)
'(a b)))
(is (not (equal ``a
''a)))
(is (not (equal '`a
`'a)))
(is (equal `'a
''a))
(is (eq (eval ``a)
(eval ''a)))
(is (eq (eval '`a)
(eval ''a)))


;;;; backquote and comma
(setf a 1
b 2
c 3
x 'y
(symbol-function 'x) #'identity
y 'z
(symbol-function 'y) #'identity
z 9
(symbol-function 'z) #'identity
m '(p q r)
n '(10 20 30))

(is (eq `,a
a))
(is (eq `,x
x))
(is (eq `,m
m))
(is (equal `,m
'(p q r)))

(is (equal `(a . b)
(append (list `a) 'b)))
(is (equal `(a b . c)
(append (list `a) (list `b) 'c)))
(is (equal `(,a . b)
(append (list `,a) 'b)))
(is (equal `(,a . b)
(append (list a) 'b)))
(is (equal `(,a . ,b)
(append (list a) b)))
(is (equal `(,a ,b)
(append (list a) (list b) 'nil)))
(is (equal `(,@m)
(append m 'nil)))
(is (equal `(,@m ,@n)
(append m n 'nil)))
(is (equal `(a ,b ,m ,@n)
(append (list `a) (list b) (list m) n 'nil)))
(is (equal `((,a b) ,c ,@m)
;; (append (list `(,a b)) (list c) m)
;; (append (list (append (list a) (list `b))) (list c) m)
(list (list a 'b) c 'p 'q 'r)
))

(signals reader-error (read-from-string ","))
(signals end-of-file (read-from-string "`,"))
(signals end-of-file (read-from-string "`, "))
(is (equal (read-from-string "`, a")
'`,a))
(is (eq (eval '`,a)
a))
(is (equal (read-from-string "``,a")
'``,a))
(is (eq (eval '``,a)
'a))
(is (eq (eval (eval '``,a))
a))
(signals reader-error (read-from-string "`,,a"))
(is (equal (read-from-string "``,,a")
'``,,a))
(is (eq (eval '``,,a)
a))
(is (equal (read-from-string "`,`,a")
'`,`,a))
(is (eq (eval '`,`,a)
a))

(is (equal `````,,,,,a
a))
(is (eq ``````,,,,,a
'a))

(is (equal `,`,`,`,`,a
a))
(is (eq `,`,`,`,`,`a
'a))


;; nested backquotes and commas [the reader internal]
;; (The reader's behavior is implementation dependent.
;; Here we use the allegro.)

(is (equal (read-from-string "`a")
'(excl::backquote a)))

(is (equal (read-from-string "``a")
'(excl::backquote (excl::backquote a))))

(is (equal (read-from-string "`,a")
'(excl::backquote (excl::bq-comma a))))

(is (equal (read-from-string "``,,a")
'(excl::backquote
(excl::backquote
(excl::bq-comma
(excl::bq-comma a))))))

(is (equal (read-from-string "`,(+ 1 2)")
'(excl::backquote
(excl::bq-comma
(+ 1 2)))))

(is (equal (eval
(read-from-string "`,(+ 1 2)"))
3))

(is (equal (read-from-string "``,(+ 1 2)")
'(excl::backquote
(excl::backquote
(excl::bq-comma
(+ 1 2))))))

(is (equal (eval
(read-from-string "``,(+ 1 2)"))
(excl::backquote (+ 1 2))))

(is (equal (eval
(eval
(read-from-string "``,(+ 1 2)")))
(+ 1 2)))

(is (equal (read-from-string "``,,(list '+ 1 2)")
'(excl::backquote
(excl::backquote
(excl::bq-comma
(excl::bq-comma
(list (quote +) 1 2)))))))

(is (equal (eval
(read-from-string "``,,(list '+ 1 2)"))
(list (quote +) 1 2)))
(is (equal (eval
(eval
(read-from-string "``,,(list '+ 1 2)")))
(+ 1 2)))

(is (equal (read-from-string "`(,x `(,x `(,x)))")
'(excl::backquote
((excl::bq-comma x)
(excl::backquote
((excl::bq-comma x)
(excl::backquote
((excl::bq-comma x)))))))))
(eval
(read-from-string "`(,x `(,x `(,x)))"))
;; => (Y (EXCL::BQ-CONS X `((EXCL::BQ-LIST X))))

(eval
(eval
(read-from-string "`(,x `(,x `(,x)))")))
;; => (Y (EXCL::BQ-LIST X))

(eval
(eval
(eval
(read-from-string "`(,x `(,x `(,x)))"))))
;; => (Y) ; fully evaluated. implementation independent.

(is (equal
(read-from-string "`(1 `( 2 `( 3,,,x)))")
'(EXCL::BACKQUOTE
(1 (EXCL::BACKQUOTE
(2 (EXCL::BACKQUOTE
(3 (EXCL::BQ-COMMA
(EXCL::BQ-COMMA
(EXCL::BQ-COMMA X)))))))))))

(is (equal
(read-from-string "`(x `(y `(z,,,x)))")
'(EXCL::BACKQUOTE
(x (EXCL::BACKQUOTE
(y (EXCL::BACKQUOTE
(z (EXCL::BQ-COMMA
(EXCL::BQ-COMMA
(EXCL::BQ-COMMA X)))))))))))

(eval
(read-from-string "`(x `(y `(z,,,x)))"))
;; =>
;; (X (EXCL::BQ-LIST
;; (EXCL::BQ-QUOTE Y)
;; (EXCL::BQ-LIST (EXCL::BQ-QUOTE EXCL::BQ-LIST)
;; (EXCL::BQ-QUOTE
;; (EXCL::BQ-QUOTE Z))
;; Y)))

(eval
(eval
(read-from-string "`(x `(y `(z,,,x)))")))
;; => (Y (EXCL::BQ-LIST (EXCL::BQ-QUOTE Z) Z))
(eval
(eval
(eval
(read-from-string "`(x `(y `(z,,,x)))"))))
;; => (Z 9)
(eval
(eval
(eval
(eval
(read-from-string "`(x `(y `(z,,,x)))")))))
;; => 9 ; fully evaluated. implementation independent.

次回は、Steeleの、

;;; Common Lisp backquote implementation, written in Common Lisp.
;;; Author: Guy L. Steele Jr. Date: 27 December 1985

です。まだまだ精査中。。。これは楽しめる!!

こつこつ。

【On Lisp】6 Functions as Representation

なるほど。ネットワークというお題にて、コンパイルを、

  1. (ルール -> データ構造 ) + 辞書 + プログラム
  2. ルール -> 辞書 + ((データ構造 & プログラム) = クロージャ) [インタプリタ]
  3. (ルール -> ((データ構造 & プログラム) = リンクされたクロージャ)) -[コンパイル]-> コンパイル済みプログラム

という文脈で説明するのか。うまい。

お、Lisp復習パートが終わった。ついにマクロだ!

こつこつ。

2010年1月9日土曜日

【On Lisp】5 Returning Functions

ひさしぶりに、のんびりLisp。


** 5 Returning Functions

*** 5.1
- そうか、レキシカルスコープでは関数の呼出し時に新
しいクロージャをつくることができるということは、
ダイナミック変数とは違う意味で動的であるといえ
るな。
- ダイナミック変数でも関数呼出し時に関数を作ること
はできるが、それはどこまでいっても単なるダイナミッ
クバインディングなだけではある。

- 例

CL-USER> (defun make-adder (n)
#'(lambda (x)
(+ x n)))
MAKE-ADDER
CL-USER> (setq add3 (make-adder 3))
#<Interpreted Closure (:INTERNAL MAKE-ADDER) @ #x1000ee3a82>
CL-USER> (funcall add3 2)
5
CL-USER> (proclaim '(special m))
T
CL-USER> (setq m 1)
1
CL-USER> (defun make-adder-dyn (m)
#'(lambda (x)
(+ x m)))
MAKE-ADDER-DYN
CL-USER> (setq add3-dyn (make-adder-dyn 3))
#<Interpreted Closure (:INTERNAL MAKE-ADDER-DYN) @ #x100112ead2>
CL-USER> (funcall add3-dyn 2)
3
CL-USER> (setq m 2)
2
CL-USER> (funcall add3-dyn 2)
4
CL-USER> (setq n 2)
2
CL-USER> (funcall add3 2)
5
CL-USER>

- そうか。関数を引数にとり関数を返す関数が意味をも
つのは、返している関数が単なる関数ではなく、レキ
シカルクロージャであり、引数である関数をクローズ
しているからなんだな。
- クロージャの威力の紹介として、オブジェクトシステ
ムなど、値の保持方法として紹介されることが多い。
それはそれで役に立つけれども、そもそもOOをあまり
やらないという人にはいまいち説得力がないように思
う。でも、汎関数プログラミングのインフラだよ、と
言われれば誰にとっても説得力があるように思う。

*** 5.2
- 直交性という観点にいくなら、それはもうSchemeの
独壇場のような。。。
- おお、Common LispのScheme化的な動きが、、、

*** 5.4
- PGのcomposeはPAIPのものよりもいい。

- PAIPのは、1引数関数しか合成できない。

(defun compose (&rest functions)
#'(lambda (x)
(reduce #'funcall functions :from-end t :initial-value x)))

*** 5.5
- なるほど。再帰のパターン化というのはこんな感じな
んだな。
- やはりlambdaというのは、実行遅延なんだよなぁ。

CL-USER> (setq x 3)
3
CL-USER> x
3
CL-USER> (setq hoge #'(lambda () (setq x 5)))
#<Interpreted Function (unnamed) @ #x1000ff6d62>
CL-USER> x
3
CL-USER> (funcall hoge)
5
CL-USER> x
5
CL-USER>

- thunkも実行遅延なんだけど、より原始的な意味でい
うと、サブルーチンというか手続きというかもそれ自体
実行遅延だよね。
- 関数もそれらの一種とするなら、関数と関数呼出しと
は制御構造の親玉だよね。コードを切り貼りして実行
タイミングを調整する機構。本質的にはgotoやjump
だし。すると、構造化プログラミングとの折り合いは
どうなっているのだろう?
- このあたりについて、プログラミング言語階層での説
明語彙として「継続」を位置づけるのはどうだろう?
継続という用語の導入として、継続を取り出してファー
ストクラスで扱うときが始めてということが多いけど、
それだと逆にわかりにくいのではないか?
- 例えば末尾再帰もある意味継続の構造の問題ですよね。
継続にノイズが入らないのが末尾再帰というか。
- 関数が数学的な意味での関数であるとすると、話はまっ
たく別になるな。そもそも実行順とか制御の考えが入っ
て無いから。
- なんかつれづれなるままに、だな。。。

- さて、遅延させて渡すときはthunkを作る(使う)など
して、 関数でもコードの抽象化(パターン抽出)ができ
るよ、というのがこの章のテーマのようだ。
- ただし、やってみるとわかるが、関数だけで抽象化
させようとすると、関数的抽象化がどうできるのか
にいろいろ腐心することになる。
- もっと手軽に抽象化するなら、関数的抽象化ではな
く表現的抽象化であり、それがこの本の主題たるマ
クロなんですよ〜、ということかなぁ。

こつこつ。

2010年1月7日木曜日

【On Lisp】4 ユーティリティ関数

年始にもかかわらず、業務繁忙。
ちょっとずつ進めて、やっと4章完了。

ユニットテストをたくさん書きながら、理解を深めた。

特にあたらしい知見はない。

こつこつ。

2010年1月2日土曜日

【On Lisp】3 関数プログラミング


** 3 関数プログラミング
*** 3.1
- 副作用について読むとき、いつも違和感がある。
- プログラマまたはユーザから見れば、何が副作用で
何が本作用かは、実現したい課題によるのではない
か。
- それを(関数型)言語の視点から一方的に、値を返却
すること以外の作用を全て副作用と呼んでしまう。
これは乱暴ではないか?
*** 3.3
- なるほど。純粋に関数型であるかどうかよりも、関数
型インターフェイスを提供するかどうかを重要視する
のか。
- 「関数が返すquoteに注意」は知らなかった。

こつこつ。

【On Lisp】2 関数


** 2 関数
*** 前説
- イメージ指向の簡潔な解説あり。
*** 2.2
- PGは、Lisp2だとコードがかっこ悪くなることがある、
という。正気か?
- 'list'を'lst'と書くのはかっこ悪くないか?
*** 2.4
- お、このタイミングでplistと関数の組み合わせを扱
うのか。展開が早くてよろし。
*** 2.7
- count-instancesでlabelsの必然性の導入というのは
違和感ある。そもそもcount-instancesくらいなら、
labelsというか再帰すら使わないんじゃないかなぁ。

CL-USER> (defun count-instances-1 (obj list-of-lists)
(mapcar #'(lambda (list)
(count-if #'(lambda (x) (eq obj x))
list))
list-of-lists))
COUNT-INSTANCES-1
CL-USER> (count-instances-1 'a '((a b c) (d a r p a) (d a r) (a a)))
(1 2 1 2)
CL-USER>
*** 2.9
- On LispはCLtL2なんだな。
- Allegroは「空でないレキシカル環境においてインタ
プリタ的に定義された関数」をコンパイルできる。

CL-USER> (let ((y 2))
(defun foo (x) (+ x y)))
FOO
CL-USER> (compile 'foo)
FOO
NIL
NIL
CL-USER> (compiled-function-p #'foo)
T
CL-USER>

- そうか。コンパイルされた関数が返す関数はコンパ
イル済みなんだな。

CL-USER> (defun make-adder (n)
#'(lambda (x) (+ x n)))
MAKE-ADDER
CL-USER> (interpreted-function-p #'make-adder)
T
CL-USER> (compiled-function-p #'make-adder)
NIL
CL-USER> (setq add2 (make-adder 2))
#<Interpreted Closure (:INTERNAL MAKE-ADDER) @ #x1000d01fb2>
CL-USER> (interpreted-function-p add2)
T
CL-USER> (compiled-function-p add2)
NIL
CL-USER> (compile 'make-adder)
MAKE-ADDER
NIL
NIL
CL-USER> (interpreted-function-p #'make-adder)
NIL
CL-USER> (compiled-function-p #'make-adder)
T
CL-USER> (setq add2 (make-adder 2))
#<Closure (:INTERNAL MAKE-ADDER 0) @ #x1000e55e52>
CL-USER> (compiled-function-p add2)
T
CL-USER> (interpreted-function-p add2)
NIL
CL-USER>

こつこつ。

【On Lisp】1 拡張可能なプログラミング言語


* On Lisp
- 新しい知見や疑問点などをメモする。
** 1 拡張可能なプログラミング言語
- 新しい知見はない。

こつこつ。

On Lisp を読む

なんとなく、気分として、必読図書で読んでいないものを読みたくなりました。というと必読図書をかなりこなしていそうですが、ほとんと読んでいないというのが実情です。まずはLisp系からいこうと思います。

トップバッターとして、今まで手を出さなかったOn Lisp を読んでみます。

On Lisp

On Lispに手を出さなかったのには理由があるのですが、読んでもいいかなぁと思える状態にはなったようです。

さて、On Lispを読み終えることができたら、Let Over Lambdaもいけるかもしれません。お風呂用まで買ったのですが、読書条件がOn Lispにdepends-onしているために読めないでおります。

こつこつ。

2009年12月30日水曜日

うんにゃらマクロ

「うんにゃら」をマクロにしてみました。


CL-USER> (defmacro unnyara (name-string)
(let ((name-value (gensym))
(not-used (gensym)))
`(macrolet ((,name-value (,not-used)
(find-symbol (string-upcase ,name-string))))
(,name-value t))))
UNNYARA
CL-USER> (setq x 0)
0
CL-USER> (setq y 'a)
A
CL-USER> (proclaim '(special y))
T
CL-USER> (let ((x 1)
(y 'b))
(print (unnyara "x"))
(print (unnyara "y"))
(locally (declare (special x))
(print (unnyara "x")))
(progv '(x y) '(2 c)
(print (unnyara "x"))
(print (unnyara "y"))
(locally (declare (special x))
(print (unnyara "x"))
(values))))

1
B
0
1
C
2 ; No value
CL-USER>

レキシカルにしてもダイナミックにしても、適切な束縛の値を返してくれているようです。

こつこつ。

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、処理系独自対応などを調べているところで力尽きました。。。
いや、正確には、飽きました。。。

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


こつこつ。

2009年12月21日月曜日

Common LispとEmacs Lispの違い [関数のあたり]

というわけで、Prolog on Emacsの完成を目指して、Common LispとEmacs Lispのスコープ関係の違いを確認してみようと思う。
スコープ自体は、Common Lispがレキシカルとダイナミックの両用で、Emacs Lispはダイナミックのみというだけでさして難しくはないのだが、では、言語の中のいろいろな機構の振舞いはそれによってどういう違いがあるかというと、また別の話である。

まず、関数のあたりから調べてみよう。


* 関数のあたり
** symbolへの格納
- まず、symbolまわりがどうなっているかをなんとな
く。
- Emacs LispはLisp2であり、インターフェイスも
Common Lisp風。

*** Common Lisp の場合
CL-USER> (setq hoge 1)
1
CL-USER> (defun hoge (x) (+ 2 x))
HOGE
CL-USER> hoge
1
CL-USER> (hoge 3)
5
CL-USER> (symbol-value 'hoge)
1
CL-USER> (symbol-function 'hoge)
#<Interpreted Function HOGE>
CL-USER> (compile 'hoge)
HOGE
NIL
NIL
CL-USER> (symbol-function 'hoge)
#<Function HOGE>
CL-USER>

*** Emacs Lisp の場合
(setq hoge 1)
(defun hoge (x)
(+ 2 x))
hoge ; => 1
(hoge 3) ; => 5

(symbol-value 'hoge) ; => 1
(symbol-function 'hoge) ; => (lambda (x) (+ 2 x))
(byte-compile 'hoge)
(symbol-function 'hoge) ; => #[(x) T\207" [x] 1]



** 関数定義

*** 'defun'
- 名前をもつ関数を作成する。
- 名前はグローバルになる。

**** Common Lisp の場合
- マクロ。

CL-USER> (defun defun-at-global () 'global)
DEFUN-AT-GLOBAL
CL-USER> (defun-at-global)
GLOBAL
CL-USER> (let ((hoge))
(defun defun-in-let ()
'let))
DEFUN-IN-LET
CL-USER> (defun-in-let)
LET
CL-USER>

**** Emacs Lisp の場合
- special form。

(defun defun-at-global ()
'global)
(defun-at-global) ; => global
(let ((hoge))
(defun defun-in-let ()
'let))
(defun-in-let) ; => let

*** 'lambda'
**** Common Lisp の場合
- lambda formは、リストである。ただし、'lambda'を
第一要素とするなど、lambda expressionたるリスト
の構造の規約がある。この意味での'lambda'は単なる
シンボルである。
- a function name を求める場所に、lambda formを使
うことができる。(ただし、常に可能というわけでは
ない) このとき、そのlambda formをlambda
expressionと言う。lambda expressionは、関数の振
る舞いを直接表現していると言える。
- lambda formsがよく使われる場所は2つある。ひとつ
目は、通常の関数呼出し、すなわちリストの第一要
素として。例えば次のとおり。

CL-USER> ((lambda (x y) (+ x y)) 1 2)
3
CL-USER>


- ふたつ目は、'function' special operator の引数と
して。例えば次のとおり。

CL-USER> (function (lambda (x) (+ 1 x)))
#<Interpreted Function (unnamed) @ #x1000eec532>
CL-USER>

- このイディオムのためのリーダーマクロが存在する。
それが"#'"であり、後続のS式を(function )でくる
む。

CL-USER> #'(lambda (x) (+ 1 x))
#<Interpreted Function (unnamed) @ #x1000d2bb22>
CL-USER>

- また、略記のためのマクロ'lambda'がある。

CL-USER> (lambda (x) (+ 1 x))
#<Interpreted Function (unnamed) @ #x1000d5a402>
CL-USER> (macroexpand-1 '(lambda (x) (+ 1 x)))
#'(LAMBDA (X) (+ 1 X))
T
CL-USER>


**** Emacs Lisp の場合
- Emacs Lispでは、lambda expressionsは普通の
expression (式)であり、評価すると自分自身になる。
例は次のとおり。

(lambda (x y) (+ x y)) ; = > (lambda (x y) (+ x y))

- Emacs Lispでは、関数は、Cで書かれたものと、Lisp
で書かれたものがある。Lispで書かれたものは、バ
イトコンパイルされていないものと、バイトコンパ
イルされているものがある。バイトコンパイルされ
ていないLisp関数は、全てlambda expressionsで記
述されている。よって、言語仕様としては、Common
Lispと比べて、Emacs Lispの方がlambda
expressionsを関数概念の中心に置いている。多少
Schemeに近い部分がある(ただしクロージャが無い点
がずいぶん違う)。

- lambda expressions はそのままで関数定義なので、
関数が使えるところならどこでも使え
る。

((lambda (x y) (+ x y)) 1 2) ; => 3

- 'function'は、基本的には'quote'にすぎない。

(function (lambda (x y) (+ x y))) ; => (lambda (x y) (+ x y))


*** Closures
- Common Lispではクロージャを使える。Emacs Lisp で
はクロージャは使えない。
- Common Lispでクロージャを生成するには、レキシカ
ル環境を参照しつつ関数を定義する。Common Lispで
は、クロージャをレキシカルクロージャやクロージャ
オブジェクトとも呼ぶ。
- Common Lispでは、クロージャは関数の一種と位置づ
けられている。

*** fletとlablels
- Common Lispでは、fletやlabelsを用いて、局所的な
関数をつくることができる。別の言い方をすると、
関数の名前をスコーピングできる。
- Emacs Lisp自体では、このような機構は提供されて
いない。clパッケージにてマクロで提供されている。



** 'function' special operator/form
- 'function'の振る舞いは'flet'との兼ね合いが重要
である。

*** fletとの兼ね合い
**** Common Lisp の場合
- functionは、レキシカル環境で、名前に紐付けられた
関数オブジェクト(関数定義とも言う)を返すspecial
operator。文脈によって、symbol-functionとは返す
ものが異なる。

CL-USER> (defun hoge (x) (+ 2 x))
HOGE
CL-USER> (flet ((hoge (x) (+ 3 x)))
(funcall (function hoge) 1))
4
CL-USER> (flet ((hoge (x) (+ 3 x)))
(funcall (symbol-function 'hoge) 1))
3
CL-USER>

- 名前に紐付けられた関数定義が存在しない場合、
functionはエラーをあげる。

CL-USER> (function hoge)
#<Interpreted Function HOGE>
CL-USER> (function piyo)
; Evaluation aborted.
CL-USER>

**** Emacs Lisp の場合
- 一方、Emacs Lispでは、fletはマクロである。マクロ
でシミュレートするにあたって、symbolのfunctionセ
ルをつかっている。よって、functionと
symbol-functionの挙動は文脈によらず同じ。

(defun hoge (x) (+ 2 x))
(flet ((hoge (x) (+ 3 x)))
(funcall (function hoge) 1)) ; => 4
(flet ((hoge (x) (+ 3 x)))
(funcall (symbol-function 'hoge) 1)) ; => 4

- functionはspecial form。'function'は、プログラ
マにとっては'quote'と同じ。コンパイラ
は、'function'が使われているときはそれは
Function cellしか使われないことがわかるので、最
適化ができる。

- 'quote'と変わらないだけあって、関数定義が存在し
なくてもエラーにならない。

(defun hoge () nil)
(setq piyo nil)
(functionp hoge) ; => t
(functionp piyo) ; => nil
(function hoge) ; => hoge
(function piyo) ; => piyo



** funcall
*** Common Lisp の場合
- funcallは関数。引数は、'a function
designator'。'function designators'は関数を指し
示すなんらかのLisp Objectsのこと。関数なので、引
数は eager に評価される。アタリマエだが、評価さ
れた結果、function designatorsになるものならなん
でも引数にしてよい。
- function designators
- 関数オブジェクト
- この場合、'funcall'は、関数オブジェクトをそ
のまま使う。
- シンボル
- この場合、'funcall'は、global environmentを
参照して、symbolのfunction cellに格納されて
いる関数定義を利用する。

- 関数オブジェクトの例

CL-USER> (funcall #'car '(1 2 3))
1
CL-USER> (funcall #'(lambda (x y) (+ x y)) 1 2)
3
CL-USER> (funcall (lambda (x y) (+ x y)) 1 2)
3
CL-USER> (setq hoge #'(lambda (x y) (+ x y)))
#
CL-USER> (funcall hoge 1 2)
3
CL-USER>

- シンボルの例

CL-USER> (funcall 'car '(1 2 3))
1
CL-USER>


*** Emacs Lisp の場合
- funcallは関数。引数を eager に評価して、評価結
果によって振舞が違う。
- 評価結果がシンボルの場合。
- そのシンボルのfunction cellにある関数定義を
採用。
- 評価結果が関数の場合
- それを採用。関数がLisp系でもC系でも。
- 例

(setq hoge (lambda () 'fn-for-value-cell))
(defun hoge ()
'fn-for-function-cell)
(symbol-value 'hoge) ; => (lambda () 'fn-for-value-cell)
(symbol-function 'hoge) ; => (lambda () 'fn-for-function-cell)
(funcall hoge) ; => fn-for-value-cell
(funcall 'hoge) ; => fn-for-function-cell

(symbol-function 'list) ; => #<subr list>
(funcall (symbol-function 'list) 1 2) ; => (1 2)


こつこつ。

2009年12月2日水曜日

【maxima】ちょっと不安定?

ちょこちょこmaximaで遊んでいるが、いろいろなところで、Segmentation Faultとか、Stack Overflowとかが発生する。ちょっと不安定なような?

例えば。


Maxima 5.13.0 http://maxima.sourceforge.net
Using Lisp GNU Common Lisp (GCL) GCL 2.6.8 (aka GCL)
Distributed under the GNU Public License. See the file COPYING.
Dedicated to the memory of William Schelter.
This is a development version of Maxima. The function bug_report()
provides bug reporting information.
(%i1) eq1:[x^2+y*x-1,x*y^2-x+y];
2 2
(%o1) [x y + x - 1, x y + y - x]
(%i2) ans:algsys(eq1,[x,y]);

Unrecoverable error: invocation history stack overflow.

Process maxima aborted



Interpidのmaximaはgclを内包しているようだ。これを、allegroなり、sbclなりに変更すれば、common lispの中で状況を追えるかな? GCLだとGDBで追うことになりそうな。Maxima/GCL/C/Executableという階層でMaximaの問題をExecutableで追うのはかなりきつい。。。

2009年11月30日月曜日

maximaをいじる

手元で数式処理できるツールが欲しいなぁ、と、だいぶ前からMaximaに目をつけていた。

気分転換がてら、ちょこちょこいじってみようと思う。

参考書は、

はじめてのMaxima

にする。

ごりごり読むのではなくて、あくまでのんびり遊びながらつまみ食いする予定。

2009年9月10日木曜日

eli の使いこなし [起動済みのLispにEmacsを接続]


  • まず localhost 上。
  • Lisp にて

    (excl:start-emacs-lisp-interface t 1 7666 "~/.eli-startup")

    を評価。すると~/.eli-startup というファイルができる。
    このファイルの中身は、

    ^A7666 542496 :upper (8 (1) :FINAL 7) 1^A

    というシンプルなもの。Emacs に渡す接続情報である。
  • 次に、Emacs で、

    (fi:start-interface-via-file "localhost" "*common-lisp*" "~/.eli-startup")

    を評価。これでREPLが立ち上がる。メジャーモードは、TCP Common Lisp モード。このモードは、keymap は fi:lisp-listener-mode-map を使っている。

  • この方法で接続した場合も、fi:common-lisp-mode で開いたソースファイルとの連携ができる。
  • "localhost"をFQDNまたはIPアドレスに変更すれば、リモートの Lisp に接続できる。ただし起動情報ファイル(ここでは.eli-startup) をEmacsがあるホストで参照できるようにしておくこと。これは、ファイルを転送してもいいし、NFSやsambaで共有してもよい。
  • この方法で、複数 Lisp に単一 Emacs からREPL接続することができる。ただし、fi:common-lisp-mode で開いた Lisp ソースと連携するのは一番最後に開いた REPL だけ。
  • listenerを増やすことができるのも、一番最後に開いた REPL の Lisp だけ。

  • この方法で、2つ以上の Emacs をひとつの Lisp につなぐこともできる。そのときも、

    (excl:start-emacs-lisp-interface t 1 7666 "~/.eli-startup")

    これは一個でよい。

  • さて、

    (excl:start-emacs-lisp-interface t 1 7666 "~/.eli-startup-1")
    (excl:start-emacs-lisp-interface t 1 7667 "~/.eli-startup-2")

    として、それぞれに別の Emacs をつなぐこともできる。ただし、

    (excl:start-emacs-lisp-interface t 1 7666 "~/.eli-startup")

    これひとつに2つの Emacs をつなぐのと、機能上の違いはない。:processes 的には、Editor Server が2つできるので違いはある。

  • こうしてみると、かなり自由自在というか柔軟なんだな。

時間ができたときに、整理して 逆引きCommon Lisp に書こう。
こつこつ。

2009年9月7日月曜日

eli の使いこなし [fi:common-lisp によるリモートLispの起動]


  • これ、M-x fi:common-lisp の引数指定で ホストを指定してあげればよいだけなのですが、うまく起動できない。
  • ソースを調べてみると、どうやら ssh ではなく rsh 専用になっているようだ。
  • eli のためだけに、ssh とは別に rsh を運用するというのはちょっと避けたい。
  • リモート Lisp は fi:start-interface-via-file に一元化かな。

こつこつ。