Operator (re)definition and order of operations

Given:

#lang racket

(* (+ 2 (* 4 6))
   (+ 3 5 7))

(define + (* 2 3))

+

Why does dr racket complain about the combination starting at line 2, that:

+: undefined;
 cannot reference an identifier before its definition

Would it help if the error message ended in “is evaluated”?

Not really. I'm a racket (sicp dialect) newb. But ...

If I put:

(define + 6)
(+ 2 2)

it correctly states that application: not a procedure; given 6

but

(+ 2 2)
(define + 6)

gives the aforementioned: +: undefined;
cannot reference an identifier before its definition

which doesn't make sense because + is the addition operator, provided by the language, or at least it should be... until the (re) define.

That is, if I just put

(+ 2 2)

and don't bother using define at all it correctly evaluates to 4.

I think this is two problems:

  1. you think defined + shadow origin +
  2. we cannot use a variable before you define it

To check the second problem, we can have the following code:

(c 2 3)
(define (c a b) `(,a ,b))

you will get c: undefined;. With the following code:

a
(define a 1)

you will get a: undefined;.

So the part that confuses you is, should + reference to origin language provided +? The answer is no, because the top-level mapping of id is linked with the compiled definition! Thus, in your code, there is actually only has one + definition and you use it before you define it.

1 Like

OK. I think I'm tracking with you. Are you suggesting that racket is compiled? At least, when run in the definitions window?

Well... Racket is compiled, but that's not relevant in this question. Your question has to do with scope: what is the scope of the top-level definition of plus?

In Racket, the scope of a top-level definition is the whole file.

3 Likes

All interpret language seems like interpret who wants performance, do compile actually, but this is not the point. The point is anyway, once you write down (define id expression), all id refers to this one in this module/namespace/compiled component.

2 Likes

(Someone jump in and correct me; I'm sure I got some details wrong, but the general idea seems reasonable.)

I think of the top-level of a module a bit like a big letrec context combined with the printing of non-define expressions. This is necessary to allow mutual recursion (e.g., try to define even? and odd? such that (even? n) is true iff n is 0 or (odd? (sub1 n)) and (odd? n) iff n is 1 or (even? (sub1 n)); without forward references, this would be difficult) because forward references are allowed. Essentially, the identifiers to be bound are not created in sequence. In a module with

(define (x) …)

x will refer to this top-level binding except where it is shadowed.

However, there is a sequence for when identifiers get their binding! The placeholders (identifiers, like x) may be grouped up front (like a letrec does so that they may all reference each other), but the bindings are not installed until the binding expressions (like define) are evaluated. @dannypsnl has a great example of this.

The error message isn't that a isn't unbound identifier which you get when a has no definition; it's that a is undefined. It will be bound, but you can't use it until it has been bound.


This all explains why racket complains about + in your example: By the time you get to the (* …) expression, + is marked as to-be-bound in this module. But it hasn't been bound yet.

2 Likes

And an interesting part is (define (id ...) ...), we can treat (define (id ...) ...) as (define id (lambda (...) ...)), so we get the following example:

(is-even? 1) ; error: is-even?: undefined

(define (is-even? n)
  (or (zero? n)
      (is-odd? (sub1 n))))

(is-even? 1) ; error: is-odd?: undefined

(define (is-odd? n)
  (or (zero? n)
      (is-even? (sub1 n))))

It seems like + is defined by scheme. Once a file includes a (define + whatever) ANYWHERE, scheme marks + as needing a binding expression before it can be referenced ANYWHERE. With x, it is not defined by scheme, therefore it requires a binding expression, naturally. Is this a reasonable understanding?

Here's my confusion, stated after the clarifications. In the repl, I can evaluate:

(+ 1 2 3 4)

with 10 as the result. Telling me that + is bound to the addition operation. Then if I:

(define + 6)

and try:

(+ 1 2 3 4)

I get the expected, can't apply 6, error.

This is rational and makes sense. + is bound to addition, I bind it to a number, a number can't be applied to anything. It's orderly and ordered.

Put all of the code above into a script and run it:

(+ 1 2 3 4)
(define + 6)
(+ 1 2 3 4)

and it seems like the code is first parsed and the parser detects that + is in need of binding and the + undefined message is generated before any other code is executed (as described in the thread, above). This seems inconsistent, but I'm positive, it's my lack of understanding. I appreciate y'alls clarifying comments and explanations.

I think you're just seeing the symptoms of "top level is hopeless":

3 Likes

Yes, the delayed evaluation from lambda is key here.

Did you specifically mean to mention "scheme" here as opposed to "racket"? Scheme has a sequence of language definitions which will answer this question, but the answers there won't necessarily be the same as those for Racket. To some degree, you can explore this using

#lang r5rs

and comparing it to

#lang racket

If you don't specifically care about Scheme as opposed to Racket, feel free to ignore this.

2 Likes

Good point. I use Dr. Racket to explore SICP, which is basically a book that uses Scheme, probably r4rs. But, I should have said Racket, which I tested for the behavior before posting here.