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

Из REBOL / Core Users Guide и Что такое красный, я узнал, что и Rebol, и Red используют определение области действия < / сильный>.

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

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

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


person Wayne Cui    schedule 23.02.2014    source источник


Ответы (3)


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

Возьмем этот код:

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.

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

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

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

Теперь с Rebol вы получите тот первый результат. Но у 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, привязку и все полезности. Когда он определяет вещи с помощью интерпретатора, он также выполняет определение области видимости.

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

person BrianH    schedule 23.02.2014
comment
Спасибо BrianH за прекрасное объяснение. Но было бы лучше использовать make object! [] или context вместо object [], чтобы он тоже был совместим с Rebol2? - person endo64; 24.02.2014
comment
Черт, я думал, что это вошло в функции backported. Я переключу его на context и перенесу комментарий о том, что это функция, чтобы вместо этого говорить о func. - person BrianH; 24.02.2014
comment
Это самое четкое описание по теме на время! Спасибо BrianH за приложенные усилия. Думаю, упоминание хорошо известного, более подробного, но сложного для восприятия Ладислава rebol.net/wiki/Bindology - person onetom; 07.06.2014

Это старый вопрос, и ответ @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
comment
Ха, слово «это» связано именно с этой страницей. Я должен быть более точным. - person klausnrooster; 29.05.2016
comment
@klausnrooster ах, трудно увидеть ссылку (и не отображается в уведомлениях)! Ну, это я тоже написал ... :-) - person HostileFork says dont trust SE; 30.05.2016
comment
Не пытайтесь исправить то, что в корне сломано, это все, что я хочу сказать. - person Maarten Bodewes; 29.05.2018
comment
@MaartenBodewes Рисование в корне нарушено, и мы все должны использовать SolidWorks, имея все уравнения и симметрия зафиксирована, чтобы ничего не рассинхронизировалось? Думаю, тогда никто не должен тратить свою жизнь на галереи или литературу. Или, может быть, всем, кто занимается искусством, стоит подумать, что M.C. Эшер - вершина этого ... и / или все делать на Haskell? Программирование - это искусство, и изучение его более высоких уровней во многом похоже на изучение того, как нарушать правила в другом искусстве. Может быть, когда-нибудь вы туда доберетесь. :-P - person HostileFork says dont trust SE; 29.05.2018
comment
Живопись не является принципиально нарушенной, и Эшер, конечно, не ее вершина. Нарушение правил - это хорошо, если вы занимаетесь информатикой, но для общего программирования вы должны стараться придерживаться структуры, которую определяет язык, насколько это возможно. Выход за его пределы приведет к (незаметным) ошибкам и неподдерживаемому коду. Но да, это только мое мнение. - person Maarten Bodewes; 29.05.2018

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

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

но,

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

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

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

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

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

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

person paul tarvydas    schedule 28.04.2021