liangxingguang
liangxingguang

Reputation: 29

why set-car! implement like this does not work

In Racket, there is not a set-car! function, and I want to implement it like this:

(define myset-car!
  (lambda (list value)
    (if (not (list? list))
        #f
        (set! list (cons value (cdr list))))))

But it does not work, it can't change the value of a list if I use it like this:

 (define p (list 2 3 4))
 (myset-car! p 'a)
 p  ; p still is (2 3 4)

Can anyone tell me why? what's wrong with what I do?

Upvotes: 3

Views: 1241

Answers (3)

uselpa
uselpa

Reputation: 18917

Nobody suggested macros yet, so here's a solution that would work for your case:

(define-syntax-rule (myset-car! lst val)
  (if (not (list? lst))
      lst
      (set! lst (cons val (cdr lst)))))

(define p (list 2 3 4))
(myset-car! p 'a)
p 

yields

'(a 3 4)

since macros a.k.a. syntactic forms transform your source code directly without invoking procedures.

Macro expansion in Dr Racket yields (i.e. your source code gets transformed to)

(define-syntax-rule (myset-car! lst val)
  (if (not (list? lst)) lst (set! lst (cons val (cdr lst)))))

(define p (list 2 3 4))
(if (not (list? p)) p (set! p (cons 'a (cdr p))))
p

Upvotes: 2

GoZoner
GoZoner

Reputation: 70155

There are a number of ways to accomplish your need. One way is to avoid mutable operations all together. For example:

;; If 'list' is a list? then change it's first value to 'value'
;; otherwise return the list.
(define my-fancy-cons
  (lambda (list value)
    (if (not (list? list))
        list
        (cons value (cdr list)))))

(define p '(2 3 4))
(set! p (my-fancy-cons p 'a))

The function my-fancy-cons does not destructively modify its list argument. You then get to do things like:

(define p '(2 3 4))
(define px (my-fancy-cons p 'a))

and still have p unchanged. If you need to change it, do as I did in the first snippet, use set! p ...

As to you original question, within your lambda, the parameter list is local and set! only changes what list 'points' to.

Upvotes: 0

Óscar López
Óscar López

Reputation: 236004

The code in the question doesn't work because in this line:

(set! list (cons value (cdr list)))

... you're assigning a new value to the list parameter, but that's a change local to the procedure, it won't be reflected "outside" the procedure in the p variable. You're simply pointing the list parameter to a different list, but p is still pointing to the original list once the procedure returns.

In fact, set-car! has to be a primitive operation. But fear not, in Racket there is a set-mcar! and a bunch of other operations for mutable pairs, if unbound you just have to import mpair:

(require scheme/mpair)

And consistently use all of the mutable pair operations. For instance, here's how to build a mutable list:

(define mlst (mcons 1 (mcons 2 (mcons 3 '()))))

Or alternatively:

(define mlst (mlist 1 2 3))

To access elements:

(mcar mlst)
=> 1

(mcdr mlst)
=> (mcons 2 (mcons 3 '()))

And of course, to mutate elements:

(set-mcar! mlst 0)
mlst
=> (mcons 0 (mcons 2 (mcons 3 '())))

(set-mcdr! mlst '())
mlst
=> (mcons 0 '())

Upvotes: 4

Related Questions