Search code examples
schemerackethtdp

Idiomatic usage of local vs lambda?


In Exercise 30.1.1 of HtDP, I started off using local and then modified it to use lambda in order to answer the question.

(define (add-to-each2 accu a-list)
  (cond
    [(empty? a-list) empty]
    [else (local ((define s (+ accu (first a-list))))
            (cons s (add-to-each2 s (rest a-list))))]))

and

(define (add-to-each5 accu a-list)
  (cond
    [(empty? a-list) empty]
    [else (cons ((lambda (x y)
                   (first (map + (list (first y))
                               (list x)))) accu a-list)
                (add-to-each5 (+ accu (first a-list))(rest a-list)))]))

In this particular instance, to me, the local version is easier to read. Are there situations where the lambda version would be preferred? Thank you.


Solution

  • First off, I think you might be getting relative-2-absolute confused with add-to-each, since add-to-each just adds the same number to each element of the list, rather than incrementing an accumulator. The rest of this post assumes that's the case, and just takes out that incrementing.

    I think let would be my first choice for the local binding. Your lambda example uses a common pattern that simulates let using lambda and application:

    (let ([x e]) body)
    

    Is equivalent to:

    ((lambda (x) body) e)
    

    If you use this transformation from lambda to let in your example, you get:

    (define (add-to-each5 n a-list)
      (cond
        [(empty? a-list) empty]
        [else (cons (let ([x n] [y a-list])
                      (first (map + (list (first y))
                                  (list x))))
                    (add-to-each5 n (rest a-list)))]))
    

    A good compiler will likely generate the same code for this as for your two examples, so it mostly comes down to style. The "left-left lambda" pattern can be more difficult to read, as you note, so I prefer let.

    However, Exercise 30.1.1 is trying to make you use map as a replacement for the explicit recursion that occurs in each of your examples. You are using map in your example but only for a single addition at a time, which makes map kind of painful: why bother wrapping up (list (first y)) and (list x) when you just want (+ (first y) x)?

    Let's look at a simple definition of map to see how it might be helpful, rather than painful, for this problem:

    (define (map f ls)
      (cond
        [(empty? ls) empty]
        [else (cons (f (first ls)) (map f (rest ls)))]))
    

    Right away, you should notice some similarities to add-to-each: the first line of the cond checks for empty, and the second line conses something to do with the first element onto a recursive call to map on the rest. The key, then, is to pass map an f that does what you want to do to each element.

    In the case of add-to-each, you want to add a particular number to each element. Here's an example of adding 2:

    > (map (lambda (n) (+ 2 n)) (list 1 2 3 4 5))
    (3 4 5 6 7)
    

    Notice that map and lambda are both here as 30.1.1 requests, and they act on an entire list without the explicit recursion of the original add-to-each: the recursion is all abstracted away in map.

    This should be enough to get you to a solution; I don't want to give away the final answer, though :)