この記事は、lispリーダーマクロアドベントカレンダー の4日目の記事です。 タイトルにある通り、Clojure でのリー ダーマクロについて取り扱います(対象とする Clojure のバージョンは 1.4)。
リーダーマクロの定義等については、このアドベントカレンダー初日の記事 #:g1: Lispのリーダーマクロとはなにか に詳しく書かれておりますのでそちらをご参照願います。ここでは Clojure でのリーダーマクロということなので、他の Lisp処理系との違いがなるべく 伝わるように書いていきたいと思います。なるべく網羅的に書こうと思います ので、どちらかといえば Clojure 初級者向き(というか知らない人向け)の記 事にしてみました。
コメントは普通の Lisp 処理系と同じく ; (セミコロン)から行末まで、にな
ります。
また、comment という常に nil を返すマクロがあるので、適当なコードの塊
をまとめてコメントアウトするときには便利かもしれません。
(comment
(defun hoge [] (fuga))
)
;=> nil同様の機能として、後述するディスパッチマクロの中に Discard マクロとい
うものがあります。これは、form の前に #_ をつけることで、form を「丸
ごと無視」するという働きをします。
#_(defun hoge [] (fuga))comment マクロよりも#_のほうが実用的ではないかと思います。
ただ、comment マクロも #_ マクロも、form が reader でちゃんと読め
る(エラーにならない)ことが前提です。カッコがアンバランスな文字列をコメン
トに入れたい、とかいう場合には、素直に ; でコメントアウトしましょう。
(comment (this is a) comment) ; => nil
(comment this is a) comment) ; => CompilerException java.lang.RuntimeException: ...
#_( this is a) ) ; => RuntimeException Unmatched delimiter: ) ...
; this is a) comment ; => これはエラーにならないあと、#! から行末までもコメントとして認識されます(スクリプトを意識し
ているのかもしれません)。
多くのプログラミング言語と同様、 文字列 の始まりを表します。ダブル クオートで囲まれた範囲が文字列となります。
これも多くのプログラミング言語と同様、\ の直後にある文字そのものを表
します。
\a ; => a という文字そのもの
(class \a) ; => java.lang.Character (java版Clojureの場合です、もちろん)Lisper ならおなじみの quote、これは普通の Lisp 処理系と同じく '
(シングルクオート)です。直後の form の eval を抑制します。
'a ; => (quote a)いわゆる Lisp でいうところの List は、( ) で表現されます。フツーで
すね。
(1 2 3)
(1 (2 (3 (4 (5 6)))))Clojure には、vector、map、set、という、リスト以外にもコレクションを取 り扱う型が標準で用意されており、またリーダーが適切に取り扱えるようマク ロ文字が定義されております。
いわゆる配列です。[ ] で表現されます。
[1 2 :a]Clojure では , は空白文字と同義ですので、
(= [1 2 :a] [1, 2, :a]) ; => trueです。マクロ文字 [ ] を使わずに書こうとすると、vec関数を使って、
(vec (list 1 2 :a))のような感じになります。
キーと値を対応付ける、Javaでいうところの HashMap 相当のコレクションで
す。{ } で表現されます。
{:a 1 :b 2 :c 3}
;; カンマを入れると区切りが目立つ
{:a 1, :b 2, :c 3}これもマクロ文字{ } を使わずに書こうとすると、hash-map関数を使って、
(hash-map :a 1 :b 2 :c 3) ; => {:a 1, :b 2, :c 3}のような感じになります。
Javaでいうところの HashSet のようなものです。重複を許さない値の集合、
といったところでしょうか。#{ } で表現されます。
#{:a :b 3} ; => #{:a :b 3} ; 順番は意識しないこれもマクロ文字#{ } を使わずに書こうとすると、hash-set関数を使って、
(hash-set :a :b 3) ; => #{3 :a :b}のような感じになります。[]、{}、#{}、簡単に書けていいですね。
#につづけて文字列を書くと、その文字列は正規表現リテラルとして扱われ
ます。正規表現パターンは、re-pattern 関数で作成できますが、いろいろ
エスケープする必要がある場合が多いので、#"" で書くと簡潔に記述するこ
とができます。
(re-seq (re-pattern "\\(list a b c\\)") "(list a b c)") ; => ("(list a b c)")
(re-seq #"\(list a b c\)" "(list a b c)") ; => ("(list a b c)")上はどちらも同じパターンマッチをさせていますが、#" を使った方が \
が少なくすっきり見えます。
Clojure の特徴として並行処理のサポートがありますが、その機能の一つとし
て Ref というものがあります。
(def ref-sample (ref 0))
ref-sample ; => #<Ref@737c2891: 0>この ref の実体を取得するには @varname という書式を使用します。
@ref-sample ; => 0実は @ はただのマクロ文字で、(deref varname) のように展開されます。
(※実際には「展開されたような動きをする」です)
(deref ref-sample) ; => 0この他にも、Clojure には form の 評価を遅らせる ためのマクロとして
delay というものがあり、遅らせたその評価を強制的に実行させる force
という関数があるのですが、deref は force の代わりとしても使えるの
で、delay に対しても @ 構文で値を参照することができます。
- 普通に
forceを使う例
(def foo (delay 0))
foo ; => #<Delay@4b85c17: :pending>
(force foo) ; => 0
foo ; => #<Delay@4b85c17: 0> 状態が変わった!@でも同じことができます。
(def bar (delay 0))
bar ; => #<Delay@47ef7de4: :pending>
@bar ; => 0
bar ; => #<Delay@47ef7de4: 0> 状態が変わった!Clojure では、変数や関数の実体への参照として var というものがあります。
defやdefnで定義するものは、変数や関数そのものと、それを参照する
var になります。
Clojure では、var に対して メタデータ を付けることが出来ます。
メタデータは、コンパイル時に参照される型情報だったり、関数や変数定義に 対するドキュメントだったりします。以下に少し例を示します。
(def ^:private foo "private var") ; :private true というメタデータを追加
(def ^{:private true} foo "private var") ; 上と全く同じメタデータは meta 関数で参照できます。
(meta foo) ; => nilただし、上記のように普通に var に対して呼び出してしまうと、nil になっ
てしまいます。上記の書き方では foo というシンボルが eval された結果
が meta 関数に渡されるので、せっかく foo という var に付けたメタデー
タを参照することができません。そこで、引数 form の var そのものを返す
var という特殊形式に登場してもらいます。
(meta (var foo)) ; => {:ns #<Namespace user>, :name foo, :private true, :line 103, :file "NO_SOURCE_PATH"}こんどはちゃんと foo のメタデータを参照できました。ところで、これには
もう一つの書き方があります。それが次に示すリーダーマクロ #' です。
#'foo と書くと、(var foo) のように取り扱いされます。すなわち、foo
のメタデータを取得するには、
(meta #'foo) ; => {:ns #<Namespace user>, :name foo, :private true, :line 103, :file "NO_SOURCE_PATH"}のように記述すればよいわけです。すっきり書けますね。var 特殊形式につ
いては、 @atos0220さん の記事 Lisp Advent Calendar 2012 1日目
の先頭あたりに詳しく書かれています。
あと、メタデータは関数のパラメータの型ヒントとしても指定できます。
;; 関数の例(引数の型 hintに ^ を使用している例)
(defn- foo [^long i ^long j] (+ i j))この foo のメタデータを meta関数で表示させても、引数の型ヒントのと
ころは残念ながらパッと見はわかりません。
(meta #'foo) ; {:arglists ([i j]), :ns #<Namespace user>, :name foo, :private true, :line 140, :file "NO_SOURCE_PATH"}こういう場合は、 id:t2ruさん のこのページ
にある ppm という関数(メタデータを含めたPretty Print)で表示させてみると
(ppm #'foo)
^{:arglists ([^{:tag long}i ^{:tag long}j]),
:ns #<Namespace user>,
:name foo,
:private true,
:line 140,
:file "NO_SOURCE_PATH"}#<Var@3315a56d: #<user$foo user$foo@56459b78>>
nilのように表示されるので、ちゃんとメタデータとして認識されているようです。
あと、メタデータを付けるためのリーダーマクロとして #^ というのが使え
るようです(が、^との使い分けがよくわかりませんでした)。
` (バッククオート) は、Lisp で言うところのシンタックスクオートと呼ば れるものです。マクロを定義する際によく使いますが、マクロ定義時以外でも 使う場合もあります。
まず、list、vector、map、set、のリテラルについては、そのまま展開します。
`[1 2 3 4] ; => [1 2 3 4]
`(1 2 3 4) ; => (1 2 3 4)
`{1 2 3 4} ; => {1 2, 3 4}
`#{1 2 3 4} ; => #{1 2 3 4}シンボルについては、その名前空間を補完します。また、シンボル名の終わり
に # をつけると、auto-gensym(自動的にユニークなシンボル名をもつシン
ボルを作成)します。
`String ; => java.lang.String
`foo ; => user/foo
`foo# ; => foo__130__auto__
`(+ x y#) ; (clojure.core/+ user/x y__133__auto__)マクロを定義する際には、シンタックスクオートされているコンテキストの中
で、より外側のコンテキストのシンボルを参照したくなりますが、そういった
場合には ~ を使ってアンクオートします。
`(let [x 1] `(+ ~x y#) ; => (clojure.core/+ 1 y__146__auto__)最後に、シンタックスクオートされた中のコンテキストに、リストの塊ではな
くバラけた形で渡したい場合は、@~ を使います。日本語では説明しづらい
ので例を下に示します。
(let [x '(1 2)] `(+ ~x)) ; => (clojure.core/+ (1 2)) 動かない!
(let [x '(1 2)] `(+ ~@x)) ; => (clojure.core/+ 1 2)x の中身は (1 2) というリストなので、~x はそのままだと (1 2) に展
開されますが、~@ を使うことで、複数のパラメータにバラけた形で展開さ
れたのがわかります。
Clojure では無名関数を定義する特殊形式はfnとなります(実は局所的な名
前も付けれますが)。無名関数は、関数型言語ではしょっちゅう使うので、簡
潔に記述できればありがたいものです。
(map (fn [x y] (+ x y)) '(1 2 3 4 5) '(6 7 8 9 10)) ; => (7 9 11 13 15)上の例を、#() と % を使うと、次のように書けます。
(map #(+ % %2) '(1 2 3 4 5) '(6 7 8 9 10)) ; => (7 9 11 13 15)2番目の例では、map に渡す各要素毎に適用する関数を無名関数 #() で表し、 そのコンテキストの中での引数を、先頭から %、%2、 %3、... というように 参照しています。
ただし、#() はネストできないのでその点だけは要注意です(エラーが出るの で簡単にわかりますが...)。
これは #= に続く form を評価します。
#=(+ 1 2 3) ; => 6ただし、変数 *read-eval* が false になると、上記式はエラーになります。
(read-string "#=(+ 1 2 3)") ; => 6
(binding [*read-eval* false] (read-string "#=(+ 1 2 3)")) ; => RuntimeException EvalReader not allowed when *read-eval* is false. ...この文字が来るとリーダーは例外を Throw します。
#< ; => RuntimeException Unreadable form ...Java版 Clojure は Java VM 上で稼働する言語であり、Clojure の reader は Javaで書かれています(少なくとも現時点では)。Clojure の reader 機能を実 現するJavaのクラスは、clojure.lang.LispReader となっていて、ソースは ここ にあります。
LispReader クラスにある read メソッドが reader 本体ですが、その中で
いくつかの記号については各々の表記法に応じた ほげReader というクラス
(LispReader の inner クラス)に処理させるよう、記号と処理クラスの対応表
(ソースでは macros という static配列)があります。
macros['"'] = new StringReader();
macros[';'] = new CommentReader();
macros['\''] = new WrappingReader(QUOTE);
macros['@'] = new WrappingReader(DEREF);//new DerefReader();
macros['^'] = new MetaReader();
macros['`'] = new SyntaxQuoteReader();
macros['~'] = new UnquoteReader();
macros['('] = new ListReader();
macros[')'] = new UnmatchedDelimiterReader();
macros['['] = new VectorReader();
macros[']'] = new UnmatchedDelimiterReader();
macros['{'] = new MapReader();
macros['}'] = new UnmatchedDelimiterReader();
// macros['|'] = new ArgVectorReader();
macros['\\'] = new CharacterReader();
macros['%'] = new ArgReader();
macros['#'] = new DispatchReader();単一のマクロ文字については、上記 macros 配列を見て、各々に対応する
ほげReader() を見ていけばよさそうです。
なお、ソースコード上の '|' については未調査です(コメントアウトされてい る以上機能していないのは良いのですが、なぜコメントアウトされたのか、と かどういう経緯で云々については調べてません)。ご存知の方がいらっしゃい ましたら教えていただけると助かります。
これまで見てきた Clojure のリーダーマクロのうち、# で始まるものにつ
いては、ディスパッチマクロとして dispatchMacros という配列で処理が分岐
する仕組みになっています。
dispatchMacros['^'] = new MetaReader();
dispatchMacros['\''] = new VarReader();
dispatchMacros['"'] = new RegexReader();
dispatchMacros['('] = new FnReader();
dispatchMacros['{'] = new SetReader();
dispatchMacros['='] = new EvalReader();
dispatchMacros['!'] = new CommentReader();
dispatchMacros['<'] = new UnreadableReader();
dispatchMacros['_'] = new DiscardReader();Clojure 1.4 より前は特殊なことをしない限りマクロ文字をいじることができ ませんでした。逆引きClojure のこのページ や、id:nokturnalmortumさんのこのページ にサンプルがありますが、どちらも LispReader.java の dispatchMacros を 無理やり書き換えることで実現しています。
ところが、Clojure 1.4 からは、標準の機能としてディスパッチマクロを独自
に定義できるようになりました。具体的には、ルートのクラスパスに
data_readers.clj というファイルを置き、その中に # に続く文字列とそ
れに対応する関数を記述し、その関数の実体を別途実装する、というやり方で
す。
これに早速トライされた @uochanさんのページ には、
- #upper 文字列を大文字にする
- #?= デバッグプリント
- #str 文字列中にあるS式を展開する
といった、簡潔で理解しやすい実例が載っておりますので参考になります。 (あともうひとかた、サンプル実装されていたはずなのですが、ちょっと見つ けられませんでした)
今回、リーダーマクロに焦点を当てて、わりと初心者向けに解説を試みてみま した。この文書が Clojure コミュニティを少しでも広げることに役立つこと ができれば幸いです。
明日は sirohuku さんの話です。
参考に読ませて頂きました。
細かいですが1箇所だけ誤植を見つけたのでお知らせします。
Unquote-splicing の
~@を@~と誤記している箇所があるようです。