#:g1

Lisp本積読解消: 記号処理プログラミング 2.1(b) マクロの機能

Posted 2017-02-26 18:17:43 GMT

今回は、記号処理プログラミングのマクロの箇所を読む。

本書は、岩波講座 ソフトウェア科学の第8巻で、記号処理言語としてLispとPrologの解説があり、記号処理機の説明としてELISの解説もある。
著者の後藤滋樹先生はNUE関係の方でもあるが、本書はTAO/ELISの入門書としても使えたのかもしれない。
出版は、1988年で記述はCommon Lispに合せてある。

マクロの解説は、「親言語としてのLISP」という段落の中にあり、基本的にDSL構築の手法として紹介している。
親言語とは、DSLに対してのホスト言語のこと。

Lispは拡張可能言語であることが紹介され、Fortran等との比較がある。

マクロには、マクロ文字と構文のマクロがあることが解説され、概念的な説明が終わる。

具体的な使い方は別の章で解説となるが、後の章では、ATNを用いた英語の構文解析でDSLを作成するアプローチを紹介し、これにマクロを利用する。
バッククォートやdefmacroの解説もこのDSL作成の過程で解説するようになっている。

基本的にATNのDSLを作成する必要に応じて解説しているので、マクロ機能単体で細かく説明することはないが、こういう説明も面白いと思った。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: Object-Oriented Common Lisp 11 Macros

Posted 2017-02-25 20:18:30 GMT

今回は、Object-Oriented Common Lispのマクロの章を読む。
著者のStephen Slade氏はTの開発者として知られていて、Tについての著作もある。

各章の冒頭は古典からの引用で飾られていて随分知的な雰囲気がある。また語彙が豊富なので英語が苦手な自分のような人間には単語を辞書で確認しつつ読み進めるのが面倒臭い。

まず、Lispでは構文を拡張できることが説明され、repeatという繰り返し構文を作りながら解説が進む。

バッククォートの説明では、defmacroとの組み合わせの他にdeftypeにも言及されていて、これは少し珍しいと思った。

ちなみに、本書でも、Common Lisp Programming for Artificial Intelligenceのバッククォートの説明の箇所が引用されていた。

本書のマクロ作成解説で特徴的なのは、gensymを使わない所。
他の書籍では変数の捕捉問題には大抵gensymを使った対処方法が説明されているが、本書では、変数のスコープを別々にすることで対処する方法のみを説明する。具体的には、repeatのようなものは、

