#:g1: Lispのリーダーマクロとはなにか

Posted 2012-11-30 23:58:00 GMT

無謀にも始まりました Lisp Reader Macro Advent Calendar 2012
私一人でもくもくと遂行する予定でしたが、なんと全員で7名の参加者が集まりました。ほとんど奇跡ではないでしょうか。

Lispのリーダーマクロとはなにか

一般にLispでは、readとevalは別々になっていて、readが評価する式を組み立てて、evalはその式を評価という流れになっています。このread時での式の組み立てを、Lisp自身で色々プログラミングできることが多いのですが、その中の定番機能の一つに、ある文字をある式に展開させるというものがあります。これが、マクロ文字と呼ばれるものです。一番簡単な例でいうと、Lispの『'』があります。これは、'(a b c)と書くと、(quote (a b c))と展開されるものですが、クォート文字の後の一つのXという式が、(quote x)という風に展開されるものです。昔からquoteは多用されていますが、quoteそのままだと読み書きが面倒なためか、最も早く実現されたリーダーマクロの一つではないかと思われます。

他の言語での類似機能とLispのリーダーマクロの比較と簡単な説明

他の言語で似たような機能にはどういうものがあるか調べてみたのですがあたりが近いものかなと思われました。
プリプロセッサとの比較
まず、プリプロセッサが一番身近な物かと思いますが、手順としては、ソースをプリプロセッサに掛けて言語が直に読める形式に変換し、言語処理系が処理、という流れが一般的かと思います。これと比較すると、Lispのリーダーマクロの場合は、一括で処理するフィルターのようなこともできなくはありませんが、前述のように、ある文字を式に展開させる規則という感じで、文字列→文字列というよりは、文字→式の変換規則の集合という感じになります。この変換規則の集合がリードテーブルと呼ばれるもので、リードテーブルを変更することによって標準とは別の構文に切り換えて読むことを可能にします。
Perlのソースフィルターとの比較
Perlのソースフィルターと比較してみると、ソースフィルターは文字列を変換する道具としてPerl自身が使えるようです。文字列→文字列の変換であるようですが、変換処理に言語処理系自身を使う(える)というのは近いように思えました。さらにPerlでも文字→式を指向するものには、Devel::Declareというものがあるようです。
Template Haskellとの比較
次にTemplate Haskellですが、こちらは、[リーダー(quoter)|文字列...|]という形式でパーザを指定しつつインラインに別の構文を記述することができる、というものですが、この[||]というところがリーダーマクロ的な感じです。言語のポリシーが違うので、Lisp一般の場合には、型等は絡んでこず、またリード時ではなく、Haskellはコンパイル時ということですが、[||]自体はリーダーマクロ的で、Lisp的には[||]は、リーダーを指定しつつ文字列から式を組み立てるリーダーマクロと見做せます。
リーダーマクロの特徴のまとめ
ということでLispのリーダーマクロの特徴として言えるのは、位のようです。ちなみに、Common Lispでは、リード時にコンパイラを呼んだり、マクロ展開したり、通常の機能はすべて使えますが、大抵間違った使い方なので変なことはしないのが作法かなと思います。

簡単な仕組みの解説

Common Lispを例に説明しますが、大抵のLisp方言は大体同じ仕組みのようです。readが文字列(文字ストリーム)から式を構築しますが、展開規則が設定された文字に出会うと、式を展開します。この変換規則は関数として記述されていて、文字と展開関数の対応は、リードテーブルという変換表に格納されています。大抵リードテーブルは、ユーザーが切り換えることが可能で、リードテーブルが切り換えられると展開規則が変化しますので異なったものを展開することが可能になります。readがリードテーブルを参照する方法は、様々ですが、Common Lispのように、readが標準で参照する変数に束縛されていて、変更する場合は、これを操作する、という方式が多いようです。

二種類のマクロ文字

Common Lisp方式のマクロ文字には、大別すると二種類あります。

単一のマクロ文字 (Macro Character)

単一のマクロ文字は、前述の『'』→quoteの展開のように、一つの文字が展開されるものです。

ディスパッチ・マクロ文字 (Dispatch Macro Character)

ディスパッチ・マクロ文字は、ある一文字が開始の役割をし、残りの文字で機能を変える(ディスパッチする)というマクロ文字です。実例でいうと、Common Lispでは、『#'』→functionのようなもので、#以降の文字で機能を切り換えます。Schemeでは、リーダーマクロというもの自体が規定されていませんので、このような区別はありませんが、#'や、#(の利用ように、MacLISP系の伝統をそれなりに受け継いでいると思われます。

リーダーマクロの歴史

今回は折角なのでリーダーマクロの歴史を調べてみたのですが、実際調べてみるとリーダーマクロは誰が始めたものなのかはっきりした記録はないようでした。とりあえず、文字→式の展開で一番最初に導入されたのは、『'』→quoteであるのは、まず確かなようです。しかし、最初に導入されたのが、どの処理系かは分かりませんでした。自分がみつけた一番古い資料は、MacLISPのLISP NEWSに記載されていたもので、1969/7/6のものです。
7/6/69 22:16  JONL

