Порядок определения переменных и функций

Почему это:

  1. Определения функций могут использовать определения, определенные после нее.
  2. в то время как определения переменных не могут.

Например,

а) следующий фрагмент кода неверен:

; Must define function `f` before variable `a`.
#lang racket
(define a (f)) 
(define (f) 10)

б) В то время как следующий фрагмент правильный:

; Function `g` could be defined after function `f`.
#lang racket
(define (f) (g)) ; `g` is not defined yet
(define (g) 10)

c) Правильно тоже :

; Variable `a` could be defined after function `f`
#lang racket
(define (f) a) ; `a` is not defined yet
(define a 10)

person Ben    schedule 29.10.2013    source источник


Ответы (2)


Вам нужно знать несколько вещей о Racket:

  1. В Racket каждый файл (начинающийся с #lang) представляет собой модуль, в отличие от многих (традиционных, r5rs) схем, в которых нет модулей.

  2. Правила области видимости для модулей аналогичны правилам для функции, поэтому в некотором смысле эти определения аналогичны определениям в функции.

  3. Racket оценивает определения слева направо. На схемном жаргоне вы говорите, что определения Racket имеют letrec* семантику; это отличается от некоторых схем, использующих семантику letrec, где взаимно рекурсивные определения никогда не работают.

Таким образом, суть в том, что все определения создаются в среде модуля (аналогично функции для локальных определений), а затем инициализируются слева направо. Поэтому обратные ссылки всегда работают, поэтому вы всегда можете сделать что-то вроде

(define a 1)
(define b (add1 a))

Они создаются в одной области действия, поэтому теоретически опережающие определения действительны в том смысле, что они находятся в области действия. Но на самом деле использование значения прямой ссылки не будет работать, поскольку вы получаете специальное значение #<undefined> до тех пор, пока фактическое значение не будет оценено. Чтобы увидеть это, попробуйте запустить этот код:

#lang racket
(define (foo)
  (define a a)
  a)
(foo)

Верхний уровень модуля дополнительно ограничен, так что такие ссылки на самом деле являются ошибками, что вы можете увидеть с помощью:

#lang racket
(define a a)

Имея все это в виду, все немного снисходительнее со ссылками внутри функций. Дело в том, что тело функции не выполняется до тех пор, пока функция не будет вызвана, поэтому, если прямая ссылка происходит внутри функции, она действительна (= не получит ошибку или #<undefined>), если функция все-таки вызвана. привязки были инициализированы. Это относится к простым определениям функций

(define foo (lambda () a))

определения, использующие обычный синтаксический сахар

(define (foo) a)

и даже другие формы, которые в конечном итоге расширяются в функции

(define foo (delay a))

Со всем этим вы не получите никаких ошибок по одному и тому же правилу — когда все использования тел функций происходят после инициализации определений.

Однако одно важное замечание заключается в том, что не следует путать этот вид инициализации с присваиванием. Это означает, что такие вещи, как

(define x (+ x 1))

не эквивалентны x = x+1 в основных языках. Они больше похожи на какие-то var x = x+1 в языке, который завершится ошибкой "ссылка на неинициализированную переменную". Это связано с тем, что define создает новую привязку в текущей области, а не "модифицирует" существующую.

person Eli Barzilay    schedule 29.10.2013
comment
как взаимные рекурсивные определения могут никогда не работать в обычной схеме letrec? Что значит никогда? - person Will Ness; 29.10.2013
comment
Никогда не работает - это ценное использование, см. Режим Racket R5RS; да, слева направо обычно означает сверху вниз. - person Eli Barzilay; 29.10.2013
comment
так что ваш ответ касается чрезмерно технического описания особенностей того, как Racket реализует то, что я описал в общих чертах. - person Will Ness; 29.10.2013
comment
Вопрос был о Racket - я более подробно объяснил, как область действия и ссылка работают в Racket. Я не знаю, что можно назвать чрезмерно техническим, но независимо от уровня точности вы должны знать, что, если вы скажете людям, что define похож на :=, они, скорее всего, запутаются. На самом деле, избавление от иллюзии, что (define x (add1 x)), является одним из основных этапов, который должны преодолеть ученики с императивным мышлением, когда они изучают рэкет (и другие хорошие функциональные языки). - person Eli Barzilay; 29.10.2013
comment
Я ввел псевдокод :=, чтобы отделить время создания значения от времени присвоения значения (привязки). Это образ, который обычно вызывает :=. Я специально сказал, что для оценки RHS необходимо, чтобы все значения уже были определены на тот момент, чтобы полностью исключить проблему x := x+1 (либо это тот же x и он не определен, либо это новое, затеняющее x). Короче говоря, нет никаких серьезных проблем с моим ответом. - person Will Ness; 29.10.2013
comment
Вы сводите на нет два термина, которые различаются в рэкете: определения и воплощения. Определений не бывает, это синтаксические свойства, поэтому все они присутствуют в коде; но экземпляры являются последовательными. Чтобы увидеть разницу, см. ошибку #lang racket (define a (+ 1 2)) (define + *) a. В любом случае, я не заинтересован в том, чтобы превращать это в соревнование по писсингу, так что я остановлюсь на этом. - person Eli Barzilay; 29.10.2013
comment
У меня нет ответа на это, но ты сказал мне заткнуться, так что, думаю, все в порядке. - person Will Ness; 29.10.2013

Ниже приводится примерное общее описание Схемы, аналогия.

Определение функции

(define (f) (g))

более-менее похоже

f := (lambda () (g))

поэтому вычисляется лямбда-выражение, и результирующий функциональный объект (обычно замыкание) сохраняется в новой определяемой переменной f. Функция g должна быть определена когда будет вызываться функция f.

Точно так же (define (h) a) похож на h := (lambda () a), поэтому только при вызове функции h будет проверяться ссылка на переменную a, чтобы найти ее значение.

Но

(define a (f))

как

a := (f)

т. е. функция f должна вызываться без аргументов, а результат этого вызова сохраняется в новой определяемой переменной a. Таким образом, функция должна быть определена уже в этот момент.

Каждое определение в файле выполняется последовательно, одно за другим. Каждое определение может ссылаться на любую переменную, определяемую в файле, как выше, так и ниже него (говорят, что все они принадлежат к одной и той же области), но разрешено использовать значения только тех переменных, которые определены над ним.

(здесь есть двусмысленность: представьте, что вы используете встроенную функцию, скажем, с (define a (+ 1 2)), но также определяете ее позже в файле, скажем, (define + -). Это определение, или переопределение? В первом случае, который является выбором Racket, использование перед определением запрещено. Во втором "глобальное" значение используется при вычислении значения a, а затем функция переопределено. Некоторые схемы могут пойти по этому пути. Спасибо Эли Барзилаю за то, что он показал мне это, и Крису Джестер-Янгу за помощь).

person Will Ness    schedule 29.10.2013
comment
В этом ответе также есть некоторые проблемы, некоторые из которых похожи на ответ Валентина (закрытие не имеет значения, здесь проблемы связаны с объемом и оценкой). Две важные проблемы: вам не следует сравнивать define в Racket (и других схемах) с назначением — они очень отличаются. Другая вопиющая проблема заключается в том, что последовательный порядок определений не имеет значения (например, некоторые схемы также запрещают обратные ссылки) и не всегда верен (например, при наличии синтаксических определений). Кроме того, модуль Racket представляет собой своего рода область закрытого мира, чтобы избежать некоторых проблем с последовательной оценкой в ​​​​стиле REPL. - person Eli Barzilay; 29.10.2013
comment
Вы имели в виду, что ссылки forward могут быть запрещены в некоторых схемах? re closures: я мог бы сказать функциональные объекты, это не имеет значения; Я никогда не говорил, что замыкания имеют какое-то значение; Мне пришлось использовать некоторое имя. При повторном назначении определяется функциональный объект, и привязка в среде получает его как значение. Вот что я имел в виду под :=; опять неуместная деталь; время имеет значение. По-видимому, модель оценки, которую я использовал, является тем, что здесь происходит; могло быть так, что весь файл обрабатывается 1-м, поэтому (define a (f)) работает; тогда ОП не пришлось бы задавать свой вопрос. - person Will Ness; 29.10.2013
comment
@EliBarzilay, и в Q не упоминались определения синтаксиса. - person Will Ness; 29.10.2013
comment
Да, прямые ссылки можно запретить, но интереснее то, что обратные есть (попробуйте #lang r5rs в рэкете, чтобы убедиться в этом); замыкания не важны, так как у вас может быть другая реализация оценки - очень известная модель - это модель замещения, в которой замыканий нет, но объем остается прежним; время важно в странном смысле, поскольку Scheme обычно пытается его избежать; и, наконец, вопрос касался области действия и ссылок на идентификаторы — и все это во многом связано с синтаксисом! - person Eli Barzilay; 29.10.2013
comment
Во второй раз я никогда не говорил, что замыкания важны. -- Предупреждение о движущихся стойках ворот: сначала упоминайте определения синтаксиса конкретно, а затем говорите о синтаксисе в целом -- время решает все: и каждый идентификатор устанавливается сразу после оценки соответствующего val-expr. -- если прямые ссылки запрещены, модульное программирование невозможно. (? мы никогда не говорили о REPL, по крайней мере, я не говорил; если некоторые REPL запрещают форвардные ссылки, это не входит в рамки этого вопроса) - person Will Ness; 29.10.2013
comment
@EliBarzilay Точно так же, если обратные ссылки запрещены, мы не сможем использовать ни одну из функций, определенных выше или где-либо еще. - person Will Ness; 29.10.2013
comment
Чтобы узнать об отсутствии прямых ссылок, см. (oca)ml; что касается вашей ссылки на руководство по ракетке, это неправильное место - посмотрите дальше вниз по странице в letrec, что здесь важно, там все еще есть сразу, конечно, но обратите внимание на отличия от let; REPL только в контексте того, почему Racket решил отклониться от традиционного верхнего уровня r5rs с другими правилами области видимости; и повторите обратные ссылки, попробуйте это в Racket: #lang r5rs (define (foo) (define a 1) (define b a) b) и посмотрите, что возвращает (foo). - person Eli Barzilay; 29.10.2013
comment
@EliBarzilay, откуда я скопировал цитату. запись для letrec. - person Will Ness; 29.10.2013
comment
Я имел в виду сравнение описания let с описанием letrec; см. также разницу между letrec и letrec* в r6rs. - person Eli Barzilay; 29.10.2013
comment
Меня здесь не интересуют r6rs; Я уже сравнивал let и letrec в Racket и знаю, как это работает в r5rs. - person Will Ness; 29.10.2013
comment
Внутренние определения Racket используют его letrec, который похож на letrec* r6rs — в отличие от многих схем, которые используют letrec r6rs для внутренних определений. - person Eli Barzilay; 29.10.2013
comment
Зачем мне заниматься letrec r6rs, если у меня уже есть документация для letrec Racket? - person Will Ness; 29.10.2013
comment
Потому что разница между ними будет поучительной. Но опять же, я остановлюсь здесь, так как это превращается в какое-то странное соревнование по писсингу. - person Eli Barzilay; 29.10.2013