我想写一个函数/宏
(defun apply-funcs (functions value) ...)
这样调用
(apply-funcs (list #'f #'g #'h) x)
就相当于 (h (g (f x)))
。如何才能做到这一点?
(defun apply-funcs (functions value)
(loop for f in functions
for result = (funcall f value) then (funcall f result)
finally (return result)))
看起来您想要
reduce
一个值上的函数列表。
CL-USER> (defun apply-funcs (functions value)
(reduce (lambda (memo fn) (funcall fn memo))
functions :initial-value value))
CL-USER> (apply-funcs
(list (lambda (n) (+ 3 n))
(lambda (n) (- n 2))
(lambda (n) (* 2 n)))
6)
14
CL-USER>
您可能将其他语言中的
reduce
理解为 fold
。我使用 funcall
而不是 apply
因为你在上面说过你想要的((apply-funcs (list #'f #'g #'h) x) => (h (g (f x)))
)。如果 apply
是值列表,并且您希望将其中的每个元素绑定到单独的参数,则可以使用 x
。例如,如果你想做类似的事情
(apply-funcs
(list (lambda (a b c)
(list (+ a c) (+ b c)))
(lambda (d e)
(+ d e)))
(list 1 2 3))
那么在
apply
的定义中您需要 funcall
而不是 apply-funcs
。
根据情况,也可以走宏观路线;
(defmacro ->> (value &body functions)
(reduce
(lambda (memo fn) `(funcall ,fn ,memo))
functions :initial-value value))
这基本上会做同样的事情。
CL-USER> (->> 6
(lambda (n) (+ 3 n))
(lambda (n) (- n 2))
(lambda (n) (* 2 n)))
14
CL-USER> (macroexpand
'(->> 6
(lambda (n) (+ 3 n))
(lambda (n) (- n 2))
(lambda (n) (* 2 n))))
(FUNCALL (LAMBDA (N) (* 2 N))
(FUNCALL (LAMBDA (N) (- N 2))
(FUNCALL (LAMBDA (N) (+ 3 N)) 6)))
T
来自 Alexandria 库的
compose
(和 multiple-value-compose
)函数,包括 compose
的编译器宏。你所描述的似乎类似于
(funcall (alexandria:compose #'h #'g #'f) x)
这样
(defun apply-funcs (functions value)
(funcall (apply #'compose (reverse functions)) value))
会做你想做的事——尽管我怀疑直接调用
compose
可能更有效地达到你的目的,具体取决于上下文。
库函数有:
(defun compose (function &rest more-functions)
"Returns a function composed of FUNCTION and MORE-FUNCTIONS that applies its
arguments to to each in turn, starting from the rightmost of MORE-FUNCTIONS,
and then calling the next one with the primary value of the last."
(declare (optimize (speed 3) (safety 1) (debug 1)))
(reduce (lambda (f g)
(let ((f (ensure-function f))
(g (ensure-function g)))
(lambda (&rest arguments)
(declare (dynamic-extent arguments))
(funcall f (apply g arguments)))))
more-functions
:initial-value function))
(define-compiler-macro compose (function &rest more-functions)
(labels ((compose-1 (funs)
(if (cdr funs)
`(funcall ,(car funs) ,(compose-1 (cdr funs)))
`(apply ,(car funs) arguments))))
(let* ((args (cons function more-functions))
(funs (make-gensym-list (length args) "COMPOSE")))
`(let ,(loop for f in funs for arg in args
collect `(,f (ensure-function ,arg)))
(declare (optimize (speed 3) (safety 1) (debug 1)))
(lambda (&rest arguments)
(declare (dynamic-extent arguments))
,(compose-1 funs))))))
您可以将
reduce
与 :from-end t
结合使用,如下例所示:
(defun working-copy-name (repository)
(flet ((trim-final-dotgit (string)
(subseq string 0 (search ".git" string :from-end t)))
(trim-initial-location (string)
(subseq
string
(or
(1+ (position #\Slash string :from-end t))
0)))
(trim-initial-cl-hyphen (string)
(if (eq 0 (search "cl-" string))
(subseq string 3)
string)))
(reduce #'funcall
(list #'trim-initial-cl-hyphen
#'trim-final-dotgit
#'trim-initial-location)
:initial-value repository
:from-end t)))
这个功能的用法是
CL-USER> (working-copy-name "https://github.com/melusina-org/cl-atelier.git")
"atelier"
请注意,此变体使用组合顺序(列表中的第一个函数是应用的最后一个函数)。如果您坚持按照应用的顺序编写函数列表,您可以
reverse
列表或使用 funcall
的后缀版本,如 (lambda (x f) (funcall f x))
并删除 :from-end t
)