LISP 105 HAS THE LONG AWAITED NEW READER, WHOSE MOST NOTABLE FEATURE IS CHARACTER-BY-CHARACTER RUBOUT FROM THE JOB CONSOLE. RUBOUTS AND NON-SLASHIFIED CONTROL CHARACTERS ARE IGNORED ON INPUT FROM TAPE OR DISK; SLASHIFIED CONTROL CHARACTERS TYPED ON THE TTY HAVE NO CONTROL EFFECT [MAYBE!!!]. A LIMITED PROGRAMMABLE SYNTAX FEATURE IS AVAILABLE FOR READ, TO BE DESCRIBED IN A FORTHCOMING COMPREHENSIVE DOCUMENT ON LISP IN GENERAL, AND PDP-6/10 LISP IN PARTICULAR. ALL SYNTAX PROPERTIES ARE UNDISTURBED IN THE STANDARD LISP, EXCEPT THE CHARACTER <SINGLE-QUOTE> WHICH HAS THE FOLLOWING FUNCTION: 'SEXP IN THE INPUT STRING, IS EQUIVALENT TO THE APPEARANCE OF (QUOTE SEXP) FOR ALL S-EXPRESSIONS SEXP. SUCH FUNCTION IS AN INSTANCE OF A MUCH MORE GENERAL SYNTAX ABILITY, FOR THE PRESENT CALLED "MACRO CHARACTERS". NOTE ALSO THAT THE OF-LATE-INFAMOUS SYNTAX PROPERTIES OF THE CHARACTERS + AND - ARE RESCINDED UNLESS THE USER RE-INSTATES THEM BY MEANS OF THE FUNCTION "SSTATUS" (SEE BELOW). THE USER MAY ALSO RESCIND THE MACRO PROPERTY OF <SINGLE-QUOTE> DESCRIBED ABOVE, OR DEFINE SIMILAR PROPERTIES FOR OTHER CHARACTERS.

このSTANDARD LISPが指しているものが実際の方言のStandard Lispなのか、この処理系の標準なのか判然としないのですが、他の方言への導入も1969年以降あたりから始まっているようで、1975年位までには各方言でも当り前のように使われている様子。ちなみに、『'』→quoteの変換自体は、1964年のMichael Levin. Syntax of the New Language. MAC-M-158に登場します。これは、Lisp2という中置記法のLispの為のもので、Lisp2は、Lisp登場当初のM式とS式の関係のように、基本的に中置記法で書き、それをS式に変換する、という設計思想のものでした。このような時代背景を考えると、60年代には、まだまだ記法自体がS式に確定しておらず、交換可能なものにしておきたいという需要もあったのかもしれません。MacLISPでは、1970年には、ユーザーがマクロ文字を設定できるようになっており、1972年にはユーザーがリードテーブルを切り換えることができたようです
06/09/72
...
5) THE READER'S SYNTAX TABLE IS NOW AN ARRAY, WHOSE SPECIAL ARRAY CELL
IS POINTED TO BY THE VALUE OF THE ATOM READTABLE.  THUS ONE CAN 
QUICKLY SWITCH BACK AND FORTH BETWEEN SEVERAL SYNTAX OPTIONS BY 
MEANS OF LAMBDA BINDNG AND SETQ.  AS WITH THE OBARRAY FEATURE, THERE 
IS A NEW FUNCTION TO CREATE A COPY OF THE CURRENT READ SYNTAX TABLE 
AS AN ORDINARY LISP ARRAY.  E.G.,
	(MAKREADTABLE 'LISP1/.5)	;(MAKREADTABLE NIL) WOULD 
					;GENSYM UP AN ARRAY
	((LAMBDA (READTABLE)		;WE WANT THE LISP1/.5 
		 (SSTATUS MACRO /; NIL) ;TABLE TO HAVE NONE OF THE
		 (SSTATUS MACRO /' NIL));MACLISP SYNTAX OPTIONS
	   (GET 'LISP1/.5 'ARRAY))
次に、70年代後半には、ディスパッチ・マクロ文字が考案されたようです。これもはっきりとした資料がなかったため憶測でしかないですが、恐らく、1978年あたりのMIT Lispマシングループから出てきたようです。まずは、機種依存の式を選択するものとして導入され(#M、#N、#Qのようなもの)、その後、1979年辺りには、これを面白いと思ったユーザーがどんどん拡張して、各方言でマクロ文字の取り合いのようなことも発生しています(#.は、○○では、もう××機能として使っているので新しく導入するなら、#=とかにしてくれ、等々)。この辺りで現在のCommon Lispに見られるような機能は一通り出揃い、80年代前半頃までには、やり過ぎ機能まで登場するに至ります。

まとめ

ということで、一通りリーダーマクロの機能と歴史を紹介してみました。明日からは、Common Lisp・Racket・Clojure・Scheme・Arc・Xyzzy Lisp等々の各方言でのリーダーマクロの機能や解説記事が続く予定です。明日は、@garaemonさんの回です。

comments powered by Disqus