listcopyschemeracket

Copy of a list (or something else) in Scheme


I'm newbie in Scheme and find out that if I change a list with set-car!/set-cdr! (even locally) the parent list is modified too. This is an example what I mean:

(define my-list '(1 2 3 4 5)) ; Original list

(define (function list) ; Some example function
   (let ((copy list))
        (set-car! copy 'new)
   (display copy)
)

(function my-list); will display (new 2 3 4 5)

my-list; but the original is changed "forever" and will be also '(new 2 3 4 5)


My question is: Is there a way to make a copy of the original list and work only on it, so at the end the original not to be changed?


Solution

  • Your code (let ((copy list)) allows list to be accessed through the name copy. So when you set-car! copy, you are actually set-car!'ing the original list.

    The phenomenon of mutation in lisp like languages is a little confusing at first.

    Mutation is to be avoided usually, for the reasons you have discovered. Parts of lists and things a floating around. This is because internally each node in a list has two parts - the car is it's value which may point to another list, and it's cdr is the part that follows it - usually this is a lit of things similar to the value in car.

    This solves your problem using SRFI1's list-copy function (let ((copy (list-copy list)))

    Here is one way to copy a list (make a new list). This code is not really complete. When we look at it internally it adds pieces of a list in each recursive call. Where do the pieces of each new list-section come from? The cdr is generated with the call (list-copy (cdr list)), while the car is simply taken - not copied! - from the other list.

    (define (list-copy list)
      (if (null? list) '() (cons (car list) (list-copy (cdr list)))))
    

    The result for you when experimenting, was that the car's of your list copy where borrowed from my-list.

    Here is a more proper version:

    (define (full-copy list)
    (if (null? list) 
      '() 
      (if (list? list) 
          (cons (full-copy (car list)) (full-copy (cdr list)))
          list)))
    

    Only the code for the car has changed. Now, the car is reconstructed to. The only parts that are borrowed are the numbers (so this copy is not okay when the numbers are instead something more special). I'm not sure how the srfi1 version works.

    The let statement only ever binds one identifier to an object - the object is not copied, you simply have another way to access it. So any changes (use of mutation, as in functions that end in "!") will affect both.