Хороший вопрос.
Код Rebol на самом деле лучше всего рассматривать как очень стилизованную структуру данных. Эта структура данных «оказывается исполняемой». Но нужно понимать, как это работает.
Например, из предложения @WiseGenius:
make-password: func[Length] [
chars: "QWERTYUIOPASDFGHJKLZXCVBNM1234567890"
password: copy ""
loop Length [append password (pick chars random Length)]
password
]
Взгляните на блок, содержащий append password...
. Этот блок «отображен» там; как это на самом деле выглядит под капотом:
chars: **pointer to string! 0xSSSSSSS1**
password: copy **pointer to string! 0xSSSSSSS2**
loop Length **pointer to block! 0xBBBBBBBB**
password
Так работают все серии, когда они загружаются интерпретатором. Строки, блоки, двоичные файлы, пути, круглые скобки и т. д. Учитывая, что это «черепахи до конца», если вы будете следовать этому указателю, блок 0xBBBBBBBB внутренне:
append password **pointer to paren! 0xPPPPPPPP**
Одним из результатов этого является то, что на серию можно ссылаться (и, следовательно, «изображать») в нескольких местах:
>> inner: [a]
>> outer: reduce [inner inner]
[[a] [a]]
>> append inner 'b
>> probe outer
[[a b] [a b]]
Это может быть источником путаницы для новичков, но как только вы поймете структуру данных, вы начнете понимать, когда использовать COPY.
Итак, вы заметили интересное следствие этого с функциями. Рассмотрим эту программу:
foo: func [] [
data: []
append data 'something
]
source foo
foo
foo
source foo
Это дает, возможно, неожиданный результат:
foo: func [][
data: []
append data 'something
]
foo: func [][
data: [something something]
append data 'something
]
Мы вызываем foo
пару раз, оказывается, что исходный код функции меняется по мере того, как мы это делаем. В некотором смысле это самомодифицирующийся код.
Если вас это беспокоит, в R3-Alpha есть инструменты для его атаки. Вы можете использовать PROTECT для защиты тел функций от изменений и даже создавать свои собственные альтернативы подпрограммам, таким как FUNC и FUNCTION, которые сделают это за вас. (PFUNC? PFUNCTION?) В Rebol версии 3 вы можете написать:
pfunc: func [spec [block!] body [block!]] [
make function! protect/deep copy/deep reduce [spec body]
]
foo: pfunc [] [
data: []
append data 'something
]
foo
Когда вы запускаете это, вы получаете:
*** ERROR
** Script error: protected value or series - cannot modify
** Where: append foo try do either either either -apply-
** Near: append data 'something
Так что это заставляет вас копировать серию. Это также указывает на то, что FUNC — это просто функция! сама по себе, как и ФУНКЦИЯ. Вы можете сделать свои собственные генераторы.
Это может сломать вам мозг, и вы можете начать кричать, что «это не самый разумный способ писать программы». Или, может быть, вы скажете: «Боже мой, он полон звезд». Реакции могут быть разными. Но это довольно фундаментальный «трюк», который приводит систему в действие и придает ей невероятную гибкость.
(Примечание: ветвь Rebol3 Ren-C принципиально сделала так, чтобы функция тела — и исходные серии в целом — заблокированы по умолчанию. Если вам нужна статическая переменная в функции, вы можете сказать foo: func [x <static> accum (copy "")] [append accum x | return accum]
, и функция будет накапливать состояние в accum
между вызовами.) эм>
Я также предлагаю обратить пристальное внимание на то, что на самом деле происходит при каждом запуске. Пока вы не запустите функцию foo
, данные не имеют значения. Что происходит каждый раз, когда мы выполняем функцию, и оценщик видит SET-WORD! за которым следует значение серии, он выполняет присвоение переменной.
data: **pointer to block! 0xBBBBBBBB**
После этого назначения у вас будет две ссылки на существующий блок. Во-первых, это его существование в структуре кода, которая была установлена во время ЗАГРУЗКИ, еще до того, как функция была запущена. Вторая ссылка — это та, которая была сохранена в переменной данных. Именно через эту вторую ссылку вы изменяете эту серию.
Обратите внимание, что данные будут переназначаться каждый раз при запуске функции. Но переназначается на одно и то же значение снова и снова... этот исходный указатель блока! Вот почему вам нужно КОПИРОВАТЬ, если вы хотите получать новый блок при каждом запуске.
Понимание простоты, лежащей в основе правил оценщика, является частью головокружительного интереса. Вот как простота была украшена, чтобы сделать язык (таким образом, который вы могли бы использовать по своему усмотрению). Например, нет «множественного назначения»:
a: b: c: 10
Это просто оценщик нажимает a: как SET-WORD! символ и говоря: «Хорошо, давайте свяжем переменную a в ее контексте привязки с тем, что производит следующее полное выражение». b: делает то же самое. c: делает то же самое, но достигает терминала из-за целочисленного значения 10... а затем тоже вычисляет значение 10. Таким образом, это выглядит как множественное присваивание.
Так что просто помните, что исходный экземпляр последовательного литерала висит в загруженном исходном коде. Если оценщик когда-нибудь соберется сделать такое УСТАНОВЛЕННОЕ СЛОВО! или SET, он позаимствует указатель на этот литерал в источнике, чтобы вставить его в переменную. Это изменяемая ссылка. Вы (или созданные вами абстракции) можете сделать его неизменным с помощью PROTECT или PROTECT/DEEP, и вы можете сделать его не-ссылочным с помощью COPY или COPY/DEEP.
Примечание по теме
Некоторые утверждают, что вы никогда не должны писать copy []... потому что (а) у вас может появиться привычка забывать писать КОПИЮ, и (б) вы создаете неиспользованную серию каждый раз, когда вы сделай это. Этот «пустой шаблон серии» выделяется, должен быть просканирован сборщиком мусора, и никто никогда его не трогает.
Если вы пишете сделайте блокировку! 10 (или любого другого размера, который вы хотите предварительно выделить блоку), вы избегаете проблемы, сохраняете серию и предлагаете подсказку по размеру.
person
HostileFork says dont trust SE
schedule
20.09.2014