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

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月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

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

こつこつ。