Search code examples
syntax-errorlispcommon-lispclisp

Why does this simple LISP function throw an error?


I isolated this function from a larger script and ran it through https://www.jdoodle.com/execute-clisp-online/. Even though there is an error thrown, it seems to follow the rules of LISP unless I'm missing something blatantly obvious.

(defun cannibals-can-eat (state start-state)
    (let ((left-bank-missionaries 2)
         (left-bank-cannibals 5)
         (right-bank-missionaries (- 3 left-bank-missionaries))
         (right-bank-cannibals (- 2 left-bank-cannibals)))

         (if (or (> left-bank-cannibals left-bank-missionaries)
                 (> right-bank-cannibals right-bank-missionaries))
             t
             nil)))

The error is sometimes The variable LEFT-BANK-MISSIONARIES is unbound.unmatched close parenthesis or syntax error near unexpected token('`. With this version of the function the error is the latter.


Solution

  • In Common Lisp there are two forms of local declarations (let):

    (let ((var1 exp1)
          (var2 exp2)
          ...
          (varn expn))
      exp)
    

    and

    (let* ((var1 exp1)
           (var2 exp2)
           ...
           (varn expn))
      exp)
    

    In the first every expression expi is evaluated in the environment holding before the let. In the second every expression expi is evaluated in the environment containing all the previous declarations var1 ... var(i-1).

    So in your example the declaration of right-bank-missionaries uses left-bank-missionaries which is undefined since it is declared in the same let.

    Simply use let* to allow the use of every variable immediately after its declaration:

    (defun cannibals-can-eat (state start-state)
        (let* ((left-bank-missionaries 2)
               (left-bank-cannibals 5)
               (right-bank-missionaries (- 3 left-bank-missionaries))
               (right-bank-cannibals (- 2 left-bank-cannibals)))
    
          (or (> left-bank-cannibals left-bank-missionaries)
              (> right-bank-cannibals right-bank-missionaries))))
    

    Note that the final if is useless if you want to return a generalized boolean.