Почему пусть предпочтительнее определять в схеме?


Я всегда писал свои процедуры схемы (и видел их написанными) так:

(define (foo x)
  (let ((a ...))
       ((b ...))
    ...))
Один из моих студентов написал: {[4]]}
(define (foo x)
  (define a ...)
  (define b ...)
  ...)

Оба дают одинаковые результаты. Я понимаю разницу в поведении: первый создает новый фрейм, который указывает на фрейм приложения процедуры, в то время как последний непосредственно изменяет фрейм приложения процедуры. Последнее дало бы несколько лучшую производительность.

Другое отличие состоит в том, что первый избегает использования неявного begin перед последовательностью инструкций в теле процедуры.

Почему прежний стандартный стиль?

2 5

2 ответа:

Это не совсем эквивалентно. define в теле процедуры больше похоже на letrec, поэтому вы можете удивиться, что не можете использовать значение, связанное в define, прежде чем все они закончатся и тело процедуры будет выполнено. Представьте, что вы хотите сделать do x + y * z:

(define (test x y z)
  (let ((ytimesz (* y z)))
     (let ((sum (+ x ytimesz)))
       (dosomething sum))))

Причина, по которой у вас есть вложенный let здесь, заключается в том, что ytimesz не может быть доступен в том же let, как это сделано. У нас есть еще одна специальная форма для этого let*

(define (test x y z)
  (let* ((ytimesz (* y z)) (sum (+ x ytimesz)))
       (dosomething sum)))

letrec и letrec* похожи, но допускает рекурсию, так что в лямбде вы можете вызвать один из других связанных членов или самого себя. Теперь, в зависимости от версии схемы, которую вы используете, вы получите один из них при написании:

(define (test x y z)
  (define ytimesz (* y z))
  (define answer (+ x ytimesz)) ;might work, might not
  (dosomething answer))

В #!R7RS, #!R6RS и #!Racket это совершенно нормально, так как это определяется как letrec*.

В #!R5RS, однако, это не будет работать вообще. Перезапись выполняется в виде letrec и инициализирует все переменные (ytimesz и answer) неопределенным значением, а затем присваивает оценку выражений временные переменные перед set! - приведением переменных к значениям временных значений, чтобы убедиться, что все использование любого из них заканчивается как неопределенные значения и некоторые даже ошибки сигнала (Racket делает в r5rs-режиме. Для лямбда-выражений, где привязки в теле вычисляются во время вызова, это не проблема, и это для этих letrec и внутренних define изначально предназначено.

Я использую define для хранения простых значений и процедур. Во-вторых, я думаю, что мне нужно использовать предварительно вычисленное значение Я мог бы переписать все это на let* или объединить define и простой let.

На самом деле, оба стиля хороши. На самом деле, некоторые люди предпочитают использовать внутренние определения.

Кроме того, последнее не обязательно "непосредственно изменяет фрейм приложения процедуры"; внутренние определения обрабатываются так же, как letrec (для систем, совместимых с R5RS) или letrec* (для систем, совместимых с R6RS и R7RS). Таким образом, ваш второй пример действительно такой же, как:
(define (foo x)
  (letrec* ((a ...)
            (b ...))
    ...))

Фактически, чтобы использовать пример, Racket переписывает внутренние определения в их эквивалент letrec* выражений и, таким образом, нет никакой разницы в производительности (за исключением любого различия между let и letrec*, конечно).