#:g1

Lisp本積読解消: LISP — 8 FEXPRとMACROの定義

Posted 2017-03-17 10:11:22 GMT

今回は、LISPのマクロの箇所を読む。
本書は、WinstonのLisp本の第一版で、原書の出版は、1981年。
安く売られていることが多いが、Common Lispではなく、MACLISPなので注意。
本章の内容もCommon Lispには存在しないFEXPRについて解説がある。

ちなみにこの本は、訳文の文体が独特なことで有名。

8 FEXPRとMACROの定義

まず、FEXPRの解説から。大まかにいって不定長引数と引数を評価しない関数であることが説明される。
関数では定義できない代表的なものとして、ifをFEXPRで定義してみせる。
しかし、FEXPRが内部でevalを呼ぶときに注意しないと変数名の競合を引き起してしまうことを説明。
この問題はマクロでエレガントに解決できるとしている(マクロも似たような問題があるが……)

そしてマクロの解説。
バッククォートを使わない(使えない?)ためsubstを使って雛形を加工していく方法が紹介されている。これはこれで面白いかもしれない。
ただ間違った置換が起きないように注意する必要はあるかも。

(defun if macro (x)
  (subst (cadddr x)
         '$$else
         (subst (caddr x)
                '$$then
                (subst (cadr x)
                       '$$pred
                       '(cond ($$pred $$then)
                              ('T $$else))))))

練習問題も定番のものが多いがそこそこ面白いので全部解いてみると良いだろう。

結び

1981年の本にしては、ちょっとLISP的な内容が古いような気がするが、1977年に出版されたArtificial IntelligenceのLISPの部分を切り出したものが原書ということなので、しょうがないのかもしれない。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: LISP 1.5 PRIMER — 19.6 MACROS

Posted 2017-03-12 06:26:54 GMT

今回は、LISP 1.5 PRIMERのマクロの箇所を読む。
出版は、1967年で丁度半世紀前の本。
出版されたLISPの入門書としては最初期のものではないだろうか。もしかしたら最初の本なのかもしれない。

邦訳も1970年にLISP入門として出版されている。

自分が知る限りLISP入門以前にLISPの本は出版されていないようなので、日本語のLISP入門書はこれが初ではないだろうか。

表紙のタイトルがS式になっていて、こういう意匠もかなり初期の試みだったのではないだろうか。
なんとなく当時から括弧が好きだった人は好きだったんだなという感を抱いてしまう。

(LISP 1.5 PRIMER
  (BY
    (CLARK WEISSMAN)))

19 LIST STRUCTURES, PROPERTY LISTS, AND MACROS

面白いのが、リスト操作とマクロが一緒にされているところ。
実際リスト操作なのだが、バッククォートが発明されてからは、マクロを書くのはテンプレート操作という印象が強くなったように思う。
Schemeのsyntax-rulesに至ってはテンプレート言語だし。

さて、まず、特殊形式を実現するのにコンパイル時に展開するマクロが使えるということが解説される。
この辺りは、Timothy Hart氏がマクロをLISP 1.5に導入した経緯を踏まえているように思うが、この本では、特殊形式の役割は可変長引数と引数を評価しないこととしていて、マクロでそれらをどう処理できるのかを解説する。

なお、マクロの定義フォームは、MACROで、

MACRO (( (name (lambda (form) ....))
         (name (lambda (form) ....))  ))

のようになる。
ちなみに、defmacroでいうと&whole引数のようにフォーム全体が渡されてくる。

課題の一つである、可変長引数の処理については、コンパイル時にplusを固定引数の下請けである固定引数の*plusの組み合わせに展開してしまう手法を説明。

LISP 1.5では、マクロでの再帰的定義はできなかったようなので、展開用の*expandという関数を定義して、これがplus*plusに再帰的に展開する。
どうも展開用の関数を定義するというのは当時メジャーなマクロ書法だったようで1960年代後半のマニュアルなどにも頻繁に紹介されている。
展開用の関数がやっていることは、リスト操作の極みなので、マクロがリスト操作の章にあるのも分かる気はする。

また、もう一つの課題である、クォートの方は、csetという大域定数を定義する関数を、cset+quotecsetqにする例を取り上げる。

章末には、マクロの問題も、6つ程あるが面白いので解いてみることをお勧めする。
defmacroとバッククォートは使わない縛りだとなお面白い。

結び

日本の1970年代後半位の書籍だとマクロについては軽く触れられているのみだが、米国では1960年代からコンパイラと合せて紹介していることが多い。
日本ではコンパイラについてはあまり注目されなかったのだろうか。その辺が少し不思議。

ちなみに、1965年より前の文献になるとマクロは、「Timothy Hartのマクロ機構」という感じで、LISPにマクロを始めて導入したHart氏の名前と共に紹介されることが多いようだ。

なお、本書籍は、PDFがSoftware Preservation Groupからダウンロード可能なので一度眺めてみてもらいたい。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: Dylan Programming — 6.4 Macros

Posted 2017-03-10 15:14:00 GMT

今回は、Dylan Programmingのマクロの箇所を読む。

Dylan Programmingは、1996年の出版で、主な対象としている処理系は、Harlequin Dylan。

当初、Harlequin Dylanは、DylanWorksと呼ばれていたらしいが、1.0がリリースされる前に名称変更になったらしい。

Harlequin Dylanは、1998年辺りに正式リリースがされ、1999年にEnterprise Editionがリリースされた後、色々あって、Functional Objectsという会社が扱うことになり、さらに色々あり、Open DylanとしてOSS化された。

以降、現在に至るまで、Open Dylanとして開発が活発になったり停滞したりしている。

本書のPDFは、OpenDylan.orgからダウンロード可能

6.4 Macros

DylanのマクロはLispのように言語を拡張することを目的としていて、扱いも用意であることが解説される。
なお、Dylanの競合は、C++やCなので、Cのマクロとの比較が随時差し挟まれる。

構文マクロには、パタンマッチ・テンプレート方式と、構文を操作する手続き型マクロがあるが、Dylanの場合は、テンプレート方式であることが説明される。
後々、手続き型マクロもサポートする予定だったらしい。なんと、知らなかった。

簡単なパタンマッチでマクロを書く例が示され、次にConstraintsについて説明がある。
Constraintsとは、?foo:expressionのようなパタン変数の後ろの部分で、構文に型が付いているようなもの。
Common Lispでいう&restのようなものは、?var:bodyと書けたりする。

また、サブパタンを書くことも可能で、見通しよく分割できることが示される。
マクロ内で識別子を生成する方法も解説。

次に、マクロの衛生性について解説され、識別子の意図しない捕捉等が起きないことが示される。
衛生性は意図的に破ることが可能で、?=を利用すれば利用される場所の識別子が利用できる。

次に、サブパタンを利用しては書けない例として、生成した識別子がサブパタンでは利用できない例を挙げ、補助マクロを利用する方法を解説する。

結び

最近、中置言語にLisp風のマクロを導入する例が多いが、最近の言語と比べてもDylanのマクロシステムは洗練されているように思える。
Dylanには、Common Lispの開発者が多く参加しているが、構文マクロを普通に使う人達が設計したことがシステムの洗練に大きく寄与したのではないだろうか。

Common Lispを知っている人からすると、Dylanは、まるで中置のCommon Lispで、Common Lispの精神もかなり継承しているのであるが、ブレイクしてくれなかったのが悔まれる。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: プログラマのためのCOMMON LISPガイド — 11 マクロ

Posted 2017-03-07 18:48:22 GMT

今回は、プログラマのためのCOMMON LISPガイドのマクロの章を読む。

1988年邦訳で、原書であるA Programmer's Guide to Common Lispも1987年出版で、CLtL1な時代の本。

「プログラマのための」とあるように、何かしらの言語の経験は前提にしている。

11 マクロ

まず、マクロの効用について説明があり、次に簡単な定義の方法とバッククォートの説明がある。
次に一歩進んで、マクロを書くマクロの説明があり、ネストするバッククォートの書き方(,',の使い方等)が親切に解説されている。
そして、マクロのλリストのデストラクト分配機能について解説がある。
これら一歩進んだ機能を解説する題材として簡単なdefstructを作って行くが、defstructを作るには一通りの知識が必要となるので確かに手頃な題材だと感じた。

章末の練習問題も手頃。後半は、本章で解説した自作のdefstruct:conc-nameや、:constructorを使えるように拡張する等、実際的な問題で面白く為になる。
なお、Common Lispの仕様に忠実に:constructorをサポートするとなると結構大変だが、巻末の解答を眺めるとさすがにそこまでは要求していないらしく、名前を変更する位で良いようだ。

また、:conc-nameや、:constructorサポートするにあたって構造体名の所はリストだけのサポートで良いらしい。
デストラクト分配を使えとあるので、destructuring-bindなし(CLtL1の仕様では存在しない)でどうやるのか色々考えてしまった。
まあ、下請けのマクロを定義するだけで良いのだが……。


HTML generated by 3bmd in LispWorks 7.0.0

LispのloopやformatはUNIXでいうsedやawkである

Posted 2017-03-04 18:38:29 GMT

Common Lispのloopformatは言語内DSLの代表例と考えられていて、その機能の多さからLisperの破天荒さを象徴するものと捉える向きも多い。
しかし、これらの言語内DSLは別にLispらしさの象徴でもないし、寧ろ原理主義者からは昔から嫌われているものの代表例でもある。

formatや繰り返し構文はFortranに由来するが、やりたいことを一つの関数に詰め込んでしまったため、ごちゃごちゃすることになってしまった。

loopは、INTERLISPのCLISPというユーザーフレンドリーな機構の繰り返し機構のforが先祖で、他の言語に親しんだ人のために中置で書ける繰り返し構文をLisp内で実現したものに由来する。

Common Lispのloopformatの内情を知っていれば、Unixのsed、awkのようにシェルからは独立した処理系や、その他オプションがどんどん増える各種コマンドについての歓迎と批判が、非常に良く似た構図であることに気付くと思う。

Unix方面では、原理主義として、一つのコマンドは一つのことしかせず、それをパイプで組み合せることを徹底したPlan9文化(またはdjb氏のユーティリティ群)があり、awkのようなものがさらに発達したPerlのようなものもあり、便利なら良いじゃんということでオプションが沢山付いたGNUのユーティリティがある。

Lisp方面で言えば、Plan9はScheme文化、formatloopのようなものは、sedawkのようなもので、便利なら良いじゃんというのは、大多数のCommon Lispユーザーの立ち位置、という感じになる。 Gaucheのように全体のバランスを考えつつ便利さも失なわないように整えている環境もある。

sedawkがUnixの対話環境の象徴かといえば、そんなことはなく、パイプでコマンドを組み合せていくスタイルこそがUnixの対話環境らしさだろう。
そのような基本があった上で、便利さを追求したり統一性を重視していると思う。

Lisp方面も同じで、formatloopがLispの象徴ということはないのである。

単一の関数やコマンドに詰め込むことの批判として、徹底して小さい部品を組み合せるアプローチも多数出ている。
formatや、loopであれば、コンビネータに機能を分解して、それを組み合せる等々のアプローチがあるし、Plan9ではコマンドは本当に一つのことしかしない。
面白いのがこれらのアプローチが絶対的に使い易いかというと、そうとも言い切れない所である。細かく分割した部品が他のドメインでも上手く使えたりして一石二鳥かというと、そうでもなかったりする。

CLerは割合に現実的なので、loopformatも全機能を熟知した上で使うということもなく、適度なイディオムを利用しているのみである。
また、設計に於いても極力機能をアトミックに分割しようということも無く中庸といった所だ。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: Anatomy of LISP — 3.12 Special Forms and Macros

Posted 2017-03-04 17:35:50 GMT

今回は、Anatomy of LISP / John Allenのマクロの箇所を読む。

LISPの構造として邦訳もある。
邦訳は入手できていないので詳細は不明だが、どうも邦訳の方は端折っている部分が多いらしい。

原著の初版は1978年で、LISP処理系の内部を解説した名著として知られている。
マクロについては、3章のEVALUATION OF LISP EXPRESSIONSの評価器から眺めた解説と、6 6章のTHE DYNAMIC STRUCTURE OF LISPのコンパイラから眺めた解説がある。

3.12 Special Forms and Macros

eval(インタプリタ)から眺めた特殊形式とマクロの解説。
andを実現するのに、まず、関数ではandが作れないことを解説し、evalを拡張する方法を示す(evandを作成)。
次に、同様のことを実現するのにマクロというものを評価器に導入することを説明。

また、可変長引数を実現するのにマクロを使うという手法を解説する。
可変長引数のplusは固定引数(2つ)の*plusに展開されるというもので、マクロそのものを用いてはいないが、現在もCommon Lisp内部ではコンパイラがインライン展開etc.で行なっていることと同じ。

マクロの種類として

  1. Textual Substitusion
  2. Syntax Macro
  3. Computational Macro

三つを取り上げ、単なる文字列の置き換え→構文の置換→サブツリーの変換と強力になることを説明するが、Computational Macroであっても扱うサブツリーに必要な情報が含まれている場合にしか有効でなく、サブツリー以外の場所の情報を必要とする場合や、サブツリーに追加情報が必要な場合にも、うまく立ち回ることができていないことを解説する。

6.18 Macros and Special Forms

コンパイラの方からマクロを眺めた解説。
大体インタプリタの方と同じだが、コンパイラからすると扱いが厄介な特殊形式を排除できることと、マクロはコンパイル時に計算してしまえる部分を多くすることが可能で効率的な処理に寄与することが解説されている。
また、特殊形式が増えるとevalが複雑で重くなるという解説もある。

結び

古代のLISPでは、マクロを説明する際には、FEXPRであったり特殊形式との比較が出てくることが多い。

マクロには、処理系を改造しなければいけない所をユーザーレベルで拡張できるところ増やす、という効用があった。
また、コンパイル時計算との親和性も高く、FEXPRで実現できることも大抵置き換えることが可能なので、FEXPRのようなコンパイルが難しい(と当時は考えられていた)形式も置き換えることとなった。

処理系内部をユーザーに開放しつつ効率が良い道具を提供するというのもLISP処理系の一つの核となる歴史の流れかなあと思う(マクロ、MOP、etc.)


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: Realm of Racket — ).3 Racket Is a Metaprogramming Language

Posted 2017-03-03 20:27:07 GMT

今回は、Realm of Racketのマクロの箇所を読む。
Realm of RacketはLand of LispのRacket版のような感じで、Conrad Barski氏も著者の一人。
とはいえLand of Lisp程個性が強くないので物足りない人もいるかもしれない。

マクロの解説は、最後の章(Good-Bye)で

  • ).1 Run Racket Run
  • ).2 Racket Is a Programming Language
  • ).3 Racket Is a Metaprogramming Language
  • ).4 Racket Is a Programming-Language Programming Language

という風にメタプログラミング度を増して解説していくうちの3番目にあたる。

).3 Racket Is a Metaprogramming Language

