如何简化 (if cond (with-xxx body) body) 只写一遍 body?

换句话说,就是把外层的 form 作为可选项,同时避免在 if-else 分支里各写一遍 body。

除了把 body 抽象到另一个函数(还是要写两遍)之外,有没有更简便的写法?

dash 有没有现成的函数,例如可以写成如下形式:

(-yyy (cond with-xxx) body)

--> 倒是可以勉强为之,但写起来也没比 let 更简洁:

(--> (if t 'prog1 'progn)       ;; => (let ((it (if t 'prog1 'progn)))
     (eval `(,it 1 2 3)))       ;;      (eval (cons it '(1 2 3))))

(--> '(1 2 3)                               ;; => (let ((it '(1 2 3)))
     (eval `(,(if t 'prog1 'progn) ,@it)))  ;;      (eval (cons (if t 'prog1 'progn) it)))

如果封装一个 with-xxx-if

#+BEGIN_SRC emacs-lisp :results value pp :with-emacs
(defmacro with-xxx (&rest body)
  `(let ((xxx 1))
     ,@body))

(defmacro with-xxx-if (cond &rest body)
  `(,(if cond 'with-xxx 'progn)
    ,@body))

(list (with-xxx-if t   (or (bound-and-true-p xxx) 2))
      (with-xxx-if nil (or (bound-and-true-p xxx) 2)))
#+END_SRC

#+RESULTS:
: (1 2)

但这样的命名有误导之嫌,看起来更像是:(if cond (with-xxx body)),应该起个什么样的名字才好?

友情提醒,不要抽象过度,抽象过度会导致抽风。。。

4 个赞

你的第二个方案,不一定要用宏。比如:

(apply (if cond #'fn1 #'fn2) '(body list))

这个情况,fn2 就可以是 identify。那么这个函数我们可以命名为:makeWrap,然后再包一层的话就是 makeWrapIf感觉上有点像 traverse。如果一定要用 form 的话,那好像似乎只能用宏

或者,换个方向,我们可以认为这个操作认为是一个 fold,当条件成立时做第一种,当条件不成立时做第二种。

或者,我们再换个角度。把 cond fn1 body 捏进一个类型里,做个 monad。

1 个赞

这种情况俺一般就 cl-symbol-macrolet

算不上是过度抽象。

我这个需求在 dash 里面应该可以归类为 threading,算是比较好理解的,像 --> 这样的符号即简洁又形象。

另外,dash 有个待合并的 pr#349 就实现了类似我的需求的 (-cond-> x &rest branches):

(-cond-> 1 t   (1+)) ;; => 2
(-cond-> 2 nil (1+)) ;; => 2

遗憾的是不能把宏放到 branches 里调用。