Короткий ответ заключается в том, что, как правило, нет необходимости оценивать блок в точке вызова, поскольку блоки в Rebol не принимают параметры, поэтому в основном не имеет значения, где они оцениваются. Однако это «в основном» может потребовать некоторого объяснения...
Все сводится к двум интересным особенностям Rebol: статической привязке и тому, как работает do
функции.
Статическая привязка и области видимости
Rebol не имеет привязок к словам в области видимости, у него есть статические прямые привязки к словам. Иногда кажется, что у нас есть лексическая область видимости, но на самом деле мы имитируем ее, обновляя статические привязки каждый раз, когда создаем новый блок кода с «ограниченной областью действия». Мы также можем перепривязывать слова вручную, когда захотим.
Однако в данном случае для нас это означает, что когда блок существует, его привязки и значения статичны — на них не влияет то, где физически расположен блок или где он оценивается.
Однако, и здесь все становится сложнее, контексты функций странные. В то время как привязки слов, привязанных к контексту функции, являются статическими, набор значений, назначенных этим словам, динамически ограничен областью действия. Это побочный эффект того, как код оценивается в Rebol: то, что является языковыми операторами в других языках, является функциями в Rebol, поэтому вызов if
, например, фактически передает блок данных в функцию if
, которую if
затем передает do
. Это означает, что во время выполнения функции do
должен искать значения своих слов в кадре вызова самого последнего вызова функции, которая еще не вернулась.
Это означает, что если вы вызываете функцию и возвращаете блок кода со словами, связанными с его контекстом, вычисление этого блока завершится ошибкой после возврата из функции. Однако, если ваша функция вызывает саму себя и этот вызов возвращает блок кода со связанными с ним словами, оценивая этот блок перед возвратом функции заставит его искать эти слова в кадре вызова текущего вызова вашей функции.
Это то же самое, независимо от того, do
вы или return/redo
, и также влияет на внутренние функции. Позвольте мне продемонстрировать:
Функция, возвращающая код, который оценивается после возврата функции, ссылаясь на функциональное слово:
>> a: 10 do do has [a] [a: 20 [a]]
** Script error: a word is not bound to a context
** Where: do
** Near: do do has [a] [a: 20 [a]]
То же самое, но с return/redo
и кодом в функции:
>> a: 10 do has [a] [a: 20 return/redo does [a]]
** Script error: a word is not bound to a context
** Where: function!
** Near: [a: 20 return/redo does [a]]
Версия кода do
, но внутри внешнего вызова той же функции:
>> do f: function [x] [a: 10 either zero? x [do f 1] [a: 20 [a]]] 0
== 10
То же самое, но с return/redo
и кодом в функции:
>> do f: function [x] [a: 10 either zero? x [f 1] [a: 20 return/redo does [a]]] 0
== 10
Итак, вкратце, с блоками обычно нет преимуществ в том, чтобы делать блок в другом месте, кроме того, где он определен, и, если вы хотите, вместо этого проще использовать другой вызов do
. Самовызывающиеся рекурсивные функции, которые должны возвращать код для выполнения во внешних вызовах той же функции, являются чрезвычайно редким шаблоном кода, который я вообще никогда не видел в коде Rebol.
Можно было бы изменить return/redo
, чтобы он также обрабатывал блоки, но, вероятно, не стоит увеличивать накладные расходы до return/redo
, чтобы добавить функцию, которая полезна только в редких случаях и уже имеет лучший способ do
.
Однако это поднимает интересный вопрос: если вам не нужно return/redo
для блоков, потому что do
выполняет ту же работу, разве то же самое не относится к функциям? Зачем нам вообще return/redo
?
Как работает DO функции
По сути, у нас есть return/redo
, потому что он использует точно такой же код, который мы используем для реализации do
функции. Вы можете этого не осознавать, но do
функции действительно необычны.
В большинстве языков программирования, которые могут вызывать значение функции, вы должны передавать параметры функции как полный набор, что-то вроде того, как работает функция apply
в R3. Обычный вызов функции Rebol приводит к тому, что для ее аргументов происходит некоторое неизвестное заранее количество дополнительных вычислений с использованием неизвестных заранее правил вычисления. Оценщик вычисляет эти правила оценки во время выполнения и просто передает результаты оценки в функцию. Сама функция не обрабатывает оценку своих параметров и даже не обязательно знает, как оценивались эти параметры.
Однако когда вы do
указываете значение функции явно, это означает передачу значения функции вызову другой функции, обычной функции с именем do
, а затем это волшебным образом вызывает вычисление дополнительных параметров, которые вообще не передавались функции do
.
Ну это не магия, это return/redo
. Способ do
работы функции заключается в том, что она возвращает ссылку на функцию в обычном возвращаемом значении быстрого доступа с флагом в значении возврата быстрого доступа, который сообщает интерпретатору, что вызвал do
для оценки возвращает функцию, как если бы она была вызвана прямо в коде. Это в основном то, что называется батутом.
Здесь мы подходим к еще одной интересной особенности Rebol: возможность быстрого возврата значений из функции встроена в оценщик, но на самом деле он не использует для этого функцию return
. Все функции, которые вы видите в коде Rebol, являются оболочками для внутренних вещей, даже return
и do
. Функция return
, которую мы вызываем, просто генерирует одно из этих значений возврата быстрого доступа и возвращает его; все остальное делает оценщик.
Итак, в данном случае на самом деле произошло то, что все это время у нас был код, который делал то, что return/redo
делает внутри, но Карл решил добавить опцию в нашу функцию return
, чтобы установить этот флаг, хотя внутреннему коду не требуется return
для выполнения так как внутренний код вызывает внутреннюю функцию. И потом он никому не сказал, что делает эту опцию доступной извне, или почему, или что она делает (думаю, вы не можете упомянуть все; у кого есть время?). У меня есть подозрение, основанное на разговорах с Карлом и некоторых ошибках, которые мы исправляли, что R2 обработал do
функции по-другому, таким образом, что return/redo
стало бы невозможным.
Это означает, что обработка return/redo
довольно тщательно ориентирована на вычисление функций, поскольку это единственная причина его существования. Добавление к нему каких-либо накладных расходов приведет к увеличению накладных расходов на do
функции, и мы используем их часто. Вероятно, не стоит распространять его на блоки, учитывая, как мало мы получим и как редко вообще получим какую-либо выгоду.
Что касается return/redo
функции, кажется, что чем больше мы о ней думаем, тем она становится все более и более полезной. За последний день мы придумали всевозможные уловки, которые позволяют это сделать. Батуты полезны.
person
BrianH
schedule
08.02.2013