Есть ли общее объяснение определения определения в Rebol и Red?

Из Руководства пользователя REBOL/Core и " Что такое красный" я узнал, что как в Rebol, так и в Red используется определение объема.

Из руководства я знаю, что это форма статической области видимости, "область действия переменной определяется при определении ее контекста", она также называется лексической областью видимости во время выполнения и представляет собой динамическую форму статической области видимости, которая зависит от определений контекста.,

Я знаю, что в com-sci есть две формы определения объема: лексическая область видимости (статическая область видимости) и динамическая область видимости. Это определение определения сбило меня с толку.

Так что же такое определение объема?

3 ответа

Решение

Ребол на самом деле не имеет области видимости вообще.

Давайте возьмем этот код:

rebol []

a: 1

func-1: func [] [a]

inner: context [
    a: 2
    func-2: func [] [a]
    func-3: func [/local a] [a: 3 func-1]
]

Итак, с этим загруженным кодом, если бы у Rebol была лексическая область видимости, вот что вы увидите:

>> reduce [func-1 inner/func-2 inner/func-3]
== [1 2 1]

Это было бы потому, что func-1 использует a из внешней области видимости, a использован func-2 из внутреннего объема, и func-3 звонки func-1, который до сих пор использует a из внешней области, где он был определен независимо от того, что в func-3,

Если бы у Ребола была динамическая область видимости, это то, что вы увидите:

>> reduce [func-1 inner/func-2 inner/func-3]
== [1 2 3]

Это было бы потому, что func-3 переопределяет aзатем звонит func-1, который просто использует самое последнее активное определение a,

Теперь для Rebol, вы получите этот первый результат. Но у Реболя нет лексической области видимости. Так почему же?

Ребол подделывает это. Вот как это работает.

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

Ребол не делает ничего из этого; в частности, он не компилируется, он создается во время выполнения. То, что вы считаете кодом, на самом деле представляет собой данные, блоки слов, чисел и тому подобное. Слова - это структуры данных, в которых есть указатель, называемый "привязкой".

Когда этот сценарий загружается впервые, все слова в сценарии добавляются в объект среды сценария (который мы ненадлежащим образом называем "контекстом", хотя это не так). Пока слова собираются, данные сценария изменяются. Любое слово, найденное в "контексте" скрипта, связано с "контекстом" или "связью". Эти привязки означают, что вы можете просто перейти по этой ссылке и перейти к объекту, где хранится значение этого слова. Это действительно быстро.

Затем, как только это будет сделано, мы начнем запускать скрипт. И тогда мы перейдем к этому немного: func [] [a], Это на самом деле не декларация, это вызов функции с именем func который берет блок спецификации и кодовый блок и использует их для построения функции. Эта функция также получает свой собственный объект среды, но со словами, объявленными в спецификации функции. В этом случае в спецификации нет слов, поэтому это пустой объект. Затем блок кода привязывается к этому объекту. Но в этом случае нет a в этом объекте, поэтому ничего не делается для a, он сохраняет связывание, которое у него уже было, когда оно было связано ранее.

То же самое касается context [...] вызов - да, это вызов функции с неправильным именем context, который строит объект, вызывая make object!, context Функция берет блок данных и ищет набор слов (те, у которых есть конечные двоеточия, например a:), затем создает объект с этими словами в нем, затем он связывает все слова в этом блоке и все вложенные блоки со словами, которые находятся в объекте, в данном случае a, func-2 а также func-3, А это значит, что aВ этом блоке кода их привязки изменены, чтобы вместо этого указывать на этот объект.

когда func-2 определяется, связывание a в своем коде блок не переопределяется. когда func-3 определяется, он имеет a в его спецификации, поэтому a: имеет привязку переопределена.

Самое смешное во всем этом то, что нет никаких областей вообще. Это первое a: и a в func-1Тело кода привязано только один раз, поэтому оно сохраняет свою первую привязку. a: в innerкодовый блок а a в func-2Связаны дважды, поэтому они сохраняют второе связывание. a: в func-3Код привязан три раза, поэтому он также сохраняет свою последнюю привязку. Это не области видимости, это просто привязка кода, затем привязка меньших фрагментов кода, и так далее, пока это не будет сделано.

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

Это действительно становится другим, когда вы понимаете, что эти привязки являются прямыми, и вы можете изменить их (вроде, вы можете создавать новые слова с тем же именем и с другой привязкой). Та же самая функция, которую вызывают эти функции определения, вы можете вызвать сами: она называется bind, С bind Вы можете разрушить иллюзию области видимости и создать слова, которые связывают любой объект, к которому у вас есть доступ. Вы можете делать замечательные трюки с bind, даже сделать свои собственные функции определения. Это очень весело!

Что касается Red, Red является компилируемым, но он также включает в себя интерпретатор, похожий на Rebol, связывание и все вкусности. Когда он определяет вещи с помощью интерпретатора, он также определяет определение.

Это помогает прояснить ситуацию?

Это старый вопрос, и ответ @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 ничего бы не сделал, и он был бы перезаписан.

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

FUN FACT: Так как если вы не предоставите параметры для спецификации 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 foreach за for-each а также length? за length )

>> collect [
       keep 10
       for-each item [a [b c] [d e f]] [
           either all [
               block? item
               3 = length 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 находится рядом с ДЕРЖАТЬ в спецификации. Это происходит потому, что COLLECT пытается сообщить FUNC, что он хочет "использовать свои сервисы" в качестве связующего и исполняющего кода. Но тело уже было создано кем-то другим. Так что, если в нем есть ВОЗВРАТ, тогда этот ВОЗВРАТ уже имеет представление о том, куда вернуться. 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].

И есть немного о "почему" определения сферы действия, а не просто "что".:-)

Насколько я понимаю:

Rebol имеет статический прицел

но,

Вопрос не в том, «какую область видимости использует Rebol?», А в том, «когда определяется область действия Rebol, когда компилируется программа Rebol?».

Rebol имеет статическую область видимости, но динамическую компиляцию.

Мы привыкли к одному времени компиляции и одному времени выполнения.

Rebol имеет несколько раз компиляции.

Компиляция кода Rebol зависит от контекста, существующего во время компиляции.

Код Rebol компилируется в разное время в разных контекстах. Это означает, что функции Rebol могут компилироваться по-разному в разное время.

Другие вопросы по тегам