conditional-statementsschemelanguage-lawyerracketchez-scheme

A question about cond expressions in Scheme


With Chez Scheme version 9.5.5, consider the two examples of cond:

(cond #t (else 2))
(cond (> 2 1) (else 2))

The first expression evaluates to #t, while the second expression evaluates to 1.

It seems that the first expression is first expanded to (cond (#t #t) (else 2)), while the second expression is expanded to (cond ((> 2 1) 1) (else 2)) before evaluation.

My question is: Since the value of expression (> 2 1) is #t, It is counter-intuitive to me that the two cond expressions have different values. Are there good reasons to define cond in such a way?

Edit: I tried the two expressions with GNU Guile 3.0.1:

Edit2: With CHICKEN Scheme 5.1.0:


Solution

  • The first expression raises an exception in Chez 9.5.4, which is the most recent release:

    > (cond #t (else 2))
    
    Exception: invalid syntax (cond #t (else 2))
    Type (debug) to enter the debugger.
    

    I'm not sure why 9.5.5 would be different in that regard; if true, this probably points to a change in the underlying implementation of cond in 9.5.5.

    In any case, the cond form takes one or more <cond clause> arguments, and an optional else clause, where a <cond clause> must be of the form (<test> <expression1> ...); i.e., a <cond clause> must be a parenthesized expression containing one test form and zero or more expressions. Each <cond clause> is evaluated in order until a <test> evaluates to true, after which the subsequent expressions are evaluated in order, and the value of the final expression is returned. When there are no subsequent expressions in a true <cond clause>, the value of <test> is returned.

    The expression (cond #t (else 2)) violates this (because the first <cond clause> is malformed) and should probably raise a syntax error, which 9.5.4 does. If 9.5.5 does not raise a syntax error in this case, a bug report should probably be filed with the maintainers. Note again that 9.5.5 is not a released version of Chez Scheme, though.

    This was already stated above, but I'll repeat here: when a <test> expression evaluates to true, and there are no subsequent expressions in the <cond clause>, the clause evaluates to the value of <test>. So this is expected to work according to R6RS, and it works as expected in 9.5.4:

    > (cond (#t) (else 2))
    #t
    

    This would also be the correct way to express the second expression in the OP question. Here I have modified the else clause to return 3 to make it a bit more clear which branch is evaluated:

    > (cond ((> 2 1)) (else 3))
    #t
    > (cond ((> 1 2)) (else 3))
    3
    

    To be clear: the first <cond clause> in the first expression above is ((> 2 1)), which contains the <test> (> 2 1). This test evaluates to #t, and since there are no subsequent expressions in this clause, the clause returns #t. The second expression has ((> 1 2)) as its <cond clause>, with the <test> (> 1 2). This test evaluates to #f, so evaluation proceeds to the next <cond clause>, (else 3), which returns 3.

    The unaltered second expression posted in the question behaves the same way in 9.5.4 as reported by OP for 9.5.5. This behavior is exactly as R6RS specifies it should be:

    > (cond (> 2 1) (else 2))
    1
    

    The first <cond clause> here is the form (> 2 1), where > is the <test>, and 2 and 1 are <expression1> and <expression2>. Now, if <test> evaluates to true, then the subsequent expressions are evaluated, and the value of the final expression is returned.

    So, with (cond (> 2 1) (else 2)), the first <cond clause> is (> 2 1), with the <test> being the expression >, which evaluates to a procedure, which is not #f and thus a true value. Then the subsequent expressions 2 and 1 are evaluated in turn, and the value of the final expression is 1, which is then returned.

    Note that the form (> 2 1) is not evaluated as a <test> here; the form > alone is evaluated as a <test>. Further, (< 2 1) should behave identically to (> 2 1) in this case. In fact, any similar form will behave identically to this, so long as the first element does not evaluate to #f:

    > (cond (> 2 1) (else 3))
    1
    > (cond (< 2 1) (else 3))
    1
    > (cond ('any-truthy-thing 2 1) (else 3))
    1
    

    Version Update

    A few days after I posted this answer the new version of Chez was released. Note that OP was working with 9.5.5, which was never a release version (presumably OP built 9.5.5 from source). The new release is 9.5.6. I did not build 9.5.5 from source to test any of this; I used the current release at the time, which was 9.5.4. I have now updated to 9.5.6 on my main machine, and both 9.5.4 and 9.5.6 have identical behavior for the expressions OP asked about. The first OP expression was (cond #t (else 2)), which OP found to evaluate to #t. Chez should not return #t in this case, but is required to raise a syntax error, as explained in my answer above. An issue was raised with the maintainers for 9.5.5, but the problem could not be reproduced. The second expression was (cond (> 2 1) (else 2)); this expression was treated correctly by 9.5.4, OP's 9.5.5, and now also by the current 9.5.6 release.