Это старый вопрос, и ответ @BrianH здесь очень подробно описывает механику. Но я подумал, что дам один с немного другой направленностью, более похожий на «рассказ».
В Rebol есть категория типов под названием слова. По сути, это символы, поэтому их строковое содержимое сканируется, и они попадают в таблицу символов. Таким образом, в то время как "FOO"
будет строкой, а <FOO>
будет еще одной «разновидностью» строки, известной как тег ... FOO
, 'FOO
, FOO:
и :FOO
- все это различные «разновидности» слов с одним и тем же идентификатором символа. («слово», «буквенное слово», «установочное слово» и «полученное слово» соответственно.)
Свертывание до символа делает невозможным изменение имени слова после загрузки. Они застревают по сравнению со строками, каждая из которых имеет свои собственные данные и является изменяемой:
>> append "foo" "bar"
== "foobar"
>> append 'foo 'bar
** Script error: append does not allow word! for its series argument
Неизменяемость имеет преимущество в том, что как символ можно быстро сравнить одно слово с другим. Но есть еще одна часть головоломки: каждый экземпляр слова может дополнительно иметь невидимое свойство, называемое привязкой. Эта привязка позволяет ему «указывать» на сущность типа ключ / значение, известную как контекст, где значение может быть прочитано или записано.
Примечание: в отличие от @BrianH я не думаю, что называть эту категорию целей привязки «контекстами» так уж плохо - по крайней мере, сегодня я так не думаю. Спросите меня позже, я могу передумать, если появятся новые доказательства. Достаточно сказать, что это объектная вещь, но не всегда объект ... это может быть, например, ссылка на фрейм функции в стеке.
Тот, кто вводит слово в систему, получает первый шанс сказать, к какому контексту оно привязывается. В большинстве случаев это ЗАГРУЗКА, поэтому, если вы скажете load "[foo: baz :bar]"
и вернете блок из 3 слов [foo: baz :bar]
, они будут привязаны к «контексту пользователя» с откатом к «контексту системы».
Все работает по привязке, и каждый «вкус» слова делает что-то свое.
>> print "word pointing to function runs it"
word pointing to function runs it
>> probe :print "get-word pointing to function gets it"
make native! [[
"Outputs a value followed by a line break."
value [any-type!] "The value to print"
]]
== "get-word pointing to function gets it"
Примечание. Во втором случае эта строка не выводилась. Он исследовал спецификацию функции, затем строка была последней вещью в оценке, поэтому она была оценена как это.
Но когда у вас в руках есть блок данных со словами, привязки становятся чьей-либо игрой. Пока в контексте есть символ слова, вы можете перенаправить это слово на этот контекст. (Предполагая также, что блок не был защищен или заблокирован от модификации ...)
Эта каскадная цепочка возможностей повторного связывания является важным моментом. Поскольку FUNC - это «генератор функций», который принимает спецификацию и тело, которое вы ему даете, он имеет возможность брать «необработанную материю» тела с его привязками и отменять те, которые он решит. Возможно, это жутковато, но посмотрите на это:
>> x: 10
>> foo: func [x] [
print x
x: 20
print x
]
>> foo 304
304
20
>> print x
10
Произошло то, что FUNC получил два блока, один из которых представляет список параметров, а второй - тело. Когда он получил тело, оба print
были привязаны к собственной функции печати (в этом случае - и важно отметить, что когда вы получаете материал из других мест, кроме консоли, каждый из них может быть привязан по-разному. !). x
был привязан к пользовательскому контексту (в данном случае), который содержал значение 10. Если FUNC не сделает ничего, чтобы изменить ситуацию, все так и останется.
Но он собрал картинку и решил, что, поскольку в списке параметров есть x, он просматривает тело и перезаписывает слова с идентификатором символа для x с новой привязкой ... локально для функции. Это единственная причина, по которой он не перезаписал глобальное значение с помощью x: 20
. Если бы вы пропустили [x] в спецификации, FUNC ничего бы не сделал, и он был бы перезаписан.
Каждый элемент в цепочке определений получает возможность перед тем, как передать что-либо дальше. Отсюда определение объема.
ЛЮБОПЫТНЫЙ ФАКТ: поскольку если вы не укажете параметры в спецификации FUNC, он не будет повторно связывать что-либо в теле, это привело к ошибочному впечатлению, что "все в Rebol находится в глобальной области". Но это совсем не так, потому что, как говорит @BrianH: «Rebol на самом деле вообще не имеет области видимости (...) Rebol подделывает ее». Фактически, это то, что FUNCTION (в отличие от FUNC) делает - он ищет в теле слова-множества, такие как x:, и когда видит их, добавляет их в локальный фрейм и привязывается к ним. Эффект выглядит как локальный, но опять же, это не так!
Если представить себе эти символы с переставленными невидимыми указателями немного в стиле Руба-Голдберга, потому, что это так. Лично для меня примечательно то, что он вообще работает ... и я видел, как люди проделывают с ним трюки, для которых вы даже не подумаете, что можно использовать такой простой трюк.
Показательный пример: безумно полезные COLLECT и KEEP (версия Ren-C):
collect: func [
{Evaluates a block, storing values via KEEP function,
and returns block of collected values.}
body [block!] "Block to evaluate"
/into {Insert into a buffer instead
(returns position after insert)}
output [any-series!] "The buffer series (modified)"
][
unless output [output: make block! 16]
eval func [keep <with> return] body func [
value [<opt> any-value!] /only
][
output: insert/:only output :value
:value
]
either into [output] [head output]
]
Этот скромный на вид инструмент расширяет язык в следующем стиле (опять же, версия Ren-C ... в R3-Alpha или Rebol2 замените _ 16_ для for-each
и length?
для length of
)
>> collect [
keep 10
for-each item [a [b c] [d e f]] [
either all [
block? item
3 = length of item
][
keep/only item
][
keep item
]
]
]
== [10 a b c [d e f]]
Уловка здесь с определением области видимости лучше всего понятна из того, что я упомянул выше. FUNC перезапишет только привязки вещей в своем списке параметров и оставит все остальное в теле нетронутым. Итак, что происходит, так это то, что он берет тело, которое вы передали в COLLECT, и использует его как тело новой функции, где оно перезаписывает любые привязки KEEP. Затем он устанавливает KEEP для функции, которая добавляет данные в агрегатор при вызове.
Здесь мы видим универсальность функции KEEP в том, чтобы объединять блоки в собранный вывод или нет, с помощью переключателя / ONLY (вызывающий решил не выполнять монтаж, только если мы видим элемент длиной 3). Но это только верхушка. Это всего лишь одна очень мощная языковая функция - добавленная пользователями постфактум - происходящая из такого маленького кода, что это почти пугает. Конечно, есть еще много историй.
Я здесь добавляю ответ из-за того, что заполнил важное недостающее звено для определения области действия, проблемы, известной как «возврат в области определения»:
https://codereview.stackexchange.com/questions/109443/definitional-returns-solved-mostly < / а>
Вот почему <with> return
находится рядом с KEEP в спецификации. Это происходит потому, что COLLECT пытается сообщить FUNC, что хочет «использовать свои услуги» в качестве связующего и исполняющего кода. Но тело уже было создано кем-то еще. Итак, если в нем есть RETURN, то RETURN уже знает, куда вернуться. FUNC предназначен только для «изменения области видимости» сохранения, но не добавляет никаких возвратов, а не добавляет свои собственные. Следовательно:
>> foo: func [x] [
collect [
if x = 10 [return "didn't collect"]
keep x
keep 20
]
]
>> foo 304
== [304 20]
>> foo 10
== "didn't collect"
Именно <with> return
позволяет COLLECT быть достаточно умным, чтобы знать, что внутри тела FOO ему не нужен обратный отскок, поэтому он решил вернуться из функции, параметр которой был просто [keep].
И есть немного о «почему» определения области действия, а не просто о «что». :-)
person
HostileFork says dont trust SE
schedule
02.11.2015