2010年5月18日火曜日

リーダマクロでlambdaを短縮する

Clojureでは無名関数を作るために用いるのは、lambdaでは無くfnという特殊式なため Common Lispより3文字短い。3文字程度なら良いのだけど、Clojureにはさらに無名関数のためのリーダマクロが用意されている。

;;この式が
#(list %1 %2)

;;こうなる(イメージ)
(fn [%1 %2] (list %1 %2))

Clojureに心引かれる箇所は色々あるけれど、このリーダマクロなら多少は自分で書いてみることができるのでは無いかと思ったので、試しに書いてみた。

展開されるようにした。また、引数は%nではなく$nで表現した。

(defun dollar-symbol-p (sym)
(and (symbolp sym) (char= #\$ (char (symbol-name sym) 0))))

(defun dollar-symbol-index (sym)
(and (dollar-symbol-p sym)
(parse-integer
(symbol-name sym)
:start 1 :junk-allowed t)))

(defun short-lambda-reader (stream ch1 ch2)
(declare (ignore ch1 ch2))
(let* ((body (read-delimited-list #\} stream t))
(dollars (remove-if-not #'dollar-symbol-p (my:flatten body)))
(rest-p (find "$R" dollars :test #'string= :key #'symbol-name))
(largest
(apply #'max (or (remove nil (mapcar #'dollar-symbol-index dollars))
'(0)))))
(let ((args (loop :for i from 1 to largest
:collect (my:symb "$" i))))
`(lambda ,(if rest-p
`(,@args &rest ,rest-p)
`(,@args))
,@body))))

(defun enable-short-lambda-reader ()
(set-macro-character #\} (get-macro-character #\)))
(set-dispatch-macro-character #\# #\{ #'short-lambda-reader))

(enable-short-lambda-reader)

;;example
;;$1が第1引数を表す。$rは$nの中で最大のもの以降の残りの要素に束縛される。
(#{(list $1 $2 $4 $r)} 1 2 3 4 5 6)
=> (1 2 4 (5 6))

'#{(list $1 $2 $4 $r)}
=> (LAMBDA ($1 $2 $3 $4 &REST $R) (LIST $1 $2 $4 $R))

(remove-if #{(member $1 '(a b c))} '(a b c d e f g))
=>(D E F G)

(#{(mapcar #{(print $1) (mod $1 2)} (remove-if-not #'numberp $1))}
'(a b 1 2 3 4 f "hoge"))
1
2
3
4
=>(1 0 1 0)

うーん、結構見づらいような。 1つの式のみ書けるようにして、#{list $1}としたほうが見やすいかもしれない。

0 件のコメント:

コメントを投稿