【问题标题】:Alternative implementation of `if` - incomprehensible behavior`if` 的替代实现 - 难以理解的行为
【发布时间】:2014-05-20 09:33:43
【问题描述】:

我想使用布尔逻辑和宏编写自己的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 对其进行调试,结果确实与独立执行不同。我怀疑我错过了一些关键的执行细节或类似的东西。有人可以向我解释这里发生了什么吗?非常感谢您的帮助。

附:我的实现是 Clozure CL。

【问题讨论】:

    标签: if-statement macros lisp common-lisp ccl


    【解决方案1】:

    您的测试用例是数据。你提供给-if 的不是(&lt; 2 1) 的评估,而是&lt;、2 和1 作为元素的列表。除了nil 之外的任何东西都是真值,所以如果你想测试你所做的尝试:(-if '(&lt; 2 1) 'true 'false) ;==&gt; 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-once 或with-gensyms)唯一。一本很棒的书是Practical Common Lisp,它可以免费阅读,其中有一部分是关于宏和common errors and how to fix them

    【讨论】:

    • gensym 在这里并不真正需要,因为条件的第二次评估根本没有必要。当您已经知道,condition 为假时,无需检查(not ,condition) 是否为真。
    • @Rörd 给我一个defmacro,它仍然使用布尔逻辑并且不需要两次谓词?用(if t nil t)测试
    • 虽然也使用了其他一些构造,但这并不意味着它不再使用布尔逻辑,对吧?所以这会起作用:(defmacro -if (test then else) `(block nil (or (and ,test (return ,then)) ,else)))
    • 不过,我看到它不再像以前那样“纯布尔逻辑”了。我仍然会允许我在这里进行更改以防止出现错误结果的边缘情况。
    • @Rörd 这很聪明。我觉得比(defmacro -if (p c a) (once-only (p) `(or (and ,p ,c) (and (not ,p) ,a))))
    【解决方案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 是真还是假。

    如果你写了一个宏并使用它,你需要首先检查代码生成。

    • 生成的代码是否如您所愿?
    • 在生成的代码中是否多次执行操作?
    • 在生成的代码中是否以正确的顺序执行操作?
    • 生成的代码中是否存在不应出现的变量或函数符号?
    • 我的宏参数处理是否正确?

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

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

    【讨论】:

    • "也是检查测试结果的方式不想要你。"这句话看不懂,应该说什么?
    • @nrz: 固定在文本中
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-04
    • 1970-01-01
    • 1970-01-01
    • 2017-10-24
    • 2016-08-24
    相关资源
    最近更新 更多