schemelispchez-scheme

Exception: multiple definitions for function in body


I have a simple program:

(import (rnrs))
(define (abs x)
    (cond ((> x 0) x)
           ((= x 0) 0)
           ((< x 0) (- x))
           ))
(define (square x)
    (* x x))
(define (sum-sq x y)
    (+ (square x) (square y)))
(display
    (sum-sq (read) 3))

When I run it, I have an exception; what did I do wrong?

/home# scheme-script /home/scheme/main.ss
Exception: multiple definitions for abs in body (top-level-program #<annotation /home/scheme/main.ss[0:15] (import (...))> #<annotation /home/scheme/main.ss)[17:122] (define (...) (...))> #<annotation /home/scheme/main.ss[124:156] (define (...) (...))> #<annotation /home/scheme/main.ss[158:210] (define (...) (...))> #<annotation /home/scheme/main.ss[212:244] (display (...))> near line 1, char 1 of /home/scheme/main.ss


Solution

  • It is illegal to redefine a variable in a top-level program in R6RS Scheme. OP has executed the script using scheme-script; in Chez Scheme scheme-script is equivalent to scheme --program, which treats the file as a top-level program. It is fine to redefine things in the REPL when you are experimenting with definitions, unless you wrap that code in a top-level-program form.

    scheme --script treats the file as a shell script, while scheme-script (i.e., scheme --program) treats it as a top-level program. To demonstrate that OP's problem is a result of the file being treated as a top-level program, run it using scheme --script, then wrap the posted code in a (top-level-program ...) form and try running again with scheme --script. The first attempt will execute successfully, and the second will again raise an exception.

    One solution to OP's problem is to use scheme --script instead of scheme-script (or scheme --program). Of course, one could simply use the built-in abs procedure, or rename the new procedure as, e.g., my-abs.

    But, sometimes you really do want to use an identifier that has previously been claimed by some library that you need to import. For that situation there is except. Here is a version of OP's code using except in the import form:

    (import (except (rnrs) abs))
    
    (define (abs x)
      (if (< x 0) (- x) x))
    
    (define (my-abs x)
      (<))
    (define (square x) (* x x))
    
    (define (sum-sq x y)
      (+ (square x) (square y)))
    
    (display "Enter a number: ")
    (let ((x (read)))
      (display x) (display "^2 + 3^2 = ") (display (sum-sq x 3)) (newline))
    
    (display "Enter a number: ")
    (let ((x (read)))
      (display "|") (display x) (display "| = ") (display (abs x)) (newline))
    
    ;;; Easier with Chez Scheme `format`, which can be made available by
    ;;; changing the `import` form at the top to:
    ;;;
    ;;;  (import (except (chezscheme) abs))
    
    ;; (display "Enter a number: ")
    ;; (let ((x (read)))
    ;;   (format #t "~A^2 + 3^2 = ~A~%" x (sum-sq x 3)))
    
    ;; (display "Enter a number: ")
    ;; (let ((x (read)))
    ;;   (format #t "|~A| = ~A~%" x (abs x)))
    

    This program imports all identifiers which are exported from the rnrs library, except for abs. The top-level program is then free to define an abs identifier.

    $ scheme-script top-level.ss 
    Enter a number: 4
    4^2 + 3^2 = 25
    Enter a number: -42
    |-42| = 42
    

    What the Standard Says

    A top-level program is like a library, except that it cannot contain export forms. From R6RS 7.1. Library form:

    ... no identifier can be imported multiple times, defined multiple times, or both defined and imported.

    The specification continues:

    The expressions of variable definitions are evaluated from left to right, as if in an implicit letrec*....

    But, by 11.4.6. Binding constructs, in a letrec* form:

    (letrec* <bindings> <body>)‌‌ syntax

    Syntax: <Bindings> must have the form

    ((<variable1> <init1>) ...),

    where each <init> is an expression, and <body> is as described in section 11.3. Any variable must not appear more than once in the <variable>s.

    So an identifier can't be imported multiple times, both imported and defined, or defined and redefined in a library or top-level program. The OP code violates this by both importing a definition for an identifier, and redefining that identifier within the top-level program.