Skip to content

LISP - 在列表上使用 `mapc`

Common Lisp 提供了一系列强大的 map 函数,用于迭代序列。虽然 mapcar 用于通过对每个元素应用函数来创建新列表,但 mapc 纯粹用于其副作用。它对每个元素应用函数,但会丢弃结果,始终返回原始列表。

(mapc function list &rest more-lists)
  • function:应用于每个元素的函数。这可以是一个命名函数(例如 #'print)或一个 lambda 函数。
  • list:要迭代的列表。
  • 返回值:mapc 返回其第一个列表参数。函数调用的返回值被忽略。

需要记住的关键区别:

  • mapcar (car 用于 ‘c’ontents):收集结果。当您想要将一个列表转换为新列表时使用它。
  • mapc (c 用于 ‘c’onditional/side-effect):丢弃结果。当您想要对每个元素做一些事情时使用它,例如打印、修改文件或更新另一个数据结构。

打印是一个经典的副作用。由于我们不需要收集任何结果,mapc 是一个完美的搭配。

;; Prints each number on a new line
(mapc #'print '(10 20 30))
10
20
30

请注意,REPL 也会打印 mapc 的返回值,即原始列表 (10 20 30)。

在这里,我们使用 mapc 迭代数字列表并将它们添加到运行总和中。修改 total 是副作用。注意特殊(全局)变量的 *earmuffs* 命名约定。

(defvar *total* 0 "A running total, for demonstration.")
(defun add-to-total (numbers)
(mapc (lambda (x) (incf *total* x))
numbers)
;; mapc returns the original list, but we are interested in the final total.
*total*)
;; Reset total and run the function
(setf *total* 0)
(add-to-total '(1 2 3 4 5))
15

就像 mapcar 一样,mapc 可以同时操作多个列表。提供的函数必须接受与列表数量相等的参数。一旦最短的列表耗尽,迭代就会停止。

(let ((users '("Alice" "Bob" "Charlie"))
(ids '(101 102 103)))
(mapc (lambda (user-name user-id)
(format t "Processing user: ~a (ID: ~a)~%" user-name user-id))
users ids))
Processing user: Alice (ID: 101)
Processing user: Bob (ID: 102)
Processing user: Charlie (ID: 103)

对于具有副作用的简单循环,dolist 宏通常更具可读性,并且是 mapc 的一个非常常见的替代方案。

;; Using mapc
(mapc #'print '(a b c))
;; Using dolist - often considered more direct and clearer
(dolist (item '(a b c))
(print item))

对于简单的单列表迭代,选择 dolist。当您想要更函数式的风格或需要并行迭代多个列表时,选择 mapc。