How to use 'bind and when ...

auteur: Patrick Philipot
date: 10-mar-2002, 11-mar-2002
version: 1.01

Contents

1. A tribute to the rebol-list
2. Scope and context
2.1. What is rebol/words ?
2.2. First and second of an object
3. Function scope
3.1. Where 'bind is needed for the first time
3.2. Who is 'self
3.3. Bind modifies its block
3.4. Bind/copy
3.5. Rebinding
4. When to use bind ?
4.1. dialect function
4.2. The foreach example
4.3. Partial binding
5. Your contribution
6. Useful functions used along this document

1. A tribute to the rebol-list

The purpose of this document is to enlighten the notion of 'bind. This baby gave me much trouble (and I am expecting more to come...).

Life is not always easy for one that embraces Rebol. The language is brilliant, elegant and powerful, but it has its share of gotchas and obscure or poor documented features... However when the dark side is frightening, you can always expect light coming from the rebolisters (the members of the rebol-list).

Thanks to Joel, Ladislav, Gregg, Carl, Romano, Gabrielle, Andrew, Brett, Alan, Maarten, Petr, Allen, Robert, Sunanda, Jason and many others, I have always found answers to my questions. My goal here is to make available what I have learned from these answers.

Vive la rebolution !


2. Scope and context

Let's try a very simple test :

>> for x 1 3 1 [print x]
1
2
3
>> print x
** Script Error: x has no value
** Where: do-boot
** Near: print x
>>

We can see here that x has a value only within the for loop. Trying to use x outside the loop rises an error. We can say that x has a temporary existence and a temporary scope.

What happens if another x exists before the for loop, let's try it :

>> x: 100
== 100
>> for x 1 3 1 [print x]
1
2
3
>> print x
100
>>

The x which is defined before the for loop is in fact invisible inside the loop. The x inside the loop, and the x outside the loop, live in separeted contexts. The x outside lives in what is called the global context. This is the main Rebol context, the one where rebol words, such as 'print 'if or 'for, are stored and have meanings.

We can use Ladislav's s-c? function to prove that x is global (s-c? stands for same context). The function s-c? returns true if its two parameters are in the same context. Here we compare 'x with the 'print command which is a global rebol word :

>> s-c? 'print 'x
== true
>>

If x is in the same context than print, we can state that x is in the global context.

The source of s-c? can be found in Ladislav's contexts.html and at the end of this document

Is it possible however to access the global x inside the for loop ?

Yes, it is !

>> for x 1 3 1 [print rebol/words/x]
100
100
100
>>

2.1. What is rebol/words ?

What is rebol/words ? It is an object that holds all the rebol words.

>> type? rebol/words
== object!

