Идиоматическое использование местного против лямбды? - PullRequest
3 голосов
/ 19 января 2011

В В упражнении 30.1.1 HtDP я начал с использования local, а затем изменил его на lambda, чтобы ответить на вопрос.

(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))))]))

и

(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)))]))

В данном конкретном случае мне легче читать версию local. Существуют ли ситуации, когда версия lambda предпочтительнее? Спасибо.

1 Ответ

3 голосов
/ 19 января 2011

Во-первых, я думаю, вы, возможно, путаете relative-2-absolute с add-to-each, поскольку add-to-each просто добавляет одно и то же число к каждому элементу списка, а не увеличивает накопитель. Остальная часть этого поста предполагает, что это так, и просто снимает это увеличение.

Я думаю let будет моим первым выбором для локального связывания. В вашем примере lambda используется общий шаблон, который имитирует let с использованием lambda и приложение:

(let ([x e]) body)

Эквивалентно:

((lambda (x) body) e)

Если вы используете это преобразование из lambda в let в вашем примере, вы получите:

(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)))]))

Хороший компилятор, вероятно, сгенерирует тот же код для этого, что и для двух ваших примеров, так что в основном все сводится к стилю. Как видите, шаблон «левый-левый lambda» может быть более трудным для чтения, поэтому я предпочитаю let.

Однако упражнение 30.1.1 пытается заставить вас использовать map вместо явной рекурсии, которая встречается в каждом из ваших примеров. Вы используете map в своем примере, но только для одного добавления за раз, что делает map болезненным: зачем беспокоиться об окончании (list (first y)) и (list x), когда вы просто хотите (+ (first y) x)?

Давайте посмотрим на простое определение map, чтобы увидеть, как оно может быть полезным, а не болезненным, для этой проблемы:

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

Сразу же вы должны заметить некоторые сходства с add-to-each: первая строка cond проверяет наличие пустых значений, а вторая строка cons относится к элементу first для рекурсивного вызова map на rest. Ключом является передача map f, который делает то, что вы хотите сделать, с каждым элементом.

В случае add-to-each вы хотите добавить конкретный номер к каждому элементу. Вот пример добавления 2:

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

Обратите внимание, что и map, и lambda находятся здесь как 30.1.1 запросов, и они действуют на весь список без явной рекурсии исходного add-to-each: рекурсия все абстрагируется в map.

Этого должно быть достаточно, чтобы найти решение; Я не хочу давать окончательный ответ, хотя :)

...