6.6 示例:函数构造器 (Example: Function Builders)

Dylan 是 Common Lisp 与 Scheme 的混合物,有着 Pascal 一般的语法。它有着大量返回函数的函数:除了上一节我们所看过的 complement ,Dylan 包含: composedisjoinconjoincurryrcurry 以及 always 。图 6.2 有这些函数的 Common Lisp 实现,而图 6.3 演示了一些从定义延伸出的等价函数。

  1. (defun compose (&rest fns)
  2. (destructuring-bind (fn1 . rest) (reverse fns)
  3. #'(lambda (&rest args)
  4. (reduce #'(lambda (v f) (funcall f v))
  5. rest
  6. :initial-value (apply fn1 args)))))
  7. (defun disjoin (fn &rest fns)
  8. (if (null fns)
  9. fn
  10. (let ((disj (apply #'disjoin fns)))
  11. #'(lambda (&rest args)
  12. (or (apply fn args) (apply disj args))))))
  13. (defun conjoin (fn &rest fns)
  14. (if (null fns)
  15. fn
  16. (let ((conj (apply #'conjoin fns)))
  17. #'(lambda (&rest args)
  18. (and (apply fn args) (apply conj args))))))
  19. (defun curry (fn &rest args)
  20. #'(lambda (&rest args2)
  21. (apply fn (append args args2))))
  22. (defun rcurry (fn &rest args)
  23. #'(lambda (&rest args2)
  24. (apply fn (append args2 args))))
  25. (defun always (x) #'(lambda (&rest args) x))

图 6.2 Dylan 函数建构器

首先, compose 接受一个或多个函数,并返回一个依序将其参数应用的新函数,即,

  1. (compose #'a #'b #'c)

返回一个函数等同于

  1. #'(lambda (&rest args) (a (b (apply #'c args))))

这代表着 compose 的最后一个实参,可以是任意长度,但其它函数只能接受一个实参。

下面我们建构了一个函数,先给取参数的平方根,取整后再放回列表里,接着返回:

  1. > (mapcar (compose #'list #'round #'sqrt)
  2. '(4 9 16 25))
  3. ((2) (3) (4) (5))

接下来的两个函数, disjoinconjoin 同接受一个或多个谓词作为参数: disjoin 当任一谓词返回真时,返回真,而 conjoin 当所有谓词返回真时,返回真。

  1. > (mapcar (disjoin #'integerp #'symbolp)
  2. '(a "a" 2 3))
  3. (T NIL T T)
  1. > (mapcar (conjoin #'integerp #'symbolp)
  2. '(a "a" 2 3))
  3. (NIL NIL NIL T)

若考虑将谓词定义成集合, disjoin 返回传入参数的联集(union),而 conjoin 则是返回传入参数的交集(intersection)。

  1. cddr = (compose #'cdr #'cdr)
  2. nth = (compose #'car #'nthcdr)
  3. atom = (compose #'not #'consp)
  4. = (rcurry #'typep 'atom)
  5. <= = (disjoin #'< #'=)
  6. listp = (disjoin #'< #'=)
  7. = (rcurry #'typep 'list)
  8. 1+ = (curry #'+ 1)
  9. = (rcurry #'+ 1)
  10. 1- = (rcurry #'- 1)
  11. mapcan = (compose (curry #'apply #'nconc) #'mapcar
  12. complement = (curry #'compose #'not)

图 6.3 某些等价函数

函数 curryrcurry (“right curry”)精神上与前一小节的 make-adder 相同。两者皆接受一个函数及某些参数,并返回一个期望剩余参数的新函数。下列任一个函数等同于 (make-adder 3) :

  1. (curry #'+ 3)
  2. (rcurry #'+ 3)

当函数的参数顺序重要时,很明显可以看出 curryrcurry 的差别。如果我们 curry #'- ,我们得到一个用其参数减去某特定数的函数,

  1. (funcall (curry #'- 3) 2)
  2. 1

而当我们 rcurry #'- 时,我们得到一个用某特定数减去其参数的函数:

  1. (funcall (rcurry #'- 3) 2)
  2. -1

最后, always 函数是 Common Lisp 函数 constantly 。接受一个参数并原封不动返回此参数的函数。和 identity 一样,在很多需要传入函数参数的情况下很有用。