Каковы различия между "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 (на самом деле их там не должно быть).

Другие вопросы по тегам