有人写过 when-let-cond 吗?

Did anybody write when-let-cond?

我在想一个有点扭曲的条件

(let ((a 0))
  (let* ((result nil))
    (tagbody
       (let ((b1 (+ 0 a)))
         (when (eq b1 1)
           (print "1")
           (setf result b1)
           (go finish)))
       (let ((b2 (+ 0 a)))
         (when (eq b2 2)
           (print "2")
           (setf result b2)
           (go finish)))
       (when T
         (print "else")
         (setf result a))
       (format t "=== ~A~%" a)
     finish)
    result))

其中 when 测试表单包含在 let 中。一方面,这似乎适合我正在处理的问题,但也似乎过于复杂。可以用宏简化吗?如果我有很多测试表格,简化它的最佳方法是什么?

尝试这样做的部分问题是将 let 块限制为仅一个测试表单及其主体。

但我想知道我是否走错了路。玩 when-let 的假想变体表明沿着这条路走下去没有任何好处。

正在尝试条件

使用 cond 的版本看起来更紧凑。

(let ((a 3))
  (let* ((b1 (+ 0 a))
         (b2 (+ 0 a)))
    (cond
      ((eq b1 1)
       (print "1")
       b1)
      ((eq b2 2)
       (print "2")
       b2)
      (T (print "else")
         a))))

所有归结为 let* 中定义的变量,在现实生活中的示例中将用于避免两次计算相同的值并提高可读性。我该怎么办?

到目前为止,使用 macrolet 是最好的解决方案。这使我能够绕过 when-let 的限制,并且并非 let 形式中的所有绑定都必须评估为真。

(let ((a 3))
  (let ((result nil))
    (macrolet ((ret-go (res)
                 `(progn
                    (setf result ,res)
                    (go finish))))
      (tagbody
         (let ((b1 (+ 0 a)))
           (when (eq b1 1)
             (print "1")
             (ret-go b1)))
         (let ((b2 (+ 0 a)))
           (when (eq b2 2)
             (print "2")
             (ret-go b2)))
         (when T
           (print "else")
           (setf result a))
         (format t "=== ~A~%" a)
       finish)
      result)))

我宁愿更多地考虑块和它们的返回值,而不是使用 goto 和变量。如果真的需要单独的 let-bound 变量和它们自己的作用域:

(prog ((a 0))
  (let ((b1 (+ 0 a)))
    (when (eql b1 1)
      (print "1")
      (return b1)))
  (let ((b2 (+ 0 a)))
    (when (eql b2 2)
      (print "2")
      (return b2)))
  (return
    (progn
      (print "else")
      (return a))))

现在有人做了。我希望它与 cond 兼容,这会引发一个问题:如果您希望绑定子句像

(cond/binding
  ...
  ((var expr) <use var>)
  ...)

但是你只想允许通用测试子句,那么只有一个参数的函数是不明确的:应该

(cond/binding
  ...
  ((car x) ...)
  ...)

调用car还是绑定car?为了使这项工作正常进行,您需要在这种情况下绑定一个无用的变量:

(cond/binding
  ...
  ((useless (car x)) <useless not used here>)
  ...)

这意味着您要么需要在各处插入 ignoreignorable 声明,要么忍受编译器警告。

所以,好吧,我决定换一种方式更好:当你想绑定一个变量时,你必须。你可以通过这样的子句来做到这一点:

(cond/binding
  ...
  ((bind var expr) <var is bound here>)
  ...)

注意 bind 在语法上很神奇(所以这意味着你不能调用一个名为 bind 的函数,但这没关系,因为我已经使用 bind 作为other macros.

中的关键字

宏也很努力(好吧,很难,因为我基本上只是输入它并且没有测试)实际上表现得像 cond:返回多个值,例如。

所以这个:

(cond/binding
  ((f x y z) t)
  ((bind x 3) (print x) (values x t))
  (t (values nil nil))
  (1))

扩展到

(block #:cond/binding
  (when (f x y z)
    (return-from #:cond/binding (progn t)))
  (let ((x 3))
    (when x
      (return-from #:cond/binding
        (progn (print x) (values x t)))))
  (when t
    (return-from #:cond/binding (progn (values nil nil))))
  (let ((r 1))
    (when r
      (return-from #:cond/binding r))))

(所有方块都是同一个方块)。

所以,这里:

(defmacro cond/binding (&body clauses)
  ;; Like COND but it can bind variables.  All clauses are (should be)
  ;; like COND, except that a clause of the form ((bind var <expr>)
  ;; ...) will bind a variable.  Note that bind has to be literally
  ;; the symbol BIND: it's magic in the syntax.
  (let ((bn (make-symbol "COND/BINDING")))
    `(block ,bn
       ,@(mapcar
          (lambda (clause)
            (unless (consp clause)
              (error "bad clause ~S" clause))
                (case (length clause)
                  (1
                   `(let ((r ,(car clause)))
                      (when r (return-from ,bn r))))
                  (otherwise
                   (destructuring-bind (test/binding &body forms) clause
                     (typecase test/binding
                       (cons
                        (case (car test/binding)
                          ((bind)
                           (unless (and (= (length test/binding) 3)
                                        (symbolp (second test/binding)))
                             (error "bad binding clause ~S" test/binding))
                           (destructuring-bind (var expr) (rest test/binding)
                             `(let ((,var ,expr))
                                (when ,var
                                  (return-from ,bn
                                    (progn ,@forms))))))
                          (otherwise
                           `(when ,test/binding
                              (return-from ,bn
                                (progn ,@forms))))))
                       (t
                        `(when ,test/binding
                           (return-from ,bn
                             (progn ,@forms)))))))))
          clauses))))

买者自负。

如果我对你的问题理解正确,那么你可以使用 or 并依赖于 when 在条件不成立时计算为 nil 的事实,例如,

(defun example (a)
  (or
   (let ((b1 (+ 0 a)))
     (when (eql b1 1)
       (print "1")
       b1))
   (let ((b2 (+ 0 a)))
     (when (eql b2 2)
       (print "2")
       b2))
   (progn
     (print "else")
     a)))