3

I am watching SICP video lectures and i came to a section where tutors are showing procedures to work with lists, so, here is one of them:

(define (map p l) (if (null? l) (list) (cons (p (car l)) (map p (cdr l))))) 

What i want to ask is: is there a way to define map in iterative way, or that cons requires lazy evaluation to be executed right?

2
  • What do you mean? There's no laziness involved in cons. Commented Mar 28, 2014 at 13:26
  • @molbdnilo He wants it either tail recursive or lazy like Haskell. Commented Mar 29, 2014 at 22:36

2 Answers 2

7

You original code is almost tail recursive.. the only thing that makes it not is the cons part. If Scheme had equal requirement for having TRMC optimization as it has TCO requirement you could leave your code as is and the implementation would have made it tail recursive for you.

Since it isn't a requirement we need to do our own TRMC optimization. Usually when iterating a list in a loop and having it tail recursive by using an accumulator you get the result in the opposite order, thus you can do linear update reverse:

(define (map proc lst) (let loop ((lst lst) (acc '())) (cond ((null? lst) (reverse! acc) acc) (else (loop (cdr lst) (cons (proc (car lst)) acc)))))) 

Or you can do it all in one pass:

(define (map proc lst) (define head (list 1)) (let loop ((tail head) (lst lst)) (cond ((null? lst) (cdr head)) (else (set-cdr! tail (list (proc (car lst)))) (loop (cdr tail) (cdr lst)))))) 

Now in both cases you mutate only the structure the procedure has itself created, thus for the user it might as well be implemented in the same manner as your example.

When you use higher order procedures like map from your implementation it could happen it has been implemented like this. It's easy to find out by comparing performance on the supplied map with the different implementations with a very long list. The difference between the executions would tell you if it's TRMCO or how the supplied map probably has been implemented.

Sign up to request clarification or add additional context in comments.

1 Comment

"any mention of TRMC gets an automatic upvote from me". :) Just to think that it was first described in 1974 and is continually overlooked ever since (in Scheme context)!!
2

You need to embrace recursion in order to appreciate SICP and Scheme in general, so try to get used to it, you will appreciate it later, promised.

But yes, you can:

(define (iterative-map f lst) (define res null) (do ((i (- (length lst) 1) (- i 1))) ((= i -1)) (set! res (cons (f (list-ref lst i)) res))) res) (iterative-map (lambda (x) (+ x 1)) '(1 3 5)) => '(2 4 6) 

but using set! is considered bad style if avoidable.

In Racket you have a different set of loops that are more elegant:

(define (for-map f lst) (for/list ((i lst)) (f i))) (for-map add1 '(1 3 5)) => '(2 4 6) 

7 Comments

Thank you. Don't think I have problems with recursion(I practically love it) I just was curious how to make it in another way.
You need to avoid mutation if you want to embrace Scheme. (yuck!)
@leppie There is no solution for making map tail recursive without mutation so every answer would have that.
@Sylwester: The only mutation you used was reverse! (which is safe in this case), so I dont see your point.
@leppie It's not the mutation which is bad here, it's the list-ref used to iterate the list in reverse. My point was you need mutation for an efficient map to compensate for the nature of singly linked lists. My second version uses set-cdr! and it twice as fast as the first and 10 fold OP's original example.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.