optimization - 在 Lisp 中,使用 let* 还是 setf 更惯用?

标签 optimization scheme lisp common-lisp idioms

当一个值的计算需要多个步骤时,我倾向于使用(let*)声明一个变量的多个版本,因此:

(let* ((var 1)
       (var (* var 2))
       (var (- var)))
 (format t "var = ~a~%" var))

而不是人们在其他语言中期望的更命令式的风格:
(let ((var 1))
 (setf var (* var 2))
 (setf var (- var))
 (format t "var = ~a~%" var))

(显然,这是一个过于简单的例子,我通常会将其合并为一个声明。)

我想我只是更喜欢第一个版本,因为我从来没有真正改变状态。它似乎更“实用”或更干净——当然,可能更线程安全。

但在我看来,后者在内存分配或执行周期方面可能“更便宜”。

这些做法中的一种是否[a] 比另一种更惯用?或 [b] 在内存使用或时钟周期方面更有效?

编辑:我的代码中的真实示例。
(let* ((config-word      (take-multibyte-word data 2 :skip 1))
       (mem-shift-amount ...)
       (config-word      (translate-incoming-data config-word mem-shift-amount blank-value)))

  ...)
(let* ((reserve-byte-count (get-config :reserve-bytes))
       (reserve-byte-count (and (> reserve-byte-count 0) reserve-byte-count))
  ...)
(let* ((licence                (decipher encrypted-licence decryption-key))
       (licence-checksum1      (coerce (take-last 16 licence) '(vector (unsigned-byte 8))))
       (licence                (coerce (drop-last 16 licence) '(vector (unsigned-byte 8))))
       (licence-checksum2      (md5:md5sum-sequence (coerce licence '(vector (unsigned-byte 8)))))
       (licence                (and (equalp licence-checksum1 licence-checksum2)
                                    (decode licence))))
  ...)

最佳答案

我会说,以上都不是;但相反:

(let* ((v0 1)
       (v1 (* v0 2))
       (v2 (- v1)))
 (format t "var = ~a~%" v2)

根据需要替换有意义的名称。好的 Lisp 编译器必须能够消除临时变量,因为宏以 gensym 的形式大量生成临时变量。通常,这些 gensym 出现在实际上并不需要它们的情况下。它们绑定(bind)到不会导致任何名称冲突并且没有多重评估问题的东西。

例如,在下面的扩展中,X本身可以安全地代替 #:LIMIT-3374 使用基因符号。 X的多重评价不会造成任何问题,并且循环不会发生变异 X :
[6]> (ext:expand-form '(loop for i below x collect i))
(BLOCK NIL
 (LET ((#:LIMIT-3374 X) (I 0))
  (LET ((#:ACCULIST-VAR-3375 NIL))
   (TAGBODY SYSTEM::BEGIN-LOOP (WHEN (>= I #:LIMIT-3374) (GO SYSTEM::END-LOOP))
    (SETQ #:ACCULIST-VAR-3375 (CONS I #:ACCULIST-VAR-3375)) (PSETQ I (+ I 1))
    (GO SYSTEM::BEGIN-LOOP) SYSTEM::END-LOOP
    (RETURN-FROM NIL (SYSTEM::LIST-NREVERSE #:ACCULIST-VAR-3375)))))) ;

很少有充分的理由在同一个构造中绑定(bind)相同的名称两次,即使在该类型的构造中是允许的。

通过不插入额外的符号,您只是在编译器环境中节省了几个字节的空间,而且只有当这些符号已经没有出现在其他地方时才会这样做。

一个很好的理由是,所涉及的变量是一个特殊变量,必须多次绑定(bind)才能出现在从 init 表达式调用的函数的动态范围内:
(let* ((*context-variable* (foo)) 
       (*context-variable* (bar)))
  ...)

两者 foobar*context-variable* 的当前值使用react多变的; foo计算可用作 *context-variable* 的值我们想要 bar看到那个值而不是foo被暴露了。

或类似的东西:
(let* ((*stdout* stream-x)
       (a (init-a)) ;; the output of these goes to stream-x
       (b (frob a))
       (*stdout* stream-y)
       (c (extract-from b)) ;; the output of this goes to stream-y
       ...)
  ...)

关于optimization - 在 Lisp 中,使用 let* 还是 setf 更惯用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58313539/

相关文章:

clojure - 如何通过递归创建列表?

performance - 具有性能优化潜力的浮点算法

python - 列表搜索优化

functional-programming - (Racket) 返回满足条件的列表的子列表

scheme - 如何在 Racket 中得到小数的余数?

lisp - 变量 *、+ 和/绑定(bind)到 SLIME 或 Clozure CL 中最近的输入吗?

lisp - 简单的计划。第 8 章高阶函数

java - 优化:使 SQL 查询脱离循环

algorithm - 最大化人与人之间的互动

LISP 合并两个整数列表并按升序对它们进行排序?