nlet-acc
良く使うユーティリティ関数・マクロの定義などを書くことにする。*1
一つ目はこれ。
【定義】
(defmacro nlet-acc (fn-name letargs &body body) (let (acc (gensym)) `(let ((,acc '())) ; acc: 結果蓄積用のlist ※1 (flet ((accumulate (x) (push x ,acc))) ; accmulate: 結果(x)を蓄積する関数 (nlet ,fn-name ,letargs ,@body)) (nreverse ,acc)))) ; accは逆順に格納されているので、nreverse ;; ※1 $$などのような名前をつけて、スコープ内でアクセスできるようにするか検討中
【説明】
局所的な名前付き関数(fn-name)を定義・実行するマクロ。
fn-name関数のlexicalスコープ内では、accumulateという一引数関数が使える。
accumulateに渡した値は(FIFO順で)蓄積され、これがnlet-acc式の結果として返される。
letargsは、fn-name関数の引数リストで、「変数名|\(変数名 初期値\)*」形式のlist。
初期値が省略された場合は、nilが値となる。
【使用例】
;; mapcarの実装(簡易版) (defun my-mapcar (fn list) (nlet-acc self ((list list)) (unless (endp list) (accumulate (funcall fn (car list))) (self (cdr list)))))
基本的にmap系の関数を一般化*2したような使い方を意識している。
【依存定義】
以下、定義に必要な関数・マクロ。
出典は『Let Over Lambda』だったと思うが、はっきりは覚えていない。今度読む時に要確認。 ※ 追記: nletは同書のp.45にあった。
;; 引数をlet形式に正規化 ;; ex. (a b (c)) -> ((a) (b) (c)) ;; こうすると、変数名を(car arg)で、値を(cdr arg)で取得できるようになる。※ letの値のデフォルトはnil (defun formalize-letargs (args) (mapcar (lambda (a) (if (atom a) (list a) a)) args)) ;; lambdaに、名前をつける機能(=自分の再帰呼び出し)を追加したようなマクロ。 ;; (funcall (lambda (a b c) ...) 1 2 3) ==> (nlet ((a 1) (b 2) (c 3)) ...) (defmacro nlet (fn-name letargs &body body) (setf letargs (formalize-letargs letargs)) `(labels ((,fn-name ,(mapcar #'car letargs) ,@body)) (,fn-name ,@(mapcar #'cadr letargs))))
僕の場合は、以下のようなnlet定義が多かったので、nlet-accを追加することにした。
(let ((acc '())) (nlet self (a b c) ... (push ... acc)) (nreverse acc))