Словарь языка Rebol содержит слово context. Как же оно связано с понятием контекста, которое мы только что усвоили?
Никак. Почти никак. Не верите? Смотрите сами:
? context
USAGE:
CONTEXT blk
DESCRIPTION:
Defines a unique (underived) object.
CONTEXT is a function value.
ARGUMENTS:
blk -- Object variables and values. (Type: block)
Итак, слово context порождает прототип -- объект-сироту, у которого нет родителей, но могут быть дети. Например,
a: context [a: 4]
b: make! a [b: 5]
a
a/a
== 4
b
b/a
== 4
b/b
== 5
Что произошло? Мы создали прототип a, в котором переменная а имеет значение 4. Затем мы создали наследника а, объект b, содержащий и переменную а, и новую переменную b.
Выполнение прототипа и его наследника не возвращает никаких значений,
зато переменные отдают свои значения. Подробнее об объектах в
rebol-стиле лучше почитать в статье Объекты и функции. Можно формировать и более изощрённые структуры:
a: context [ a: context [ a: context [ a: 4 ] ] ]
a
a/a
a/a/a
a/a/a/a
== 4
? a
A is an object of value:
a object! [a]
Я, к сожалению, так и не придумал, где такие странности можно использовать.
3. Простые примеры
Посмотрим, как ведёт себя переменная в определённом контексте и без него, например:
for x 1 8 2 [print x]
==1
==3
==5
==7
print x
** Script Error: x has no value
** Near: print x
Итак, х существует только внутри цикла (или, как выражаются теоретики, x определён лишь во временном пространстве имён). Вне цикла доступ к x невозможен: мы скорее сможем опрееделить новый x в глобальном контексте:
x: 100
== 100
for x 1 8 2 [print x]
==1
==3
==5
==7
print x
==100
Итак, мы убедились в существовании нескольких контекстов, в одном из которых x имеет значение 7 (последнее значение, полученное переменной в цикле), а в другом -- глобальном -- значение 100.
Можно ли достучаться до глобальной переменной из контекста цикла? Да, можно:
for x 1 3 1 [print rebol/words/x]
==100
==100
==100
Объект rebol/words содержит все слова:
first rebol/words
== [end! unset! error! datatype! context! native! action! routine!...
Исследуем какой-нибудь объект:
>> o: make object! [ a: 1 b: 2 d: does [print [a b c] ] ]
>> first o
== [self a b d]
>> second o
== [make object! [
a: 1
b: 2
d: func [][print [a b c]]
] 1 2 func [][print [a b c]]]
>>
Итак, объекты состоят из двух блоков: блока имён (в o это [self a b d]) и блока значений (второй блок с хитрой структурой).
4. Имена и значения функций
Итак, внутри цикла глобальное слово может быть недоступно для интерпретатора, разве только мы используем некие трюки и ухищрения для того, чтобы "достать" его из общего контекста. Внутри объекта можно спрятать и целую функцию:
o: make object! [
print: func [b [block!]][rebol/words/print compose ["o-print-> : " (b)]]
test: does [print ["test"]]
]
>> o/test
o-print-> : test
Что произошло? Мы определили локальную функцию print, и отныне все обращения к слову print внутри объекта o будут печатать блок данных, предваряемый невнятным префиксом. К сожалению, более осмысленные примеры мы готовим на завтрак.
4.1. Зачем нужен 'bind ?
Добавим-ка ещё одну функцию, выполняющую некий блок кода, данный ей в качестве аргумента:
o: make object! [
print: func [b [block!]][rebol/words/print compose ["o-print-> : " (b)]]
test1: does [ print ["test"]]
test2: func [b [block!]][do b]
]
Теперь попробуем напечатать какую-нибудь белиберду!"
>> o/test2 [print ["Привет, Ктулху!"]]
Привет, Ктулху!
>>
Хм, а куда делась локальная версия print?
Внутри блока [print ["Привет, Ктулху!"]] слово 'print привязано к глобальному контексту, и будет к нему привязано, если мы его не перевяжем заново. Для этого и используется слово bind.
o: make object! [
print: func [b [block!]][rebol/words/print compose ["o-print-> : " (b)]]
test1: does [ print ["test"]]
test2: func [b [block!]][do b]
test3: func [b [block!]][do bind b 'self]
]
>> o/test3 [print ["hello World!"]]
o-print-> : hello World!
Вот так мы привязали слово b (т.е. блок-аргумент) к контексту объекта o. Слово self существует в каждом объекте, и его контекст -- контекст внутри родительского объекта. На самом деле, можно использовать любое другое слово, характерное для этого объекта: 'test1,'test2, даже 'print.
Заметьте, что если bind выполняется для блока, эффект "привязывания" к контексту не исчезает после использования:
>> myBlock: [print ["Hello World!"]]
== [print ["Hello World!"]]
>> do myBlock
Hello World!
>> o/test3 myBlock
o-print-> : Hello World!
>> do myBlock
o-print-> : Hello World!
Эффект этот довольно мощен, но не всегда желателен, поэтому лучше связывать копии блоков:
o: make object! [
print: func [b [block!]][rebol/words/print compose ["o-print-> : " (b)]]
test1: does [ print ["test"]]
test2: func [b [block!]][do b]
test3: func [b [block!]][do bind copy b 'self]
]
Теперь всё вроде бы логично:
>> myBlock: [print ["Hello World!"]]
== [print ["Hello World!"]]
>> do myBlock
Hello World!
>> o/test3 myBlock
o-print-> : Hello World!
>> do myBlock
Hello World!
Кроме того, можно воспользоваться bind/copy, результат будет тот же.
4.2. Как изменить привязку
Мы можем вернуть привязку блока к глобальному контексту. Для этого мы должны воспользоваться каким-нибудь глобальным словом. Рекомендуется использовать для этой цели слово system:
>> do myBlock
o-print-> : Hello World!
>> bind myBlock 'system
== [print ["Hello World!"]]
>> do myBlock
Hello World!
5. Примеры применения bind
Для программирования в определённом стиле использование bind просто необходимо -- без связывания практически неосуществимо метапрограммирование. Рассмотрим основные (и потому довольно примитивные) варианты использования связывания.
5.1 Диалектные функции
Хм, а зачем вообще переименовывать функцию print? Можно было воспользоваться словом myprint и избежать проблем со связыванием слова с контекстом? Да, можно поступить и так.
o: make object! [
myprint: func [b [block!]][print compose ["o-print-> : " (b)]]
test1: does [ myprint ["test"]]
test2: func [b [block!]][do b]
test3: func [b [block!]][do bind copy b 'self]
]
>> o/test1
o-print-> : test
Получилось!
>> myblock: [myprint ["Hello World!"]]
== [myprint ["Hello World!"]]
>> o/test2 myblock
** Script Error: myprint has no value
** Where: test2
** Near: myprint ["Hello World!"]
А здесь, увы, не получилось, поскольку myprint связан с глобальным контекстом, в котором у него нет значения.
>> o/test3 myblock
o-print-> : Hello World!
Такие задачи встречаются чаще всего при создании диалекта. Тогда требуется построить блоки, содержащие диалектные функции, которые могут быть использованы вне диалекта. Хороший пример построения диалекта можно найти в статье TUI Dialect - A dialect to print ASCII sequences in REBOL".
5.2. Доступ внутрь цикла
В рассылке rebol-list был выловлен ещё один пример простого использования связывания. Вот начальная задача:
block: ["a" "b" "c"]
code: [print member]
foreach member block [do code]
** Script Error: member has no value
** Where: do-boot
** Near: print member
Здесь слово 'member внутри блока 'code связано с глобальным контекстом (и в нём у него нет значения!). Слово 'member в цикле foreach -- это новое слово, связанное с контекстом цикла. Нужно связать блок code c локальным словом 'member.
>> block: ["a" "b" "c"]
== ["a" "b" "c"]
>> code: [print member]
== [print member]
>> foreach member block [do bind/copy code 'member]
a
b
c
Теперь слово 'member в блоке 'code связано с контекстом цикла, в котором и определено его значение.
5.3. Частичное связывание
Связывание не изменяет значение всех слов в блоке, но лишь тех из них, у которых есть локальные дубликаты.
glorp: "I'm global"
glunk: "I'm global too"
wordblock: [glorp glunk]
some-func: func [b [block!] /local glorp foo][
glorp: "I'm local"
foo: 42
print [glorp]
print b
bind b 'foo
print b
]
>> print wordblock
I'm global I'm global too
Всё глобально, верно?
>> some-func wordblock
I'm local
I'm global I'm global too
I'm local I'm global too
Но после связывания glorp в 'wordblock связан с контекстом some-func.
После связывания glunk в 'wordblock остался неизменным, поскольку в контексте foo нет одноимённых слов.
>> print wordblock
I'm local I'm global too