>> first rebol/words
== [end! unset! error! datatype! context! native! action! routine! op! function! object! struct! library! port! any-type! any-word!...

As any other objects, rebol/words is made of two blocks. The first block is a table of all its words.

We will note in passing that rebol/words is peculiar in that the table of all its words does not start with 'self.

2.2. First and second of an object

In a recent post (5-mar-2002), Holger Kruse ( holger@rebol.net ) explained to the rebol-list that a context is internally made of two tables: a table of words and a table of corresponding values and that for object!s one can look at this two tables.

o: make object! [
    a: 1
    b: 2
    c: 3
    d: does [print [a b c]]
]

>> first o
== [self a b c d]

The first part is a table of all words.

>> second o
== [
    make object! [
        a: 1
        b: 2
        c: 3
        d: func [][print [a b c]]
    ] 1 2 3 func [][print [a b c]]...

The second part of an object is a table containing the corresponding values.


3. Function scope

Previously we have seen that a global word can be made invisible inside a loop. Using an object, we can show that a global function can be made invisible too.

o: make object! [
    print: func [b [block!]][rebol/words/print compose ["o-print-> : " (b)]]
    test1: does [ print ["test"]]
]

This object defines a function named 'print. As before, we have used the rebol/words path to access the global 'print function.

>> o/test1
o-print-> :  test
>>

When 'print is used inside the function, it refers to the local 'print .

3.1. Where 'bind is needed for the first time

Let's add a new function test2. The purpose of test2 is to 'do a given block of code.

o: make object! [
    print: func [b [block!]][rebol/words/print compose ["o-print-> : " (b)]]
    test1: does [ print ["test"]]
    test2: func [b [block!]][do b]
]

Can we use test2 to print with our new print function ? let's try ...

>> o/test2 [print ["hello World!"]]
hello World!
>>

Good gracious, this is not the local 'print ! What happens ?

A quite simple rebol mecanism indeed: inside the block [print ["hello World!"]], 'print is bound to the global context and it will stay bound to it unless ... we use the 'bind command.

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!

The magic of 'bind has occurred. Every word of b is now bound to the same context as 'self.

3.2. Who is 'self

The second argument of 'bind must be a word from the target context. 'self is a word that is available in any object! The list of all the object's word can be displayed like this :

>> first o
== [self print test1 test2 test3]

We could have used any of the o object words for 'bind :

test3: func [b [block!]][do bind b 'test1]

test3: func [b [block!]][do bind b 'test2]

test3: func [b [block!]][do bind b 'test3]

test3: func [b [block!]][do bind b 'print]

3.3. Bind modifies its block

When 'bind is performed on a block, effects are permanent.

>> myBlock: [print ["Hello World!"]]
== [print ["Hello World!"]]
>> do myBlock
Hello World!
>> o/test3 myBlock
o-print-> :  Hello World!
>> do myBlock
o-print-> :  Hello World!

This effect is often not desirable, this is why 'bind is commonly given a copy of a block.

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]
]

Now the original block stays unchanged.

>> myBlock: [print ["Hello World!"]]
== [print ["Hello World!"]]
>> do myBlock
Hello World!
>> o/test3 myBlock
o-print-> :  Hello World!
>> do myBlock
Hello World!

3.4. Bind/copy

'bind/copy deep copy the block before using it.

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!"]]

>>  o/test3 myBlock
o-print-> :  Hello World!
>> do myBlock
Hello World!

When the /copy refinement is used myBlock is unchanged.

3.5. Rebinding

It is possible to 'bind a block again to the global context. All we need is a global word such as 'system.

>> do myBlock
o-print-> :  Hello World!
>> bind myBlock 'system
== [print ["Hello World!"]]
>> do myBlock
Hello World!

However I have never seen this method used anywhere.


4. When to use bind ?

'bind is not a guru function. In fact 'bind is often needed in every day situation. Some of these situations are presented here.

4.1. dialect function

One can argue that defining a 'print function inside an object was not a very clever idea. Naming the function 'myprint for example would have been more simple. We can explore this path and see if it is true.

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

This one passes the test.

>> myblock: [myprint ["Hello World!"]]
== [myprint ["Hello World!"]]

>> o/test2 myblock
** Script Error: myprint has no value
** Where: test2
** Near: myprint ["Hello World!"]

This one fails because myprint is bound to the global context where it has no value.

>> o/test3 myblock
o-print-> :  Hello World!

Finally 'bind is again the solution.

This kind of construction is often used when creating a dialect. It is common then to build blocks where "dialect functions" must be used from outside. A good example can be found in the "TUI Dialect - A dialect to print ASCII sequences in REBOL" by Ingo Hohmann at http://www.rebolforces.com.

4.2. The foreach example

Exploring the rebol-list archives ( at http://www.escribe.com/internet/rebol/index.html ) I have found an interesting example of a every day situation involving 'bind.

The problem was exposed with a very small script by Fantam :

block: ["a" "b" "c"]
code: [print member]
foreach member block [do code]
** Script Error: member has no value
** Where: do-boot
** Near: print member

The explanation was given by Larry. The word 'member in 'code is bound to the global context (where it has no value yet). The word 'member in the foreach is a new word that is bound to the local context of the foreach loop. One solution is to bind the code to the local 'member.

>>  block: ["a" "b" "c"]
== ["a" "b" "c"]
>>  code: [print member]
== [print member]
>>  foreach member block [do bind code 'member]
a
b
c

The 'member in 'code is now bound to the loop context where it has a value.

4.3. Partial binding

The following is an example inspired by a post from Joel Neely on Thusday, 18 Oct 2001. It emphasizes the fact that binding does not alter every word of a block, but only those that have a local counterpart.

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

Every thing is global.

>> some-func wordblock
I'm local
I'm global I'm global too
I'm local I'm global too

After binding 'glorp in 'wordblock is bound to the some-func context.

After binding 'glunk in 'wordblock is unchanged because there is no word with the same spelling in the context of 'foo.

>> print wordblock
I'm local I'm global too

The change as always is permanent.


5. Your contribution

If you have an example of other 'bind usage feel free to send it to me at pat665@ifrance.com . Criticism, remark are also welcome, send them at bullshit-happens@ifrance.com (it's not a joke ... yeah ok it'is a joke but the address is real).


6. Useful functions used along this document

This function can be used to find out, if a Word is undefined:

undefined?: func [
    {determines, if a word is undefined}
    word [any-word!]
] [
    error? try [error? get/any :word]
]

Two Words WORD1 and WORD2 are bound to the same context, if the expression (s-c? word1 word2) yields TRUE.

s-c?: func [
    {Are word1 and word2 bound to the same context?}
    word1 [word!]
    word2 [word!]
] [
    found? any [
        all [
            undefined? word1
            undefined? word2
        ]
        all [
            not undefined? word2
            same? word1 bind use reduce [word1] reduce [
                'first reduce [word1]
            ] word2
        ]
    ]
]

Documentformater copyright Robert M. Münch. All Rights Reserved.
Formatted with Make-Doc-Pro Version:1.0.0 on 11-Mar-2002 at 20:38:53