(defmacro repeat (n result &body body)
  `(flet ((body () ,@body)
          (result () ,result))
     (let ((n ,n))
       (do ((count n (1- count)))
           ((<= count 0) (result))
         (body)))))

(repeat 3 nil (print 'a)) ⊳ a ⊳ a ⊳ a → nil

のように本体や結果節を繰り返し構文の変数スコープの外に置くことで捕捉を防ぐ。

ただ上記のようにfletを使った場合、関数名のブロックが作られるため、

(repeat 3 nil 
  (progn (return-from body) (print 'a)))
→ nil

としてしまうと、ブロック名を捕捉できてしまうので、無名関数を使うなり関数名をgensymで生成するなりする必要があるだろう。
なお、本書では上記のような変数以外での捕捉問題の解説はない。

また、局所関数に展開する方法の他に、下請け用関数を定義するバージョンもあり(練習問題の解答で詳細が解説される)

Common Lisp Programming for Artificial Intelligenceでも紹介されていた方法だが、一歩進めた感がある。

次に、関数とマクロの比較、分配束縛、リーダーマクロと解説が進む。
リーダーマクロについては結構詳細な解説がある。

最後に進んだ話題として、リードテーブルの文字ケースの扱い、define-modify-macro、ディスパッチマクロ文字、*macroexpand-hook*について解説がある。

面白いのが、*macroexpand-hook*の解説で、traceのマクロ版を作成しつつ解説が進むが、何故かとてもボリュームがある(8頁も)。
コードが多いとはいえ、*macroexpand-hook*についてここまで詳しい本は他にないのではないだろうか。

練習問題も充実しているが、設問の意図がはっきりしないものが多く、またあまりマクロに関係無い問題もあり飛したくなる。

解答が巻末に付いているので、意味が分からない場合は、解答を見て意図を把握してから問題に取り組むと良いかなと思った。

気になった所

  • ANSI CL規格に沿ってない
  • リードテーブルの扱い
  • 処理系依存なコードが普通に出てくる
  • Common Lispの命名規約に沿ってない

1997年出版であるので、ANS沿って説明かと思いきや、どうもCLtL2相当の説明らしくspecial-form-p等が普通に出てくる。

リードテーブルの扱いというのは、(copy-readtable rt nil)すれば良いのに、無駄なユーティリティを作成しているように見える点。

処理系依存コードについては、Allegro CLに依存しているらしいが、著者はどの辺りが処理系依存かはあまり把握していなさそう。

命名規約に沿わないというのは、スペシャル変数には、*earmuff*を付けるのが規格が採用しているマナー(というか謎のバグ防止)であるのに付けない所。

結び

マクロの章しか読んではいないが、どうも本書は脱線が多いのではないだろうか。
ディスパッチマクロのディスパッチという概念を解説するのに紙テープ時代の話を引用したりとか……。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: はじめてのLisp関数型プログラミング 2.2.8 マクロ関数

Posted 2017-02-24 16:18:21 GMT

今回は、はじめてのLisp関数型プログラミングのマクロの箇所を読む。

本書はLispで関数形関数型プログラミングを学ぶのが主なのでマクロについては3頁程でさらっと流している。

対象のLisp方言は、ISLISPか、Common Lisp。
Lispのマクロは文字レベルではなく、構文レベルであること、バッククォートでテンプレート的にマクロを書く方法、評価方法がざっと書いてある。

3頁に色々詰め込んであるので、初学者にはちょっと難しいかなと思った。
これで興味を持ったら他のもっと詳しく書いてある本で勉強するのが良いと思う。

マクロでifを定義するのに、ifを使う例があったが、そういえば、ifを使わない方法があるのを思い出した。

Henry G. Baker氏のMetacircular Semantics for Common Lisp Special Formsにあるアイデア。
※記憶で書いたらシンボルの属性の使い方が逆だった。

(setf (get 'my-if 'T) 
      (lambda (then else) 
        (declare (ignore else))
        (funcall then)))

(setf (get 'my-if 'NIL) (lambda (then else) (declare (ignore then)) (funcall else)))

(defmacro my-if (pred then &optional else) `(funcall (get 'my-if (not (null ,pred))) (lambda () ,then) (lambda () ,else)))

(defun fib (n) (my-if (< n 2) n (+ (fib (1- n)) (fib (- n 2)))))

(fib 30) → 832040


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: ANSI Common Lisp 10 マクロ

Posted 2017-02-21 17:43:33 GMT

今回は、ANSI Common Lisp / ポール・グレアムの10章 マクロを読む。 2003年位に本屋で立ち読みしてLispのことはさっぱり分からないが面白そうなので購入。

10 マクロ

やはり、ポール・グレアム(pg)氏の文章は面白い。
若干の主張の偏りはあるが、面白い文章でCommon Lispの入門ができるというのは素晴しい。
偏っている所が面白い所でもある。

まず、評価器の話から始めて、マクロの話につなげる。
マクロのメリットが説明されるが、ここで脚注に関数の評価について何故か細かい注釈がある。
関数と引数の評価についてだが、ANSでいうと、

あたりの話だと思われる

(progn
  (defun foo (x) (+ x 3))
  (defun bar () (setf (fdefinition 'foo) (lambda (x) (+ x 4))))
  (foo (progn (bar) 20)))

上記が23になるか、24になるかは処理系依存。
ちなみに、SBCL、GCL、Allegro CLあたりは23、LispWorks、Clozure CL、ECLは24になった。同じKCL系のGCLとECLで違っているのがちょっと不思議

更に話が逸れるが、Macro Forms as Placesの話もさらっと書いてあった。
読んだ筈だったが、すっかり忘却してしまっていたらしい……。

閑話休題。次にバッククォートの説明がされ、マクロの使い方と、マクロの設計について、またマクロ作成時に注意する点等が一通り解説される。

マクロの設計について色々語る本は少ないと思う(On LispとかLet Over Lambda等目立つ本はあるが……)。

次に、一般化参照の解説と、マクロを書くためのユーティリティを解説し、最後に、未だ開拓され尽されてはいないマクロの可能性で締める。

練習問題も考えさせるような問題で良くできていると思った。

気になった所

aifitcall-next-methodの引数省略のようなものが同列に説明されているけれど、ちょっと違うのではないかなあと思った。

結び

「マクロを書くというのは〜コンパイラを書き換えられるのとほとんど一緒である」のような言い回しにやはりセンスを感じる。

解説内容も親切で詳しいし、この章にCommon Lispのマクロを書く上で必要なことは、殆ど全部書いてある。面白いので読んだことがない人にはお勧めしたい。
また、pg氏のようにマクロのユーティリティを積み上げていく独特のスタイルについては、On Lispのコードや、ユーティリティ集、Arcのユーティリティのソースコードを読むと雰囲気がつかめる


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: Common Lisp Programming for Artificial Intelligence 6.2 Macros

Posted 2017-02-20 21:03:26 GMT

今回は、Common Lisp Programming for Artificial Intelligenceのマクロの解説の箇所を読む。
1989年出版の本で、2010年にAmazonで¥550位で購入

内容は年代と題名から察せられるように、GOFAIな本で、Common Lispを解説しつつも本題はAIという本。

6.2 Macros

マクロの説明としては、それ程詳しくはないが、評価機がどのようにマクロを扱うか、という視点で説明しているのは、面白いと思った。

評価機の視点で、通常の関数とマクロを比較し、なんとなく使い方を理解させるという戦略だろうか。

それが済むと、早速バッククォートを利用した簡単なマクロの作成方法を解説し終了。

基本的にコード生成の説明に近いと思った。

マクロ書法、作成上の注意点、macroletについての説明などは無し。

気になった所

マクロのユースケースとして、高速化が挙げられているが、やはりこの時代はこういうのが多かったのだろうか。この辺りに古さを感じる。

また、練習問題で作成させるマクロが、関数として実装した方が適切な物が多く、マクロである必然性に乏しいため、あまり適切な問題ではないなあという感想。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: LISPマシン・プログラミング技法 3.6 マクロ

Posted 2017-02-19 18:01:00 GMT

今回は、LISPマシン・プログラミング技法のマクロ解説を読む。

2014年にAmazonで100円で購入。
この本は、Symbolics Lispマシンの入門書だが、Lispプログラミングについても、そこそこの分量を割いている。

3.6 マクロ

簡単にソースコードの変換の仕組みであることが説明され、マクロの利用が有効な手段となる事例として、

  1. プログラムが長い
  2. 入力がめんどくさい
  3. 間違えやすい
  4. いろんなところで用いられる

位を挙げている。

ざっくり言えば複雑さを制御する手段として説明したいらしい。

詳細については、Symbolics Common Lisp: Language Concept “Macros” を参照せよとあり、後は簡単にマクロ展開のツールの説明がされるのみ

マクロ展開のツールの説明

Lisp Listener(REPL)では、Show Expanded Lisp Codeコマンド、関数としては、(scl:mexp)が紹介されている。

Show Expanded Lisp Codeというのは非常に長いコマンドだが、実際には、s e l位を打ち込めば補完してくれるので、呼び出し自体はそれ程面倒でもない。
とはいえ、Listenerに直接入力することはあまりないと思う。

(scl:mexp)の方は、実行するとCommon Lispのinspectのような感じで入力を待つので、そこにフォームを入力する。

一番手軽なのは、やはりZmacsのコマンドで、Macro Expand Expression (c-sh-M)だろう。
SLIMEにも同様のコマンドがあるが、Lispマシンから受け継いできたコマンドともいえる。
マクロを再帰的に展開するには、Macro Expand Expression All (m-sh-M)を使う。

なお、GNU Emacsだとターミナル経由で利用されることが多かったためか、Shiftのあるなしは検出しないことが多いが、Symbolicsでは、Shiftも良く使い、マニュアルではshで表し、単体の文字は大文字で書かれる。

結び

良い機会なので、後でSymbolics Common Lisp: Language Conceptのマクロの箇所も読んでみようかなと思う。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: Common LISPcraft 13. Macros / 14. Macro Characters

Posted 2017-02-18 19:38:37 GMT

今回は、Common LISPcraftのマクロの箇所を読む。
2009年位に600円位で購入。
出版は、1984年(第二版1986年)で、時代的にCommon Lispの仕様は、CLtL1。
なお、邦訳はされていない。

Common LISPcraftの前に、LISPcraftというFranz Lispを対象にした本が、同じくWilensky氏によって書かれていて、それのCommon Lisp版ということで、Common LISPcraftという題名になっている。
しかし、題名は引き継いでいるものの殆ど新規に書下したように見える。

Chap. 13 Macros

アクセサの別名を作る所から始まって、マクロ展開の解説、setf系マクロの定義の方法など、割合に網羅的に解説されている。
解説は丁寧だが、古い方言や書法の話が入ったりしているので、ちょっと古さを感じてしまう。
バッククォートや、macroletの解説はなし。

Chap. 14 Macro Characters

リーダーマクロの解説。通常のマクロ文字の解説の後、バッククォートの説明があり、次にディスパッチ・マクロ文字の解説がされる。
解説は、かなり丁寧で、これ程丁寧にリーダーマクロの基本を解説している本は無かったような気がする。

バッククォートの解説の後、13章では導入しなかったバッククォートを利用してマクロを書くことが解説される。 この辺りの流れは、どうやら前回眺めたArtificial Intelligence Programmingに影響を受けているようで、解説している内容も割と似ている(がこちらの方が簡素)。

ちなみに、13章のマクロのネタにも、Artificial Intelligence Programmingのネタが使われている。

結び

この本は基本的なスタンスとして機能は網羅的に解説するように思えた。

網羅的に解説しつつも取って付けたような感じはしないので、解説が上手いのかなあと思ったりするが、例題にCommon Lispより前の古えのLisp方言の機能を真似るようなものが多く、ちょっと古臭いのが残念。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: Artificial Intelligence Programming 2nd ed. | 2. Macros and Read-Macros

Posted 2017-02-14 17:24:27 GMT

今回は、Artificial Intelligence Programming 2nd ed.を読む。 2013年にAmazonで565円位で購入。
第一版の方は、人工知能プログラミング / 日本コンピュータ協会として翻訳もされているらしいが、この本は何故かAmazonでは取り扱いがない。
第一版の方は出版が1981年ということもあるので、LispはCommon Lisp以前の方言だと思われる。

第二版は、1987年の書籍。
人工知能プログラミングの本だが、半分位はCommon Lispの解説があり、PAIPと似たような構成。というかLispの人工知能本は、第一部Common Lisp、第二部人工知能という構成が殆どで、この本もそのようになっている。

著者は、Eugene Charniak、Christpher K. Riesebeck、Drew V. McDermott、James R. Meehan各氏でLisp界隈では有名な方々。

制御構文の詳しい解説もされていない2章の時点でマクロとリーダーマクロの解説を始めるというのは、なかなか珍しいと思った。
しかも、リーダーマクロの解説が先。リーダーマクロの解説の題材もバッククォートを展開する関数を定義して、動きを観察したり、動作をいじってみたりする。
set-macro-characterの使い方なども解説される。

リーダーマクロの次に通常のマクロの解説になるが、バッククォートの詳細は理解しているので、そちらの解説はなし。
マクロ学習の題材としては、letの作成が取り上げられる。

次に、repeatというdotimesのようなマクロをお題にして、マクロ変数の捕捉問題と、多重評価問題を解説する。
この解説の中で、変数捕捉回避の方法として、gensymを使った方法とは別に、thunkを使った方法が解説される。

(defmacro repeat (n &body body)
  `(let ((thunk (lambda () ,@body))
         (cnt ,n))
     (loop 
      (when (>= 0 cnt) (return))
      (funcall thunk)
      (decf cnt))))

更に、このrepeatの場合は、下請け関数にthunkを渡すように展開してしまうことも可能であることが示される。

(defmacro repeat (n &body body)
  (repeat* ,n (lambda () ,@body)))

この辺りは類似の本には見られない解説で面白い。
一応、クロージャーで変数捕捉を回避する方は、関数呼び出しが発生するのでベタに展開するマクロより効率は落ちるという説明もされている。

下請け関数に全部任せてしまうのは、最近では、call-withスタイルとも呼ばれ、Google Common Lispスタイルガイドでも紹介されているものだが、結構昔からあるんだなあと感心

なお、thunkもインライン化されたりコンパイラが優秀ならば、ベタなマクロと同じ効率になることもあるので、一概には効率が落ちるともいえないと思う。

といっても、まあ効率重視の局面ではベタに書くのが良いのだろう(二転三転)

そして、let特殊変数スペシャル変数専用の結合束縛構文版を作成する。これは後の章で更に込み入ったものとなる(最終的にはletfのようなものになる)

気になった所

題材は面白いんだけれども、練習問題を解くのは結構難しい気がした。
機転が利く人は前提知識がなくても解けるのかもしれないが、解けなくても、自分なりに考えた上で解答を見て動作を確認すれば、へえと感心するような類の問題が多い気がする。 (なお、回答は巻末に纏まっている)


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: 実用Common Lisp | 3.2章 特殊形式: マクロ etc

Posted 2017-02-13 17:01:50 GMT

今回は、実用Common Lisp、通称PAIP。名著として名高い本のマクロの解説を読んでみる。

3.2章 特殊形式: マクロ

この本では、まず、特殊形式の仲間としてマクロを解説し、後に正確な定義に近付けているらしく、この章でマクロと特殊形式の違いが解説される。

マクロを書く順序として、

  1. 本当に必要かどうか
  2. 構文を書く(書き出す・考える)
  3. 展開形を考える
  4. 実際にdefmacroで記述する

としていて、本当に必要かどうかまず良く考え、書くとしたら既存の文法に合せるように書くべきであることが説明されている。

実際にマクロを書く例として while を作成するが、最初は、バッククォートを利用しないもので書き、次の段落(逆引用符表記法)でバッククォートを利用したテンプレート作成的な書き方を説明。

他、

  • 24.6 シーケンス関数: Once-only: マクロ論のレッスン・マクロ乱用の回避
  • 25.14 マクロに関する問題

でもマクロについての解説あり。

全体的に機能の解説をするついでにコーディングスタイルの話もすることが多いため、余計な解説と情報が多いなという印象。
特殊形式の解説の筈なのに、シーケンス関数の話も混ざっていたり、後の章の話が度々出てきたり、Common Lispの基礎を学ぶのにはこの本はあまり向いていないのではないかと思った。
しかも、コーディングスタイルについては、若干押し付けがましい。
押し付けがましい割には、Norvig先生のコード例も自身のコーディングスタイルに沿っていなかったりする。
また、示されているスタイルは昔からのオーソドックスなものというよりは、Novig先生オリジナルな成分が多目なスタイルに思える。

大著なので、最初に完成した部分と、入門者向けの部分等で統一感がないのかもしれない、と少し思った。

スタイルについては話半分で流しておいて、そこそこ書けるようになって、気になってきたら、Norvig先生がKMP氏と一緒に纏めたスタイル論があるので、別途そちらを参照した方が良いのではないかと思う。

気になった所

局所ローカルマクロ・関数によるシャドウについて

⸨25.14 マクロに関する問題⸩では、3.2章の内容をさらに細かく考察していて、局所ローカルマクロ・関数が展開された関数・マクロをシャドウしてしまう問題についても触れている。
具体的には、下記のような状況だが、

(defun last1 (list)
  (car (last list)))

(defmacro foo (list) `(last1 ,list))

(flet ((last1 (x) x)) (foo '(1 2 3 4)))(1 2 3 4)

これを防ぐには、

(defmacro foo (list)
  `(funcall ,#'last1 ,list))

(flet ((last1 (x) (print x))) (foo '(1 2 3 4))) → 4

と書く必要がある、と解説されている。
実際にはこのように書かれることは殆ど無いが、Common Lispでは、局所ローカルマクロ・関数が多用されないので、スタイル上あまり問題にならないとの説明。

,#'last1というのがそら恐しいが、大域定義を参照するのだから、単に'last1とシンボルで書いてしまえば良いじゃん、と思ってしまった。何か理由があるのだろうか。

  1. #'last1
  2. 'last1
  3. ,#'last1

と書けるが、#'last1と書いてしまっては元の木阿弥。
本書では関数は、#'を付けて書くように統一されているので、'last1ではなく、,#'last1とした、という所だろうか。
もしくは、インライン展開やコンパイラ・マクロへの配慮とか?

局所ローカルマクロ・関数のシャドウ問題への対処は実際には、上記のようにfuncallに分割するようなことはしないで、パッケージ・ロックで防ぐのが一般的だろう。

パッケージ・ロックは、規格外の機能だが大抵の処理系は装備しているので、これを使えばシャドウされたらエラーとすることができる。

たとえば、SBCLであれば、(sb-ext:lock-package "パッケージ")とするだけで良い(もしくはdefpackage:lock Tを指定)

(とはいえLispWorksのように大域的な再定義しかエラーにしてくれない処理系もある……)

記述が古いところがあるので注意

ANSI Common Lispが成立する前に出版された本故に記述が古い所があるdefine-setf-methodspecial-form-p等々)

macroletの解説がない

これだけの大著なのにmacroletの解説がない。
まあ人工知能がメインの本なのでしょうがないのかもしれない。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: Common Lisp 入門 | 7章 マクロ

Posted 2017-02-12 15:01:35 GMT

今回は、Common Lisp 入門 (湯淺太一/萩谷昌己著)のマクロの章を読む。

マクロ展開関数は一般のリスト処理関数として定義できる.
すなわち,マクロ展開とはリスト処理の一種である

という明確な解説から始まって、マクロと特殊形式スペシャル・フォームの違い等を説明。実装者視点なのか細かい所の解説が多い。

組み込みマクロの解説としてsetfの解説があるが、普通のマクロよりこちらの方に力が入っている印象。
一般化変数generalized variableの説明も詳しい。

マクロの定義については、まずはバッククォートを使わないで説明が進み、局所ローカルマクロまで解説した後に、バッククォートを使った書き方を解説。
macroletの定義部内でレキシカル変数・関数が参照できないことが解説されているが、このことを解説している本は少ないと思う。

コードにすると、

(let ((x 42))
  (macrolet ((foo () x))
    (foo)))
⊳ Error: The variable x is unbound.

のようなもの。

ANSによると、

The macro-expansion functions defined by macrolet are defined in the
lexical environment in which the macrolet form appears. Declarations
and macrolet and symbol-macrolet definitions affect the local macro
definitions in a macrolet, but the consequences are undefined if the
local macro definitions reference any local variable or function
bindings that are visible in that lexical environment.

とのことで未定義動作になる。

実際試してみるとAllegro CLのインタプリタ動作では動いたりするがコンパイルするとエラー、他の処理系も軒並エラーとなる。

ふと、&environmentを使って中でマクロ展開したら良いのではないかと思い試してみた所、この方法ならどの処理系でもレキシカル変数・関数が参照できた。
しかしこれは合法なのだろうか。シンボルマクロの展開に辺りに解説が書いてありそうだが合法かどうかの記述をみつけることができなかった(乞う詳細)

(defun kou (arg)
  (let ((x arg))
    (macrolet ((x (&environment env)
                 (macroexpand 'x env)))
      (x))))

(kou 42) → 42

(defun ots (arg) (let ((x arg)) (macrolet ((x (&environment env) (symbol-macrolet ((x (macroexpand 'x env))) `(list ,x ,x ,x)))) (x))))

(ots 42)(42 42 42)

(defun hei (arg) (let ((x arg)) (flet ((hei () x)) (macrolet ((x (&environment env) (macroexpand '(hei) env))) (x)))))

(hei 42) → 42

(defun tei (arg) (let ((x arg)) (flet ((tei () x)) (macrolet ((x (&environment env) (macroexpand '(funcall #'tei) env))) (x)))))

(tei 42) → 42

注意点

CLtL1時代の本なので、special-operator-pspecial-form-pだったりする。


HTML generated by 3bmd in LispWorks 7.0.0

Older entries (2085 remaining)