Does eq? in Racket behave different from eq? in Guile or eq in Common Lisp?

Viewed 152

Happy New Year!

I am working my way into Racket (am not an expierienced Lisper).

Now I came across the following in Racket 8.3:

#lang racket
> (define str1 "hello")
> (define str2 "hello")
> (eq? str1 str2)
#t

I expected #f like here. And in Guile and in Common Lisp this was returned.

According to this I could reproduce that the following behaves as expected:

> (set! str1 (string #\h #\e #\l #\l #\o))
> (set! str2 (string #\h #\e #\l #\l #\o))
> (eq? str1 str2)
#f

Yet I don't see the reason.

Is it, that in Racket only the function STRING returns two different objects yet a literal string is unique like a symbol? Thus, eq? behaves usually yet literal strings are treated differently?

Would you be so kind and explain it to me?

Thank you very much.

4 Answers

What eq? does is the same thing that eq does in Common Lisp: it tells you if two objects are the same object.

What you are seeing is that it's often not defined whether literal objects which are similar are in fact the same object or not, and there can often be benefits in making them the same. One particular benefit is that you use less memory that way.

In the case of Racket it clearly is the case that literal strings are coalesced like this, at least in some cases. (But not always: given the following program

#lang racket

(define (ts)
  (define (getit prompt)
    (printf "~S? " prompt)
    (read))
  (eq? (getit "1")
       (getit "2")))

(ts)

then

$ racket ts.rkt
"1"? "foo"
"2"? "foo"
#f

for instance.)

In general, you should not make assumptions about whether literals which are similar are in fact the same object or not, because the system may, or may not, choose to coalesce them, and may or may not choose to do so in any particular case.

As an example of this, in CL if you have a file which contains this function

(defun foo ()
  (eq "foo" "foo"))

And you compile that file, it's just completely and explicitly undefined whether that function returns true or false: even if READ returns distinct objects for the two strings (which I am not sure it is required to do but it may be), the file compiler is certainly allowed to coalesce them so there is only a single literal string (and of course it is thus allowed to notice that the function always returns true and simply turn it into (defun foo () t)).

So the answer is: never make assumptions about literals which are similar either being, or not being, identical: if you want objects which are similar but not identical you need to make them yourself with a function which is defined to make new objects.

Is it, that in Racket only the function STRING returns two different objects ...

Not only function string. Almost every string function, like string-append, can create two different objects:

(define a "a")
(eq? (string-append a a) (string-append a a)) ;=> #f

... a literal string is unique like a symbol? Thus, eq? behaves usually yet literal strings are treated differently?

For Racket, yes, and this is guaranteed in the documentation:

string constants generated by the default reader (see Reading Strings) are immutable, and they are interned in read-syntax mode.

To elaborate a bit more, Racket uses read-syntax to read your program, and string reading in read-syntax is interned.

(define a (syntax-e (read-syntax))) ; input "a"
(define b (syntax-e (read-syntax))) ; input "a"
(eq? a b) ;=> #t

The standard doesn't want to dictate implementation lingo they instead make a vast list of what should be #t, what should be #f and that everything eq? always also is eqv?, but not vice versa. They write everything except the crucial information that it is pointer equalness. Ie. eq? returns #t when two arguments is the same object, like the == of Java.

In Scheme, (eq? "a" "a") is unspecified. Both #t and #f are acceptable results. It is one of the examples in the report. Literal values like "a" or '(a b c) are immutable. This is referenced from quote to the information about the storage model.

In the same report trying to mutate literals is considered an error. This is shown for string-set! like this:

(define (f) (make-string 3 #\*))
(string-set! (f) 0 #\?)  ‌⇒  unspecified

So make-string makes a new string each call which you really cannot use so the first example is OK Scheme code that wast cycles. If you were to bind the result to a variable and then do it you'll have access to the result like this:

(define (f) (make-string 3 #\*))
(define test (f))
(string-set! test 0 #\?)  ‌⇒  unspecified
test  ‌                    ⇒  "?**"

Now the second example is more on topic.

(define (g) "***")
(string-set! (g) 0 #\?)  ‌⇒  unspecified
             ; should raise  &assertion exception

The R5RS went so far as to say it isn't even Scheme so any result would be ok, while R6RS and later strongly suggest that trying to mutate should raise an exception.

Most implementations don't so for those calling (g) most likely results in one of "?**" or "***" when the illegal code works without an error.

Do back to you code:

(define str1 "hello")
(define str2 "hello")
(eq? str1 str2) ⇒  unspecified

For the exact same reason (eq? "a" "a") is unspecified. An interpreter might always return #f and even compiled code, but compiled code is more likely to return #t.

(define str1 (string #\h #\e #\l #\l #\o))
(define str2 (string #\h #\e #\l #\l #\o))
(eq? str1 str2)  ⇒  #f

They are always different since (string #\h #\e #\l #\l #\o) creates a new string and since str1 and str2 are created separately they are different strings that look the same.

Know that such compound data types can be checked for equality with equal? that would return #t when two objects are considered the same, eg. often when they look the same. Thus:

(equal? str1 str2)  ⇒  #t
Related