Каковы различия между "let" или "letrec" и "define" для создания локальных привязок?
Я не понимаю, в чем различия (извините за надуманный пример):
(define average
(lambda (elems)
(define length
(lambda (xs)
(if (null? xs)
0
(+ 1 (length (cdr xs))))))
(define sum
(lambda (xs)
(if (null? xs)
0
(+ (car xs) (sum (cdr xs))))))
(define total (sum elems))
(define count (length elems))
(/ total count)))
а также
(define average
(lambda (elems)
(letrec ((length
(lambda (xs)
(if (null? xs)
0
(+ 1 (length (cdr xs))))))
(sum
(lambda (xs)
(if (null? xs)
0
(+ (car xs) (sum (cdr xs))))))
(total (sum elems))
(count (length elems)))
(/ total count))))
Насколько я могу судить, они оба создают новую область, и в этой области создают 4 локальные переменные, которые ссылаются друг на друга и на себя, а также оценивают и возвращают тело.
Я что-то здесь упускаю или letrec
синоним области видимости define
s?
Я знаю, что это может зависеть от реализации; Я пытаюсь понять основы Lisps.
2 ответа
Вы правы, что есть параллели между define
а также letrec
версии вашего кода. Однако дьявол кроется в деталях. В R5RS, внутренний define
имеет letrec
семантика. В R6RS, внутренний define
имеет letrec*
семантика.
Какая разница? Ваш код на самом деле только что подчеркнул эту разницу. Как упоминает ответ Чжэхао, ваше определение total
а также count
внутри того же letrec
как length
а также sum
это неверно: length
а также sum
не гарантируется привязка ко времени, когда вы оцениваете значения (length elems)
а также (sum elems)
, поскольку привязка этих переменных не гарантируется слева направо.
letrec*
похож на letrec
, но с гарантией слева направо. Так что если вы изменили letrec
в letrec*
было бы хорошо.
Теперь вернемся к моему первоначальному комментарию: потому что внутренний R5RS define
использования letrec
семантика, даже ваша define
версия кода была бы неправильной при реализации R5RS, но это было бы хорошо при реализации R6RS, которая имеет letrec*
семантика.
Обе команды let/letrec и define создадут определения в локальной области. Однако let/letrec удобнее, когда вы не внутри и неявно начинаете оператор. Например, следующий код использует макрос let.
(define (test) (let ((x 1)) x))
Тот же код, использующий локально определенные определения, будет
(define test (lambda () (define x 1) x))
Это своего рода надуманный пример, но обычно использование макросов let для выполнения локальных привязок считается более функциональным.
Кроме того, ваш пример кода не использует letrec правильно. Вам не нужны определения внутри объявлений letrec (на самом деле их там не должно быть).