通常,我发现我想根据先前值的值有条件地计算一系列值。例如,我们将这些值称为x
、y
和z
。首先我计算x
。如果x
满足某些条件,那么我计算y
,这是x
的函数,依此类推。 示意性地,
;; compute value x
;; if x =? #f -> #f
;; else compute value y = f(x)
;; if y =? #f -> #f
;; else compute value z = f(y)
;; et cetera
你如何在方案中做到这一点?我认为通常人们会使用cond
但cond
会丢弃测试结果,因此在这种情况下没有用。
使用let*
,它在 brevious 绑定的范围内按顺序计算初始化形式。在初始化窗体中,使用and
使计算具有条件。
(let* ((x (compute-x))
(y (and x (f1 x)))
(z (and y (f2 y))))
;; code that uses the variables
)
您可以在cond
子句中使用=>
设备,例如:
(define (testing p x)
(if (p x)
x
#f))
(display
(cond
((testing even? 1) => (lambda (x) ; the clause is skipped
(list 10 x)))
((testing even? 2) => (lambda (x) ; the clause is entered and
(list 20 x))))) ; `x` is bound to 2
您将在相应的子句中嵌套cond
,以表示您描述的嵌套条件。
这样,代码结构明显遵循您的逻辑,这总是好的。
所以你的代码可以用标准的Common Lisp这样写:
(let ((it x-expression))
(if it
(let ((it (f it)))
(if it
it))))
请注意,我没有给出其他形式(替代),因为它在 CL 中是可选的。 Paul Graham 引入了 Anaphoric 宏,以自动缓存测试值。
(defmacro aif (test-form then-form &optional else-form)
`(let ((it ,test-form))
(if it ,then-form ,else-form)))
(aif x-expression
(aif (f it)
(aif (f it)
it)
这在 CL 中效果很好,但它在 Plan 中带来了卫生问题。我知道有人为绑定名称创建了一个带有额外参数的东西,但它失去了它的优雅:
(aif x x-expression
(aif y (f x)
(aif z (f y)
z)))
我一直在尝试cond
在创建评估器时,我通常会在剥离之前进行测试,最后以if
和let
嵌套结束。我的第一次迭代如下所示:
(define (ev expr env)
(defcond
((symbol? expr) (symbol->value expr env))
((not (pair? expr)) expr => (operator (ev (car expr) env)))
((macro? operator) (macro-apply expr (cdr expr) env) => (args (map (lambda (e) (ev e env)) (cdr expr))))
(else (fun-apply operator args env))))
它还支持另一种方式,因为我发现重用=>不是那么优雅:
(define (ev expr env)
(defcond
((symbol? expr) (symbol->value expr env))
((not (pair? expr)) expr)
(define operator (ev (car expr) env))
((macro? operator) (macro-apply expr (cdr expr) env))
(define args (map (lambda (e) (ev e env)) (cdr expr)))
(else (fun-apply operator args env))))
现在,这也可以以与aif
相同的方式使用。如果您有兴趣,方案宏是:
(define-syntax defcond
(syntax-rules (else bind define =>)
((_ "build" terms ())
terms)
((_ "build" alternative ((bind (b e) ...) . rest))
(defcond "build" (let ((b e) ...) alternative) rest))
((_ "build" alternative ((bind name (b e) ...) . rest))
(defcond "build" (let name ((b e) ...) alternative) rest))
((_ "build" alternative ((define b e) . rest))
(defcond "build" (letrec ((b e)) alternative) rest))
((_ "build" alternative ((predicate consequent) . rest))
(defcond "build" (if predicate consequent alternative) rest))
((_ "build" alternative ((predicate consequent => (b e) ...) . rest))
(defcond "build" (if predicate consequent (let ((b e) ...) alternative)) rest))
((_ "build" alternative ((predicate consequent) . rest))
(defcond "build" (if predicate consequent alternative) rest))
((_ "maybe-else" ((else expression) . rest))
(defcond "build" expression rest))
((_ "maybe-else" ((something expression) . rest))
(defcond "build" #f ((something expression) . rest)))
((_ "reverse" terms ())
(defcond "maybe-else" terms))
((_ "reverse" (fterms ...) (term1 terms ...))
(defcond "reverse" (term1 fterms ...) (terms ...)))
((_ terms ...)
(defcond "reverse" () (terms ...)))))
不是很优雅的实现,但它有效。如您所见,它也支持bind
和命名bind
。例如。
(defcond
((not (pair? lst)) #f)
(bind loop ((lst lst) (acc 0)))
((null? lst) acc)
(else (loop (cdr lst) (+ acc (car lst))))
虽然我喜欢这个想法,但我仍然不认为它应该是一个神圣和优雅的。在出现更好的语法之前,我将编写它以提高可读性。例如:
(if (not (pair? lst))
#f
(let loop ((lst lst) (acc 0))
(if (null? lst)
acc
(loop (cdr lst) (+ acc (car lst))))))