0

I was trying to define a data structure initialization function like '(() ()), so that I can generate many of it later.

Right after I defined it, the init function works expectedly. But after I use set-car! inside a (let* ...) function call, the behavior of (init) changed.

My question is how to explain this behavior?

; define init func
(define (init)
  (display "initializing goal space...")
  (newline)
  '(() ())
  )

; call init func
(init)

; use set-car! in (let* ...)
(let*
    ((x (init)))
  (display x)
  (newline)
  (set-car! x (list 'foo))
  (display x)
  (newline)
  )

; call init func again
(init)

The output log in DrScheme, lang = Textual (MzScheme, include R5RS)

Welcome to DrScheme, version 372 [3m].
Language: Textual (MzScheme, includes R5RS).
initializing goal space...
(() ())
initializing goal space...
(() ())
((foo) ())
initializing goal space...
((foo) ())
> 
pimgeek
  • 259
  • 1
  • 6
  • 17
  • possible duplicate of [Unexpected persistence of data](http://stackoverflow.com/questions/18790192/unexpected-persistence-of-data) – Joshua Taylor Dec 14 '13 at 15:13
  • 2
    That duplicate is nominally about Common Lisp, but it's exactly the same principle at work, as @Chris Jester-Young [points out](http://stackoverflow.com/a/20580530/1281433). `quote` gives you literal data, and modifying literal data has undefined consequences. – Joshua Taylor Dec 14 '13 at 15:15
  • 1
    Even though it's a duplicate, this is still a good question (and it's hard problem to search for, because there are no error messages or other obvious terms to search for), and you've asked it well by providing a minimal working example and clearly showing the results. +1 for you. – Joshua Taylor Dec 14 '13 at 15:17
  • 1
    Actually, I've updated the answer in the duplicate to include the citation to the Scheme spec about literal data, too. – Joshua Taylor Dec 14 '13 at 20:38
  • Thanks for your detailed explanation, I was trying to write some toy code(I'm not experienced...) which can be understood by learners who are 1~2 steps less experienced. Maybe that's why it looks minimal :) Anyway, your feedback is very helpful~. – pimgeek Dec 23 '13 at 14:33

1 Answers1

5

Make your init function return (list '() '()) instead of '(() ()). This will cause it to return a new list each time it's called.

Literal data, like '(() ()), is immutable. That means that trying to mutate it using set-car! has undefined behaviour. The reason for this is that implementations are allowed to return the same instance of the literal data each time it's evaluated, so in this case, with your original code, each call to init was actually returning the same list.

C. K. Young
  • 219,335
  • 46
  • 382
  • 435
  • 1
    I wonder what if it's back-quoted, 'cause back-quote *should* indicate our intention to *build* the list, maybe it *should be required* to *never* translate into quoted lists. (?) There was a question recently, in [tag:common-lisp] I think, with this problem: the backquoted list like ``(progn `(a ,x 1 2))`` got translated into something like `(list* 'a x '(1 2))` which caused problems of course. CL is closed, but maybe in Scheme/Racket it should be changed? This problem comes up so often, maybe it's best to nip it in the bud at least for the backquoted forms? – Will Ness Dec 14 '13 at 07:55
  • 3
    @WillNess Alas, that ship has already sailed. Quoting from R7RS, section 4.2.8: A quasiquote expression may return either newly allocated, mutable objects or literal structure for any structure that is constructed at run time during the evaluation of the expression. **Portions that do not need to be rebuilt are always literal.** [emphasis mine, continues] – C. K. Young Dec 14 '13 at 15:25
  • @WillNess [continued] Thus, `(let ((a 3)) \`((1 2) ,a ,4 ,'five 6))` may be treated as equivalent to either of the following expressions: `\`((1 2) 3 4 five 6)` `(let ((a 3)) (cons '(1 2) (cons a (cons 4 (cons 'five '(6))))))` However, it is not equivalent to this expression: `(let ((a 3)) (list (list 1 2) a 4 'five 6))` – C. K. Young Dec 14 '13 at 15:28
  • @WillNess To summarise the quoted section above, the behaviour you mentioned for CL is a _required_ behaviour in R7RS (i.e., not at the discretion of implementers). This should not be surprising, since Scheme is generally even more mutation-avoidant than CL is. – C. K. Young Dec 14 '13 at 17:17
  • thanks for the info, Chris. It is less convenient for sure. So if the list ends with some var, I *can* use backquote to build it easily, and if it ends with some auto-quoting entity, I must manually use list and cons etc. If set-cdr! is so frowned upon why is it still in the language? (rhetorical question) Sure isn't consistent from the user's viewpoint. Hmm. – Will Ness Dec 14 '13 at 19:41
  • @WillNess It's not as simple as that; even the "list ends with some var" case is allowed to be treated as literal, as described in the `\`((1 2) 3 4 five 6)` example, though that is implementation-specific. So to be safe, you need to always use `list-copy` (or something similar, such as a deep-copying version) if you plan to mutate the data. BTW, in Racket (`#lang racket`), conses are immutable (and `set-car!` and `set-cdr!` are not provided); it provides a new type, mcons, that people can use if they really need a mutable cons. But I very seldom see mconses being used. – C. K. Young Dec 15 '13 at 13:35
  • you're right, it should've been "lists that end with some *''anti-quoted''* var". :) And even then, some internal sublist could still get translated with a quote... It's a mess. Maybe it's a worthy request for R8RS, if ever there is one...? – Will Ness Dec 15 '13 at 19:03