简体   繁体   中英

What is “named let” and how do I use it to implement a map function?

I'm totally new to Scheme and I am trying to implement my own map function . I've tried to find it online, however all the questions I encountered were about some complex versions of map function (such as mapping functions that take two lists as an input).

The best answer I've managed to find is here: ( For-each and map in Scheme ). Here is the code from this question:

(define (map func lst)
  (let recur ((rest lst))
    (if (null? rest)
      '()
      (cons (func (car rest)) (recur (cdr rest))))))

It doesn't solve my problem though because of the usage of an obscure function recur . It doesn't make sense to me.

My code looks like this:

(define (mymap f L)
  (cond ((null? L) '())
    (f (car L))
    (else (mymap (f (cdr L))))))

I do understand the logic behind the functional approach when programming in this language, however I've been having great difficulties with coding it.

The first code snippet you posted is indeed one way to implement the map function. It uses a named let. See my comment on an URL on how it works. It basically is an abstraction over a recursive function. If you were to write a function that prints all numbers from 10 to 0 you could write it liks this

(define (printer x)
  (display x)
  (if (> x 0)
      (printer (- x 1))))

and then call it:

(printer 10)

But, since its just a loop you could write it using a named let:

(let loop ((x 10))
  (display x)
  (if (> x 0)
      (loop (- x 1))))

This named let is, as Alexis King pointed out, syntactic sugar for a lambda that is immediately called. The above construct is equivalent to the snippet shown below.

(letrec ((loop (lambda (x) 
              (display x)
              (if (> x 0)
                  (loop (- x 1))))))
  (loop 10))

In spite of being a letrec it's not really special. It allows for the expression (the lambda, in this case) to call itself. This way you can do recursion. More on letrec and let here .

Now for the map function you wrote, you are almost there. There is an issue with your two last cases. If the list is not empty you want to take the first element, apply your function to it and then apply the function to the rest of the list. I think you misunderstand what you actually have written down. Ill elaborate.

Recall that a conditional clause is formed like this:

(cond (test1? consequence)
      (test2? consequence2)
      (else   elsebody))

You have any number of tests with an obligatory consequence. Your evaluator will execute test1? and if that evaluated to #t it will execute the consequence as the result of the entire conditional. If test1? and test2? fail it will execute elsebody .


Sidenote

Everything in Scheme is truthy except for #f (false). For example:

 (if (lambda (x) x) 1 2) 

This if test will evaluate to 1 because the if test will check if (lambda (x) x) is truthy, which it is. It is a lambda. Truthy values are values that will evaluate to true in an expression where truth values are expected (eg, if and cond ).


Now for your cond. The first case of your cond will test if L is null. If that is evaluated to #t , you return the empty list. That is indeed correct. Mapping something over the empty list is just the empty list.

The second case ( (f (car L)) ) literally states "if f is true, then return the car of L".

The else case states "otherwise, return the result mymap on the rest of my list L ".

What I think you really want to do is use an if test. If the list is empty, return the empty list. If it is not empty, apply the function to the first element of the list. Map the function over the rest of the list, and then add the result of applying the function the first element of the list to that result.

 (define (mymap f L) (cond ((null? L) '()) (f (car L)) (else (mymap (f (cdr L)))))) 

So what you want might look look this:

 (define (mymap f L) (cond ((null? L) '()) (else (cons (f (car L)) (mymap f (cdr L)))))) 

Using an if :

 (define (mymap f L) (if (null? L) '() (cons (f (car L)) (mymap f (cdr L))))) 

Since you are new to Scheme this function will do just fine. Try and understand it. However, there are better and faster ways to implement this kind of functions. Read this page to understand things like accumulator functions and tail recursion. I will not go in to detail about everything here since its 1) not the question and 2) might be information overload.

If you're taking on implementing your own list procedures, you should probably make sure they're using a proper tail call, when possible

(define (map f xs)
  (define (loop xs ys)
    (if (empty? xs)
        ys
        (loop (cdr xs) (cons (f (car xs)) ys))))
  (loop (reverse xs) empty))

(map (λ (x) (* x 10)) '(1 2 3 4 5))
; => '(10 20 30 40 50)

Or you can make this a little sweeter with the named let expression, as seen in your original code. This one, however, uses a proper tail call

(define (map f xs)
  (let loop ([xs (reverse xs)] [ys empty])
    (if (empty? xs)
        ys
        (loop (cdr xs) (cons (f (car xs)) ys)))))

(map (λ (x) (* x 10)) '(1 2 3 4 5))
; => '(10 20 30 40 50)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM