Common lisp でパイプ
シェルのパイプ機能ってとても便利だ。ruby などでメソッドをドットで繋げていくのも楽しい。ということで Common Lisp にてマクロでやってみた。
最初の引数が初期値で、その後ろに関数の名前(lambda 式でもいい)をずらずらと書いていく。キーワード :collect は多値をリストに変換する。関数適用はデフォルトで funcall が使われていて、キーワード :apply を指定すると次だけ apply が代わりに使われる。途中の値を表示したかったら :tee を挟む。
バグと呼ぶのが適切かどうかは分からないけれど、途中に返り値なしの関数が出てきた場合、次の関数には nil が渡されるようになっている。返り値なしのまま :tee を通ったときにも同じことが起きる。
(defmacro pipe (val &rest rest) (do ((res val) (elt (car rest) (car r)) (r (cdr rest) (cdr r))) () (cond ((eq elt :collect) (setq res (list 'multiple-value-list res))) ((eq elt :tee) (setq res (tee-form res))) ((eq elt :apply) (setq res (list 'apply `#',(car r) res)) (setq r (cdr r))) ((fname-p elt) (setq res (list 'funcall `#',elt res))) (t (error "Illegal argument: ~S" elt))) (if (null r) (return res)))) (defun tee-form (form) `(let ((lst (multiple-value-list ,form))) (cond ((and (consp lst) (null (cdr lst))) (format t "~A~%" (car lst)) (car lst)) ((cdr lst) (dolist (elt lst) (format t "~A~%" elt)) (apply #'values lst)) (t (format t "~A~%" "; No value") (values))))) (defun fname-p (elt) (or (and (symbolp elt) (fboundp elt)) (and (consp elt) (eq 'lambda (car elt)))))
使用例:
CL-USER> (pipe 1 1+ / :tee list) 1/2 (1/2) CL-USER> (pipe (values 1 2 3) :tee :collect :apply (lambda (x y z) (+ x (* 3 y) z))) 1 2 3 10