Racketは他のLisp方言と比べても一番強力かつ表現力が高いという前置きがある。
とはいえ、この章ではそこまで詳しくは解説せず、言語の構文を拡張する方法として簡単なマクロを解説するのみ。
主にRacketで用いられるdefine-syntaxsyntax-rulesを合体したような、define-syntax-ruleを利用するが、それ程つっこんだ解説はしない。

(define-syntax-rule
  (my-and a b)
  (if a b #f))

衛生マクロなので変数捕捉等については特に気にする必要はないが、一応わざと大域変数とマクロ内で利用している変数の名前を衝突させてみたりしても上手く動きますねという解説がある。

次に、パタンマッチの書式の簡単な解説として、lambdaに展開されるletを作成しつつ説明する位で終了。

).4 Racket Is a Programming-Language Programming Language

マクロの応用として、DSLを作ってみせる内容だが、Racketのモジュールと#langの仕組みを使ってデバッグプリント付きの言語から、Racketに遅延評価を導入してみせたりする。

遅延評価の導入については、Racketで関数適用は、%#appを利用していて、これを差し換えることによって実現する。

この機構のお蔭でevalに手軽にフックを掛けるようなことができ、マクロともリーダーマクロとも違う第三の拡張方法という感じ。

ちなみに、evalにフックを掛ける機構は、Common Lisp系でも*evalhook**applyhook*というものが存在したが、インタプリタ動作とコンパイラ動作の整合性向上のためにANSI Common Lispでは廃止になっている。

結び

Racketが強力というのは良く耳にはするが、あまり触ったことはなかった。
メタプログラミングについては、確かにかなり洗練されていて強力そうだなと感じたので、もうちょっと遊んだりしてみたい。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: Loving Common Lisp — Defining Common Lisp Macros

Posted 2017-03-01 11:48:36 GMT

今回は、Loving Common Lisp, or the Savvy Programmer's Secret Weaponのマクロの章を読む。
2013年に第一版、2016年に第三版が出ている電子書籍。
143頁でKindle版が現在¥583。

最近の書籍だけにQuicklispの説明などもある。

著者のMark Watson氏は、三十余年年のプログラマ人生の中でLispを始め様々な言語を使ってきたが、LispではNLP自然言語処理や、VR仮想現実に取り組んできたとのこと。

1991年にCommon LispでNNニューラルネットワークNLP自然言語処理や、カオス理論を学ぶ本も出したりしている。

但し、1991年という年からも分かるように、時代が一周しているので注意。

Defining Common Lisp Macros

マクロについてはさらっと流していて、説明する内容も、Lispでは構文が拡張できることと、バッククォートの簡単な説明位で、あとは展開形の確認の仕方ということで、macroexpandの説明位。

結び

本書は、最近のCommon Lispについてざっと説明するような内容で、各項目も手頃な分量で書いてある。
最近のCommon Lisp全体を俯瞰するような目的には良いかもしれない。


HTML generated by 3bmd in LispWorks 7.0.0

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

Older entries (2093 remaining)