[REBOL] Fun with blocks of words (a tad longish)
From: joel:neely:fedex at: 10-Oct-2001 7:39
Hello, all,
I found the following little explorations instructive while thinking
about words and contexts. I hope they are of interest/value to
someone else.
Let's make a block of words:
>> bow: []
== []
>> repeat i 5 [append bow i]
== [1 2 3 4 5]
Woops! That gave me a block of integers, not a block of words.
Besides which, I hate using variables unless necessary.
I can get rid of the variable BOW (since REBOL is a very literal
language) like this:
>> repeat i 5 [append [] i]
== [1 2 3 4 5]
Now let's get a block of words instead of a block of integers:
>> repeat i 5 [append [] [i]]
== [i i i i i]
But is that a block of five homographs (distinct words whose names
are all spelled the same), or is it a block with five references
to the same word?
>> reduce repeat i 5 [append [] [i]]
== [6 6 6 6 6]
It's five references to the same word.
Thought 1: REPEAT creates a context when executed.
Within the block that is evaluated REPEATedly,
the word that is the first argument to REPEAT is in that
new context. (* Not considering the use of nested words
that also create contexts, e.g. USE, etc...)
But what if I *really* want a block of homographs? Hmmmm.
Let's try sticking another context-creating word in there.
>> reduce repeat i 5 [use [.i] [.i: i append [] [.i]]]
== [5 5 5 5 5]
Again we get five references to the same word!
Thought 2: USE creates a context when executed. When
word(s) in the first argument appear in the
second argument, they are words in that context. It
appears that there's a side-effect of "remembering" that
fact so that subsequent USEs of the same block are still
involved with that context.
Hmmm. If I give USE a fresh second block every time, then I can
avoid the side-effect.
>> reduce repeat i 5 [use [.i] copy [.i: i append [] [.i]]]
== [5 5 5 5 5]
Duh! A simple COPY doesn't get to the .I that's in an additional
layer
of block nesting.
>> reduce repeat i 5 [use [.i] copy/deep [.i: i append [] [.i]]]
== [5]
Double-duh! Now there's a fresh empty block for every evaluation
of USE. Sigh... The only way I can think of to get around that is
to re-introduce a word to serve as a "variable".
>> b: []
== []
>> reduce repeat i 5 [use [.i] copy/deep [.i: i append b [.i]]]
== [1 2 3 4 5]
Aha! It looks like I (finally!) have five homographs! I'd really
like to look at them, though...
>> repeat i 5 [use [.i] copy/deep [.i: i append b [.i]]]
== [.i .i .i .i .i .i .i .i .i .i]
Rats! That's why I hate using "variables". Side-effects accumulate!
While I'm at it, I'll keep the result this time so that I can look
at the "raw" block and also REDUCE it:
>> b: []
== []
>> c: repeat i 5 [use [.i] copy/deep [.i: i append b [.i]]]
== [.i .i .i .i .i]
>> reduce c
== [1 2 3 4 5]
Yes! Five homographs! Just what I wanted!
Thought 3: COPY/DEEP is a way to avoid the side-effect
that USE has on its second block. However,
I have to remember that COPY/DEEP also blows away any
side-effects on literal values!
I wonder -- is there any way to get homographs without losing the
ability to have literal values? How else can we create contexts?
How about with a function?
>> apparg: func [b x] [append b [x]]
>> c: repeat i 5 [apparg [] i]
== [x x x x x]
>> reduce c
== [5 5 5 5 5]
Hmmmph! (Insert appropriate numbers of "duh" ;-) I'm back to
getting five occurrences of the same word. Oh yeah...
Thought 4: The context of a function is persistent.
Subsequent uses of the function are using
the same context. This isn't like ___ (insert name of
commonly-used language here) which creates a new frame
or environment for each invocation of a function.
I suspect that this might be a performance choice. Create the
context when the function is defined, and keep it around to avoid
repeating that work every time the function is evaluated.
Errrk? What about recursion?
>> rapparg: func [b i x] [
[ either i > x [b] [rapparg append b [i] i + 1 x]
[ ]
>> c: rapparg [] 1 5
== [i i i i i]
>> reduce c
== [1 1 1 1 1]
Well, it *looks like* I is getting saved and restored for the
recursive calls, but let's make sure (by using some fairly
gnarly-looking before-and-after tracing):
>> rapparg: func [b i x /local r] [
[ print b
[ r: either i > x [b] [rapparg append b [i] i + 1 x]
[ print r
[ r
[ ]
>> c: rapparg [] 1 5
2
3 3
4 4 4
5 5 5 5
6 6 6 6 6
6 6 6 6 6
5 5 5 5 5
4 4 4 4 4
3 3 3 3 3
2 2 2 2 2
1 1 1 1 1
== [i i i i i]
>> reduce c
== [1 1 1 1 1]
Yup! The argument block is accumulating a bunch of references
to the same word (an argument) whose value is being saved and
restored to make the recursive call behave like one would
expect it to.
Thought 5: I suspect that means that there's a time
penalty for using recursion (compared to
repeatedly calling the same function within an iteration)
but that benchmark will have to wait for another day.
Wait a minute! My attention is wandering! I wanted to make a
block of homographs. How else can I make new contexts? Whoa!
I can make a new function everytime I iterate!
>> c: repeat i 5 [do func [b x] [append b [x]] [] i]
== [x x x x x]
>> reduce c
== [1 2 3 4 5]
Yeehah! Now I get a fresh context (and therefore a fresh word)
every time.
Thought 6: FUNC appears *not* to have the side-effect
of altering its second block (in the way
that USE does). Subsequent uses of FUNC ... ... really
get me a new context for each evaluation.
Of course, being stubborn as well as simple-minded, I can't help
but think about the idea of USE inside the body of a function.
Since USE seems to have a side-effect on its second (literal)
argument, I would expect to get the same word over and over.
Let's try it...
>> appuse: func [b x] [use [.x] [.x: x append b [.x]]]
>> c: repeat i 5 [appuse [] i]
== [.x .x .x .x .x]
>> reduce c
== [1 2 3 4 5]
Wot??? I got five homographs! Does USE inside a function body
behave differently than USE at the console level?
>> repfun: func [b x] [repeat i x [use [.x] [.x: i append b [.x]]]]
>> c: repfun [] 5
== [.x .x .x .x .x]
>> reduce c
== [1 2 3 4 5]
It does! We're back to five homographs, even though the body of REPFUN
is essentially the same as the unsucessful attempt from the console
prompt earlier (except for using an argument instead of a literal as
the object of the APPEND).
Thought 7: There's more going on here than meets the eye.
I need another cup of coffee.
As usual, feedback/comments/corrections/coffee are welcome! ;-)
-jn-
--
joelDOTneelyATfedexDOTcom