1

我想if用布尔逻辑和宏编写我自己的。我想出了以下实现:

(defmacro -if (condition if-true if-false)
  "Implements `if` in terms of Boolean logic only"
  `(or (and ,condition ,if-true)
       (and (not ,condition) ,if-false)))

我在几个案例中手动测试了它,它按预期工作。但后来我写了一个简单的测试函数来执行一系列测试,得到了一个我仍然无法理解的结果。我写了这样的函数:

(defun -if-test ()
  (let ((passed 0)
    (test-format "TEST: ~50a ==> ~:[FAILURE~;SUCCESS~]~%")
    (cases '((return-value (> 2 1) true)
         (return-value (< 2 1) false)
         (standard-output (> 2 1) "true")
         (standard-output (< 2 1) "false"))))
    (dolist (test-case cases)
      (destructuring-bind (type test expected) test-case
    (let ((result (case type
            (return-value
             (eq (-if test 'true 'false) expected))
            (standard-output
             (with-output-to-string (out)
               (string= (-if test (print "true" out) (print "false" out)) expected)))
            (otherwise (error "Unknown test type: ~a" type)))))
      (when result (incf passed))
      (format t test-format test-case result))))
    (format t "Result: ~a/~a tests passed.~%" passed (length cases))))

当我运行测试时,我得到以下输出:

TEST: (RETURN-VALUE (> 2 1) TRUE)                        ==> SUCCESS
TEST: (RETURN-VALUE (< 2 1) FALSE)                       ==> FAILURE
TEST: (STANDARD-OUTPUT (> 2 1) true)                     ==> SUCCESS
TEST: (STANDARD-OUTPUT (< 2 1) false)                    ==> SUCCESS
Result: 3/4 tests passed.
NIL

第二个失败案例显然在手动运行时与作为此函数的一部分运行时给出不同的结果。我尝试使用 SLDB 对其进行调试,结果确实与独立执行不同。我怀疑我错过了一些关键的执行细节或类似的东西。有人可以向我解释这里发生了什么吗?帮助真的很感激。

PS 我的实现是 Clozure CL。

4

2 回答 2

2

您的测试用例是数据。您提供-if的不是评估,而是(< 2 1)带有 和 作为元素的<列表。任何东西都不是真值,所以如果你想测试你所做的尝试:.21nil(-if '(< 2 1) 'true 'false) ;==> true

所以在这种情况下,你的测试是错误的。但是,您的宏中有一个错误:

(-if (progn (print "ONLY ONE TIME!") x) nil t)
; ==> (not x), but it prints "ONLY ONE TIME!" twice!

在制作宏时,您应该始终确保参数只被评估一次,它们被评估的顺序是在参数顺序中,因为 commonlisper 会期望这样,并且无论用户使用什么符号,该代码都不会失败。最后一个要求使用gensym以确保卫生。

要修复您的宏错误,您需要扩展为评估谓词的 let 表单,这样副作用就不会出现两次。此变量的名称必须与gensym使用gensym类似only-oncewith-gensyms来自流行书籍的宏或宏保持唯一。一本优秀的书是Practical Common Lisp,它可以免费阅读,其中有一部分是关于宏和常见错误以及如何修复它们的

于 2014-05-20T10:50:57.537 回答
2

您的-if-test代码根本不起作用。

CL-USER 18 > (pprint (macroexpand '(-if test 'true 'false)))

(LET ((#:OR-SUBFORM-RESULT1129 (AND TEST 'TRUE)))
  (IF #:OR-SUBFORM-RESULT1129
     #:OR-SUBFORM-RESULT1129
    (OR (AND (NOT TEST) 'FALSE))))

CL-USER 19 > (let ((test '(> 2 1))) (-if test 'true 'false))
TRUE

CL-USER 20 >  (let ((test '(< 1 2))) (-if test 'true 'false))
TRUE

您的代码不测试条件。它试图测试变量test是真还是假。

如果您编写宏并使用它,则需要首先检查代码生成。

  • 生成的代码看起来像你想要的吗?
  • 在生成的代码中是否不止一次地完成了事情?
  • 生成的代码中的事情是否以正确的顺序完成?
  • 是否有不应该出现在生成的代码中的变量或函数符号?
  • 我的宏参数处理正确吗?

此外,检查测试结果的方式也不是你想要的。您对表达式STRING=的结果执行 a 。-IF不带输出。另外:with-output-to-string返回一个字符串 - 总是。的值result就是这个字符串。(when result <do-this>)then 运行<do-this>,因为 result 是一个字符串,这始终是正确的。在 Common Lisp 中,除了 NIL 之外,每个值都是真的。

一般来说,如果你真的想用不同的参数测试一个宏,你实际上需要运行宏扩展。因此,您需要在测试时生成代码并EVAL在其上运行。

于 2014-05-20T10:51:05.083 回答