Почему пусть предпочтительнее определять в схеме?
Я всегда писал свои процедуры схемы (и видел их написанными) так:
(define (foo x)
(let ((a ...))
((b ...))
...))
Один из моих студентов написал: {[4]]}
(define (foo x)
(define a ...)
(define b ...)
...)
Оба дают одинаковые результаты. Я понимаю разницу в поведении: первый создает новый фрейм, который указывает на фрейм приложения процедуры, в то время как последний непосредственно изменяет фрейм приложения процедуры. Последнее дало бы несколько лучшую производительность.
Другое отличие состоит в том, что первый избегает использования неявногоbegin
перед последовательностью инструкций в теле процедуры.
Почему прежний стандартный стиль?
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*
, конечно).