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

Я всегда писал свои процедуры схемы (и видел их написанными) следующим образом:

(define (foo x)
  (let ((a ...))
       ((b ...))
    ...))

Один из моих учеников написал:

(define (foo x)
  (define a ...)
  (define b ...)
  ...)

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

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

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


person Ellen Spertus    schedule 18.02.2014    source источник


Ответы (2)


Это не полностью эквивалентно. define в теле процедуры больше похож на letrec, поэтому вы можете удивиться, что не можете использовать значение, привязанное в define, пока все они не будут завершены и тело процедуры не будет выполнено. Представьте, что вы хотите сделать 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* похожи, но допускают рекурсию, поэтому в лямбде вы можете вызвать один из других связанных членов или самого себя. Теперь, в зависимости от версии Scheme, которую вы используете, вы получите одно из них при написании:

(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.

person Sylwester    schedule 18.02.2014
comment
Большое спасибо. Знаете ли вы, почему define в процедуре ведет себя иначе, чем на верхнем уровне? - person Ellen Spertus; 19.02.2014
comment
Во внутренних определениях R6RS, как и в определениях R7RS, используется letrec*, а не letrec. - person Chris Jester-Young; 19.02.2014
comment
@espertus Потому что язык Scheme определяет два разных значения для define, одно для привязок верхнего уровня, а другое, по сути, для синтаксического сахара для letrec*. Это схема для вас. :-) - person Chris Jester-Young; 19.02.2014
comment
@ ChrisJester-Young исправил R6RS. Разница между верхними уровнями должна быть связана с тем, что в действительности верхний уровень - это не фрейм или только один фрейм. Определение верхнего уровня изменяет верхний фрейм, но оценивается позже, чем предыдущий define, что делает его особенным? - person Sylwester; 19.02.2014

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

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

(define (foo x)
  (letrec* ((a ...)
            (b ...))
    ...))

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

person Chris Jester-Young    schedule 18.02.2014
comment
Спасибо за исправление моего недоразумения. Мы действительно используем Racket. - person Ellen Spertus; 19.02.2014
comment
Спасибо тоже за ссылку на стиль. - person Ellen Spertus; 19.02.2014