lispcommon-lispsbclclispsyntax-checking

Unreachable Ill-formed if-expression is syntax error in Scheme but not in Common Lisp


I'm trying to get a better understanding of how S-expressions are evaluated in different lisps and wanted to see they would handle interesting ill-formed expressions. I get that Common Lisp and Scheme are totally different languages, but is there a specific difference in their semantics that explains the difference in behavior. For example, Lisp-1s and Lisp-2s have observable differences in their behavior, as do hygienic vs non-hygienic macro systems.

I have a program containing an unreachable ill-formed if expression in Scheme and Common Lisp.

;; foo.scm
(if #t 1 (if))

(display "12")

And the Common Lisp version

;; foo.lisp
(if t 1 (if))

(display "12")

chicken and guile both produce a syntax error.

Chicken:

% chicken foo.scm

Syntax error: (foo.scm:1) in `if' - pair expected

    (if)

    Expansion history:

    <syntax>      (##core#begin (if #t 1 (if)))
    <syntax>      (if #t 1 (if))
    <syntax>      (##core#if #t 1 (if))
    <syntax>      (if)  <--

Guile:

% guile foo.scm
...
.../foo.scm:1:9: source expression failed to match any pattern in form (if)

sbcl and clisp both print 12 and emit no warnings.

SBCL:

% sbcl --load foo.lisp
This is SBCL 1.3.11, an implementation of ANSI Common Lisp.
...
12
0]^D

CLISP

% clisp foo.lisp

"12"

Solution

  • Implementations of Common Lisp: Interpreter and Compilers

    In Common Lisp the type of code execution depends on what the Lisp system implements and how you use it. Often Common Lisp implementations have multiple ways to execute code: an interpreter and one or more compilers. Even a single running implementation may have that and it will allow the user to switch between those.

    GNU CLISP has both an interpreter and a compiler

    Example in GNU CLISP:

    LOAD of a text file typically uses the interpreter:

    [1]> (load "test.lisp")
    ;; Loading file test.lisp ...
    ;; Loaded file test.lisp
    T
    

    The Interpreter will not detect the error, because it does not check the whole expression for syntactic correctness. Since the else clause with the syntax error is never used, the Interpreter will never look at it.

    CLISP also has a compiler:

    [2]> (compile-file "test.lisp")
    ;; Compiling file /tmp/test.lisp ...
    ** - Continuable Error
    in #:|1 1 (IF T 1 ...)-1| in line 1 : Form too short, too few arguments: (IF)
    If you continue (by typing 'continue'): Ignore the error and proceed
    The following restarts are also available:
    ABORT          :R1      Abort main loop
    

    As you see, the CLISP compiler detects the syntax error and gives a clear error message.

    SBCL uses a compiler, but also has an interpreter

    SBCL by default uses a compiler, which will detect the error. For top-level forms it uses for some forms a simpler evaluation mechanism. One can also switch to an interpreter.

    If you write a simple IF form in SBCL, the evaluator does not use full compilation and doesn't catch the error:

    CL-USER> (if t 1 (if))
    1
    

    If you write the same code inside a function definition, the error gets detected, because function definitions will be compiled by default:

    CL-USER> (defun foo () (if t 1 (if)))
    ; in: DEFUN FOO
    ;     (IF)
    ; 
    ; caught ERROR:
    ;   error while parsing arguments to special operator IF:
    ;     too few elements in
    ;       ()
    ;     to satisfy lambda list
    ;       (SB-C::TEST SB-C::THEN &OPTIONAL SB-C::ELSE):
    ;     between 2 and 3 expected, but got 0
    ; 
    ; compilation unit finished
    ;   caught 1 ERROR condition
    WARNING: redefining COMMON-LISP-USER::FOO in DEFUN
    FOO
    

    If you would switch to the full SBCL interpreter, the error would not be detected at definition time:

    CL-USER> (setf *evaluator-mode* :interpret)
    :INTERPRET
    CL-USER> (defun foo () (if t 1 (if)))
    WARNING: redefining COMMON-LISP-USER::FOO in DEFUN
    FOO
    

    Optimisation and syntax checking

    Note that some optimising compilers might not check the syntax of unreachable code.

    Summary

    In most Common Lisp implementations you need to use the compiler to get full syntax warnings/errors.