Hi Joel,
this response was written on Thursday last week. At the time the message I
am responding to was your latest message to the list. Since then you have
sent a few other messages.
Text begins here =======>
R Stands For Relative!
I decided to review your messages bottom up (the latest message first).
Let's see how long that makes sense.
As your latest message, I came across the one that - how apropos - happens
to take me up on the challenge I once sent Ladislav.
In the following response I will be doing four things:
1. I comment on your step by step description of how the function works
that I threw into the challenge. On several occasions I confront your
explanations with REBOL code and demonstrate that the statement I am
criticizing is incorrect.
2. I raise the question: Where you able to accomplish the goal you set
yourself? You were trying to demonstrate that my function's behavior could
be explained without having to consider local and global contexts. I
disregard the mention of local and global contexts that are irrelevant to
the problem at hand and conclude that
a) Having demonstrated that your explanation of the function's behavior is
mostly incorrect, I could consider your explanation refuted as a whole and
by virtue of this refutation I could insist that your attempt failed to
explain the function's behavior altogether, and therefore you failed to
explain the function's behavior without using "global" and "local" contexts
as relevant arguments. A false explanation does not prove anything.
b) Instead I demonstrate that the explanation you give, while being
incorrect, yet relies in relevant ways on the distinction between local and
global contexts in order to explain how the function we were discussing
accomplishes its job.
I therefore conclude that even if your explanation of the function's
behavior had been correct, you would have failed to explain the function's
behavior without making use of local and global contexts, and therefore you
would have failed to accomplish your goal of beating the challenge.
3. I explain why many experienced programmers, when they first start
working with REBOL, are surprised, to find that the following code:
f: func [/local a] [
a: ""
for i 1 5 1 [insert a i]
]
results in a permanent modification to the literal string referenced by 'a.
The second time the function is entered, 'a will be referencing the string
"12345". The confusion results from a very simple prejudice. It has nothing
to do with incorrect explanations of the series! datatype.
4. I reconstruct the context in which I sent the function as a challenge to
Ladislav and explain in detail why I used the function and what it was
supposed demonstrate. I conclude that Ladislav's attempt to describe the
difference between the behavior of the two expressions
a: ""
b: copy ""
by introducing the term "dynamic data creation" is insufficient because it
is based on a false abstraction. The abstraction is false because it
ignores an essential detail of the expression that it sets out to explain.
Throughout my explanations I try to remain true to the terminology
introduced in the final release of the User's Guide (not the beta manual),
the REBOL Dictionary and REBOL itself. Jeff like to point out that REBOL is
its own meta-language.
When REBOL identifies a value such as my-word as a word! (or one of the
subtypes of a word such as set-word! or lit-word!), then I refer to my-word
as a word (or the appropriate subtype, if and when it is critical to
differentiate the subtype from the generic type). When REBOL reports the
datatype of a literal string as a string
>> type? "this is a string and not a word."
== string!
I refer to that value as a string and I do not refer to that value as a
word. I insist that if you develop a terminology that disregards REBOL's
classification of values, then you might end up with a beautiful
terminology. That terminology however is incorrect and invalid if it is to
describe REBOL. More about terminology when I discuss false abstractions.
Now, for some fun:
you quote my response to Ladislav:
>[EMAIL PROTECTED] wrote:
>>
>> Hi Ladislav,
>>
>> you wrote:
> ...
>> >
>> >Let's get to the above example, but let's use a little bit different code:
>> >
>> >f: func [/local a b] [ a: "" b: copy "" for i 1 6 1 [append a i append
b i]
>> >print a print b]
>> >>> f
>> >123456
>> >123456
>> >>> f
>> >123456123456
>> >123456
>> >>> f
>> >123456123456123456
>> >123456
>> >
> ...
>>
>> then we would be very surprised by the following demonstration,
>> wouldn't we?
>>
>> f: func [/local a b] [
>> a: ""
>> b: copy ""
>> for i 1 6 1 [
>> append a i append b i
>> ]
>> print a print b
>> insert pick second :f 5 copy/part second second :f 6
>> ]
>>
>> The line in which 'b was created wasn't changed. Most dramatically, it
>> continues to be the exact same line you commented on as
>>
>> >the creation of B
>> >is "dynamic" as opposed to A, where the creation is "static"
>>
> ...
>> Let me see an explanation that maintains that
>>
>> >the notions of "global" vs. "local" are misleading in this case.
>>
>> as you claim, while it manages to explain away the fact that while
>> a and b continue to be created in the exact same way the were before...
>>
>
and write:
>OK. I'll have a go at it.
>
>1) At translation time the text which begins with (sans formatting)
1. At which point in time is "at translation time"?
I take it that "at translation time" means:
1.1 the function has been completely entered in the REBOL console or a file
is being evaluated using the REBOL function do.
1.2 REBOL does something, which transforms the text into some internal
representation.
1.3 Then REBOL evaluates this internal representation as a step distinct
from 1.2.
"At translation time" would mean during 1.2. Something that happens before
the evaluation begins. I wonder whether there is any evidence for there
being a distinct step of translation. For all I know, REBOL's "internal
representation" of what I enter in the console may just be a string
containing the text I entered.
1.4 We know that there is a REBOL word load, which appears to do what you
mean by "at translation time":
>> type? first load "a: 1"
== set-word!
the function load has translated the characters in its argument string into
REBOL tokens. Therefore the token a: is already identified as set-word!.
Load has not evaluated the translated string yet, therefore the set-word!
'a has not been evaluated and 'a has not been assigned a value yet:
>> value? 'a
== false
>> a
** Script Error: a has no value.
** Where: a
Trying to evaluate the word 'a results in an error because 'a has no value
yet, as documented by value? 'a.
1.5 We have been previously told by some REBOL staffer (was it Bo?) that
the REBOL function 'do does an implied 'load.
We also know from REBOL's User's Guide that the REBOL console performs a
'do on its input.
It would be redundant for the REBOL console to perform a 'do including do's
implied 'load, if input entered at the REBOL console would already have
been translated as a separate, distinct step.
My argument is not conclusive. Perhaps translation is a distinct step and
REBOL does act redundantly. I conclude that proposing a distinct
translation step is speculative. It may or may not be true. I tend to think
it is true.
Whether translation and evaluation are two distinct steps or not is
irrelevant to my following arguments.
>1) At translation time the text which begins with (sans formatting)
>
> [a: "" b: copy "" ;...etc...
>
> is translated into a block.
> The second element of that block is
> initialized to refer to the empty string, as is the fifth element
> of that block.
2. I take it that the second element you mean here is "" and the fifth
element you mean is again "". The two literal strings are not references to
"the empty string". They not initialized to anything in the expression you
are commenting on.
Reference in REBOL has a specific meaning. It describes a process that
occurs when a set-word! type value is evaluated. Either the evaluation
succeeds, in which case the value that was set-worded becomes a word, i.e.
it now owns a valid reference to another value. Or the evaluation fails, in
which case the value does not acquire a reference to a value:
>> a: 1
== 1
>> a
== 1
>> type? "a"
== string!
>> "a": 1
** Syntax Error: Invalid word-get -- :.
** Where: (line 1) "a": 1
A string cannot be a reference.
A string CAN act as an argument to the function make with the type
designator set-word!:
>> make set-word! "a"
== a:
Here the string is an argument only. By the time it has become a set-word!
it is no longer a string:
>> string? make set-word! "a"
== false
2.1 Only the word! family of values (word!, lit-word!, set-word!) can
reference something:
We construct abc as a set-word! and assign 'a as a reference to the
set-word! abc: WITHOUT providing a value for 'abc to reference:
>> a: make set-word! "abc"
== abc:
>> type? :a
== set-word!
When we evaluate 'a, REBOL attempts satisfy abc: as a set-word! value.
REBOL complains that abc has not been supplied a value to reference:
>> a
** Script Error: abc needs a value.
** Where: a
Now we follow the same steps to create 'a as a reference to a string, again
WITHOUT providing a value for the string to reference (as we did in the
case of the set-word! abc:):
>> a: make string! "abc"
== "abc"
>> type? :a
== string!
Now we evaluate 'a
>> a
== "abc"
REBOL does not complain that the string "abc"'s reference has not been
satisfied by a value. That is because a string is not a reference!
2.2 Each of the two literal strings is an independent entity. Since you
speak of "the empty string" and you say " as is the fifth element", your
formulation suggests that the two literal strings are identical, since they
are both references to "the empty string". If they are identical, the
function same? should return true. However, same? returns false:
>> same? "" ""
== false
In contrast, when we *do* create two *actual* references to the *same*
string, same? returns true:
>> a: ""
== ""
>> b: :a
== ""
>> string? a
== true
>> string? b
== true
>> same? a b
== true
2.3 Conclusion:
2.3.1 An empty literal string does not reference "the empty string" because
a string! value does not have the capability to reference something. That
capability is reserved for values of type word!
2.3.2 Two distinct empty literal strings are not the same string.
>
>2) When the entire expression has been translated, it is immediately
> evaluated (since it was typed into the REBOL console).
3. Since you say it "it is immediately evaluated", I don't see why you
would propose "translation" as a distinct step.
4. The word "it" in "it is immediately evaluated" appears to refer to "the
entire expression". Do I understand you correctly? What does "entire
expression" mean?
Does it mean:
4.1 func [] [ ... whatever ...]
i.e what you mean is that the function func is evaluated. That function
takes two blocks, whose contents at this point are NOT evaluated.
Or do you mean:
4.2 func [/local a b] [ ... also being evaluated ... ].
i.e. the function func AS WELL AS the words contained in either or both of
the blocks are evaluated at this point in time?
> Doing so
> defines a word 'f in the global context. The value associated
> with 'f is an entity of type 'function! whose second element is
> a deep copy of the block discussed in 1).
5. If the second element of the function! 'f is a deep copy of the block
discussed in your point 1) it should behave as such, right? It does not:
5.1 We set a global word 'a to the value 1:
>> a: 1
== 1
5.2 We make a deep copy of a block [a: 5], retrieve the first element, a:,
and get that element's current value in the global context: The result is
>> get first copy/deep [a: 5]
== 1
Conclusion:
5.3 Because the block surrounding the expression [a: 5] prevented the
assignment of the integer 5 to the word 'a from being evaluated, 'a
continues to return the value we originally set it to, namely 1.
5.4 The second block of a function behaves differently.
Let's create a function we call 'f, whose second block is the same as the
block whose deep copy we just investigated:
>> f: func [/local a] [a: 5]
5.5 We can retrieve the first element in the 'f function's second block:
>> first second :f
== a:
as we did above with the deep copy of the block [a: 5]
If f's second block is a deep copy of the block [a: 5], it should behave as
the deep copied block did above, when we used get to retrieve the value of
'a. In the deep copy example, the value of the global 'a was returned. If
f's second block is a deep copy of the argument passed to func, the same
thing should happen, we should retrieve the value of the global 'a.
When we try to access a's value in the second block of the function 'f,
something quite different happens:
>> get first second :f
** Script Error: a has no value.
** Where: get first second :f
If the second block of the function 'f was no more than a deep copy of the
second block we supplied to 'func, then the set-word a: that we retrieved
from the function's second block should have been bound to the global
context and should have evaluated to ==1, as was the case when we used 'get
to retrieve the value of the first element, a:, from the deep-copied block.
5.6 Let's look at another example:
>> c: 10
== 10
>> f: func [/local a b] [ a: 5 b: 6 c: 0 ]
>> fifth second :f
== c:
>> get fifth second :f
== 10
fifth second :f retrieves the set-word c:. The set-word! c: was not
declared local in the first block of 'func. Therefore 'c continues to be
bound in the global context and the value 10 is returned.
5.7 Explanation
In contrast to c:, we get an error message when we tried to retrieve the
value of a:. Why? Because f's second block's 'a is no longer bound in the
global context. In contrast to what happens with a deep copy of a block, in
a function the set-word! a: has been bound to the local context of the
function, even though it has not been bound to a value yet, as long the
function's body was not evaluated.
The function's first block was processed, and the words that are to be
bound locally were identified in the function's second block. A local
context for the function was created. Those words in the second block that
were indicated by the function's first block were bound to the function's
local context.
5.8 Conclusion
The function's local words are bound to the local context at the time the
function is created, not at the time the function's body is evaluated.
> For brevity, I'll
> refer to the second element of the 'function! value associated
> with 'f as #BARNEY# (I'm speaking "commentary" here, not REBOL).
6. Since in this case you explicitly point out that you are deviating from
REBOL parlance, I assume that the other terminology you introduce is
intended to be consistent with "speaking REBOL" and not just "commentary".
BODY would have worked quite well. It's commonly used in REBOL function
specifications and help texts.
>
>3) When 'f is invoked, a local context is created for the words 'a
> and 'b, then #BARNEY# is evaluated.
6.1 As demonstrated above (see my point 5.x), the local context was already
created when func was evaluated, i.e. when the function! value was created.
It is not created "when 'f is invoked".
6.2 'f is not invoked. The evaluation of 'f results in the detection that
'f is a word, and that leads to dereferencing it, i.e. retrieving its
value, which in turn is evaluated. Since the value is a function, first its
arguments are bound, refinements are set to none or true, provided there
are any and depending on whether they are included in the path of the word
being dereferenced, and then the function's body is evaluated.
To be brief you may want to say the function is evaluated (or even
invoked). But here you set out to analyze the behavior of a function. Here
precision should prevail over brevity.
The words declared /local in the spec block were already bound to the
function's local context under your 2) (my point 5.x), as documented above.
> This evaluation causes the
> following events to occur:
>
>3.1) 'a is set to refer to the second element of #BARNEY#.
Correct.
>3.2) 'b is set to refer to a copy of the fifth word of #BARNEY#.
6.3 That is incorrect. The fifth element of #BARNEY# is a literal value,
namely a value of type string! It is not a value of datatype word!
Therefore it is not a word:
f: func [/local a b] [
a: ""
b: copy ""
for i 1 6 1 [
append a i append b i
]
print a print b
insert pick second :f 5 copy/part second second :f 6
]
>> fifth second :f
== ""
>> type? fifth second :f
== string!
In contrast, the word 'copy is identified as a word:
>> fourth second :f
== copy
>> type? fourth second :f
== word!
So, the element at position 5 is not a word, it is a value of type string!,
whereas the word 'copy is a word, it is a value of type word!, but it is
not located at position 5.
6.4 I'm trying to guess what you mean when you say:
>3.2) 'b is set to refer to a copy of the fifth word of #BARNEY#.
6.4.1 Are you claiming that the 'b is set to refer to the fourth element?
That element is a word, namely the word 'copy. While it is a word it is not
the fifth word, nor is it the fifth element of the function's body.
'b is not set to reference the word 'copy.
6.4.2 Or perhaps you mean that 'b is set to a copy of the fifth element.
The fifth element is not a word (as demonstrated above).
'b indeed is set to a copy of the fifth element, which is a literal string.
6.4.3 Are you saying that 'b is set to a copy of the fifth word? The fifth
word is the word 'a where it references the series used as an argument for
'append.
While the word 'a is indeed the fifth word, 'b is not set to a copy of its
value.
6.4.4 I am quite sure that you mean that 'b is set to the fifth element of
the f's body, as indeed it is. That element happens to have the datatype
string!, it is a literal string. Either you mean to say "string" and you
happened to write "word" by mistake. That can happen. No problem.
Or you believe that the fifth element, the literal string, is correctly
referred to as a word. That is not the case. The generic term for a REBOL
construct is the word "value". (See the User's Guide.) Everything,
including words, is a value. Words are values that have the datatype word!.
Their datatype provides words with their special properties, such as being
dereferenced when REBOL encounters them, or being assigned as references to
other values, when they are used as set-word!.
Other values, such as strings, are not words, they are values that have
their own datatype. If you happened to see Eric's updated chart of type -
pseudotype relationships, you will notice that word is not a generic term
for every REBOL datatype.
6.5 Conclusion
It is misleading to apply terms to REBOL values that are inconsistent with
REBOL's own perception of those values, as documented by the REBOL function
type?.
A string is a string and word is a word. The fifth element of the body is
not a word, but its copy is assigned to 'b. The fifth word is not the
argument passed to copy at the time copy is assigned its string value.
7. I believe that you now skip the 'for loop and only deal with a single
iteration of the loop. That's fine.
>3.3) 'a is passed to 'append, which modifies the data of the
> string to which 'a refers.
8. No, REBOL dereferences 'a before the function gets its hands on it. What
'append receives is the string referenced by 'a, not 'a itself:
8.1 Here we create the word arg as a reference to a series value:
>> arg: "123456"
== "123456"
8.2 Append does not accept a word as an argument.
>> type? 'arg
== word!
>> append 'arg "xyz"
** Script Error: append expected series argument of type: series port.
** Where: append 'arg "xyz"
8.3 Here we pass 'append the string value referenced by arg:
>> type? :arg
== string!
>> append :arg "xyz"
== "123456xyz"
8.4 We can control whether a function accepts a word or the value
referenced by a word. The following function accepts a word:
>> f: func [ par [word!] ] [ get par ]
>> f 'arg
== "123456xyz"
The same function does not accept the value referenced by the word:
>> f arg
** Script Error: f expected par argument of type: word.
** Where: f arg
Now, what does 'append require, a word or a series! value?
>> help append
Appends a value to the tail of a series and returns the series head.
Arguments:
series -- (series port)
value --
Refinements:
/only -- Appends a block value into a block series as a block
8.5 Conclusion:
The first argument to append is expected to be a value of type series! or
port!. append does not accept an argument that is a word!.
> Since 'a refers to the same
> string as the second word of #BARNEY#, this has the side-
> effect of modifying the data referred to by the second word
> of #BARNEY#.
9.1 The second element of #BARNEY# is not a word, it is a literal value of
type string! The second element of #BARNEY# is the literal string "". The
only type of value that references anything is a value of type word!
See my proof documented under 2.x.
9.2 There are no side-effects. The literal string referenced by 'a is
modified.
9.3 We do not need an explanation for the fact that the string referenced
by 'a is modified, since 'append's argument was already the string, it was
not 'a.
>3.4) 'b is passed to 'append, which modifies the data of the
> string to which 'b refers.
Again, 'b is not passed, the value referenced by 'b, which is the string,
is passed.
> Since no other references to
> that string exist (it was created only in step 3.2), the
> side-effect of this modification is limited to 'b.
No, the effect is limited to the string referenced by 'b itself. Since 'b
was not passed to 'append, but rather its value, the string itself, there
is no side-effect here, nor was there a side-effect when we looked at 'a.
>3.5) The values to which 'a and 'b refer are displayed on the
> console.
Correct.
>3.6) The fifth element of #BARNEY# is modified by inserting a
> copy of the second element of #BARNEY#.
10. Correct. You don't appear to understand why I added this step. More
about that later.
>
>4) Upon completion of the evaluation of 'f, the local context
> created at the beginning of step 3 is destroyed.
11.1 The local context was already created when the function! value was
created. See my comments and proof under 5.x
11.2. The local context is not destroyed. In this example I leave out the
last line to avoid mis-interpretations. This is the function I am using:
f: func [/local a b] [
a: ""
b: copy ""
for i 1 6 1 [
append a i append b i
]
print a print b
]
>> f
123456
123456
The function was evaluated. Subsequently I look at the value referenced by
the function's word b:
>> third second :f
== b:
>> get third second :f
== "123456"
The binding between 'b and the string "123456" is maintained after the
evaluation of the function's body has been completed.
> With no
> "surviving" references to it, the value associated with 'b is
> now subject to garbage collection.
11.3 That is not true either. We force a garbage collection:
>> recycle
11.4 We check whether the third element of f's second block is indeed 'b:
>> third second :f
== b:
It is. Now let's get its value:
>> get third second :f
== "123456"
'b remains bound to the string returned by copy, even after the function's
evaluation was completed AND we forced a garbage collection.
11.5 We can take a peek at the state of the function at this point in time:
>> source f
f: func [/local a b][
a: "123456"
b: copy ""
for i 1 6 1 [
append a i append b i
]
print a print b
]
The literal string referenced by 'a now displays the modifications
resulting from repeatedly appending for's index value to the string. Since
I removed that last line from the example function, the literal string
passed as an argument to copy continues to be empty. And as demonstrated
under 11.4, 'b continues to be bound to the string "12345".
The local context wasn't destroyed and b's binding to the string returned
by copy was preserved, it was not collected by the garbage collector.
>
>5) As long as 'f remains associated with the same entity of type
> 'function! and the value of #BARNEY# remains unchanged (except
> via step 3.6 above), there will remain a chain of references
> from the global environment to the second and fifth elements
> of #BARNEY#. Therefore, their values (and changes thereto)
> will persist between invocations of 'f.
12. Yikes!
12.1 Due to 11.3 we know that the references in the function's local
context survived. The statements that attempt to explain the disappearance
of the string returned by copy and referenced by 'b and that try to explain
the survival of the literal strings are false.
12.2 Let us assume - as you did when you wrote this - that the local
context of the function was destroyed. Your argumentation then is
self-contradictory:
12.2.1 You make your survival of the two literal strings depend on a "chain
of references" from the global context to the literal strings.
12.2.2 You (incorrectly) claimed that the local context of 'f had been
destroyed. Therefore b's reference to the string returned by copy had been
severed. Therefore the string did not survive.
12.2.3 By virtue of 12.2.2 together with the demise of b's reference to the
string returned by 'copy, the reference of 'a to its literal string should
have been severed as well at the time of the destruction of the local
context. Why does a: retain its reference to the literal string, whereas b:
loses its reference to the string returned by copy during the supposed
destruction of the local context?
12.2.4 If a: loses its reference to the literal string as b: does, then the
chain of references is interrupted and the literal string referenced by a:
should also fall victim to the garbage collection, unless there is
something in the nature of a literal string that prevents that.
12.2.5 Which reference is responsible for the survival of the literal
string that acts as copy's argument?
12.3 Let us assume that the ongoing existence of the literal strings
depends on a chain of references beginning in the global context. When we
enter the following, simplified function:
>> f: func [/local a][
a: ""
print a
]
Initially the empty string is not referenced by the word a:
>> first second :f
== a:
>> get first second :f
** Script Error: a has no value.
** Where: get first second :f
Without evaluating the function, we modify the literal string that will be
referenced by 'a when the function is evaluated:
>> insert second second :f "I'm the new value."
== ""
Of course, the string now contains the inserted value:
>> source f
f: func [/local a][
a: "I'm the new value."
print a
]
Which "chain of references" is responsible for the survival of the string
we just modified? Since 'a does not reference a value yet, the chain of
references is interrupted at the set-word! a:. Why then does the literal
string persist in a local context in which it is not being referenced (yet)?
Let's look at a case that makes the workings clearer. First, let's recall
what reduce does:
>> a: 1
== 1
>> b: 2
== 2
>> block: [a b]
== [a b]
>> block
== [a b]
Reduce takes a block argument, evaluates the expressions contained in the
block and returns a new block in which the expressions contained in the
argument block are replaced by the results of evaluating them:
>> reduce block
== [1 2]
Of course reduce does not modify the original block that we passed to it as
an argument:
>> block
== [a b]
Now lets look at what happens when we copy a string in a block and insert
something into it:
>> block: [ head insert copy "" "abcdefg" ]
== [head insert copy "" "abcdefg"]
Our new block contains instructions to copy a string, insert some stuff in
the string returned by copy and return the string beginning at its head.
When we reduce it:
>> reduce block
== ["abcdefg"]
we see the block returned by reduce, which contains the result of
evaluating the expression in the block passed as an argument.
Of course the block passed as an argument wasn't changed. Why should it
have been modified?
>> block
== [head insert copy "" "abcdefg"]
Now we will create a slightly different block by removing the 'copy
function, everything else remains the same:
block: [ head insert "" "abcdefg" ]
When we reduce this block:
>> reduce block
== ["abcdefg"]
we get the same result we previously did using copy.
The block passed as an argument wasn't changed, of course. Or was it?
>> block
== [head insert "abcdefg" "abcdefg"]
we see that the string in the block we passed as an argument was modified.
In this example there are no references, no local contexts of a function.
The only thing referenced was the block in which the expressions were
embedded and the two expressions do not differ with respect to how the word
'block references the block that contains the expressions. So the only form
of reference that did occur is identical in both cases.
With respect to the block that used the copied string I commented that the
original block had not been modified and asked, "Why should it have been
modified?". In the second example, where we used the literal string, the
block passed as an argument should not have been modified either. Why then
did the insertion affect the literal string embedded in the block?
Let us look at a third example. In this example we begin by creating a
copied string that we assign to the global word copied-string:
>> copied-string: copy ""
== ""
Then we create a new block, which inserts "abcdefg" into the string
referenced by copied-string:
>> block: [head insert copied-string "abcdefg"]
== [head insert copy-string "abcdefg"]
Remember that copied-string was created using 'copy, like the string in the
block.
We now reduce this block:
>> reduce block
== ["abcdefg"]
and again reduce returns a block that contains the string as it appears
after insert and head were applied to it.
Of course, the globally referenced string, copied-string, has been modified
in the process:
>> copied-string
== "abcdefg"
Why was it modified? After all it was a copied string. Yes, it was a copied
string, like in our first example:
>> block: [ head insert copy "" "abcdefg" ]
but it was assigned a global word, copied-string, and that global word,
copied-string, extended the context of the string returned by copy.
Subsequently, reduce evaluated the expressions in the block
block: [head insert copied-string "abcdefg"]
and inserted the string "abcdefg" in the string globally bound by being
referenced by the word 'copied-string.
Here is a slightly different example:
>> block: [head insert string-copy: copy "" "abcdefg"]
== [head insert string-copy: copy "" "abcdefg"]
Here we generate the copy in the block and reference the string returned by
copy with the word 'string-copy that we create on the fly.
When we reduce this block
>> reduce block
== ["abcdefg"]
once again reduce returned the result of evaluating the expressions of
block, the block has not been modified:
>> block
== [head insert string-copy: copy "" "abcdefg"]
however, we have now created a new global word, string-copy, which
references the string as it exists after the string "abcdefg" was inserted
into the string returned by copy:
>> string-copy
== "abcdefg"
In summary: the block sans 'copy
block: [ head insert "" "abcdefg" ]
acts like our last two examples. In those two examples the modifications
made to the string resulting from the word copy were preserved, because the
string returned by copy was referenced by a global word in both cases. In
the first case we prepared the global word copied-string, in the second
example the word string-copy was created on the fly, when reduce forced the
evaluation of the expressions contained in the block.
Now here is what I'm saying: the literal string in the example
block: [ head insert "" "abcdefg" ]
preserves its modifications, like the strings that were referenced by
global words in the two other examples. This is what I mean by "the literal
string is global". Literal series values, such as strings and blocks, no
matter in which local context they are used, always act like series that
are being referenced by a global word, with respect to persistence, even
though they are not being explicitly referenced by a global word. They are
- if you want - referenced by an implied global word. Short: Literal values
are bound in the global context, without having to be explicitly referenced
in the global context.
>
>"Aha!", one might say, "The words 'global' and 'local' are all thru
>that description!" And so they are. But most of those occurrences
>are irrelevant to the point at hand.
Yeah sure. "most of those occurrences are irrelevant." Hmmm, are there any
that are not irrelevant? How man relevant "occurrences" would it take to
prove that your theory requires the distinction between local and global
contexts to work? Let me see here � hmmm � exactly one? Now, of course,
your theory doesn't work anyway, as shown above. Disregarding that, how can
you claim that a theory, which argues as follows:
>4) Upon completion of the evaluation of 'f, the local context
> created at the beginning of step 3 is destroyed.
> With no
> "surviving" references to it, the value associated with 'b is
> now subject to garbage collection.
And
>5) As long as 'f remains associated with the same entity of type
> 'function! and the value of #BARNEY# remains unchanged (except
> via step 3.6 above), there will remain a chain of references
> from the global environment to the second and fifth elements
> of #BARNEY#. Therefore, their values (and changes thereto)
> will persist between invocations of 'f.
You say:
"the local context" ... "is destroyed." ... "the value associated with 'b
is now subject to garbage collection."
You say:
> ... there will remain a chain of references
> from the global environment to the second and fifth elements
> of #BARNEY#. Therefore, their values (and changes thereto)
> will persist between invocations of 'f.
You say:
>But most of those occurrences
>are irrelevant to the point at hand.
Perhaps most are. The two I quoted are not "irrelevant" to the "point at
hand". These two "occurrences" of "local" and "global" are fundamental to
your explanation of why some strings survive (because they are protected by
a chain of references that begins in the GLOBAL context) and others are
destroyed (because references to them in LOCAL context are destroyed
together with their local context). Again, your hypothesis has not been
proven wrong repeatedly. But its worth mentioning that - had your theory
been correct - it would not have accomplished its explanation without using
"local" and "global" to distinguish between the behavior of the two
distinct types of strings.
12.4 Result:
You fail to provide a hypothesis that manages to explain the difference in
the behavior of the two strings refererenced by 'a and 'b respectively
without relevantly using "local" and "global", for two reasons:
12.4.1. Your explanation has been demonstrated to be incorrect in almost
all aspects.
12.4.2 Had your explanation been correct, it had relied on "local" and
"global" contexts to explain the difference between the behavior of the two
values referenced by the two words.
>The real driving factor for
>this entire discussion has been the question of why an evaluation
>that sets a word's value based on a literal string can produce
>different results on repeated use. That phenomenon occurs regardless
>of whether the word in question is global or not.
13.1 You say
>"sets a word's value based on a literal string"
The word's value is not set BASED on a literal string. The word is set as a
reference to a value, which IS a literal string. The VALUE IS a literal
string. The WORD is set as a REFERENCE to that value.
13.2: I never claimed that whether the word was global or local has
anything to do with the persistence of literal strings. My statement was
that literal strings are persistent because the literal strings themselves
are automatically defined in the global context. I was not commenting on
the context of the words referencing them!
You say that the question we are trying to answer is:
>...why an evaluation
>that sets a word's value based on a literal string can produce
>different results on repeated use.
That is incomplete. The question we are trying to answer at this point -
and that is the question you undertook to answer in your contribution - is
why there is a difference between how the literal strings are handled
versus how strings returned by copy are handled IN A LOCAL CONTEXT.
No one has ever expressed surprise about how this works in the GLOBAL
CONTEXT.
14. Experienced programmers are surprised when they find that in REBOL
a) in the LOCAL CONTEXT of a FUNCTION
b) the evaluation of an assignment of a word local to the function as a
reference to a literal string,
c) which starts out as an empty string literal string,
d) at the time the function is re-entered
e) becomes the assignment of the local word to a literal string
f) that has retained the data it was populated with during
g) the prior evaluation of the function.
That's what we are trying to explain. That's a different ballgame.
15. The reason people are surprised is quickly explained:
In mainstream programming languages, such as C, literal values are
constants. If you are dealing with the literal value "", your C program
cannot happily concatenate characters into that literal value, because the
literal value is a constant.
If you do a strcat("", "rather long string containing plenty of garbage"),
you will sooner or later find that your program crashes because you are
corrupting memory.
When programmers who, for instance, are used to working with C encounter
the REBOL instruction:
a: ""
Their intuition tells them that the literal string will not be modified. It
takes a while to get used to the idea that in REBOL literal series values are
a) themselves the targets of series modifying instructions,
b) shrink and grow as needed, and
c) retain modifications made to them.
In other words
a = ""
is the assignment of an empty string in C to the variable 'a. If this
assignment appears in a function, it will always be the assignment of an
empty string constant. If the empty string literal in C should change its
value, you have a bug. That assignment looks very similar to but is
something completely different from the following expression in REBOL:
a: ""
In REBOL this is the assignment of the word 'a as a reference to a literal
string that for now is empty, but that will be persistently modified by any
changes made to the literal string by referencing it using the word 'a or
any other word that may reference it later, or by any other means.
You wrote:
>That phenomenon occurs regardless
>of whether the word in question is global or not.
It was not my hypothesis that the literal string survives due to the word
being global! Both words "in question" were LOCAL to the function. No one
ever argued that the literal string survived because the word referencing
it was global. The word referencing the literal string was
a) set local in the function's spec block;
b) was understood to be local in all the arguments I contributed.
>It doesn't even
>require a function! For example:
>
> >> trick: [a: "" append a "X"]
> == [a: "" append a "X"]
> >> do trick
> == "X"
> >> do trick
> == "XX"
> >> do trick
> == "XXX"
> >> trick
> == [a: "XXX" append a "X"]
>
>Which can be made "non-accumulative" by:
>
> >> trick2: [a: copy "" append a "X"]
> == [a: copy "" append a "X"]
> >> do trick2
> == "X"
> >> do trick2
> == "X"
> >> do trick2
> == "X"
> >> trick2
> == [a: copy "" append a "X"]
>
>The accumulation stops because 'trick2 uses 'copy to create a fresh
>string each time, and the modification is not made with a reference
>to a persistent string.
If that is the correct and complete explanation, then what is happening here:
>> false-abstraction: [a: copy b: "" append a "X" append b a]
== [a: copy b: "" append a "X" append b a]
>> do false-abstraction
== "X"
>> do false-abstraction
== "XXX"
>> do false-abstraction
== "XXXXXXX"
>> do false-abstraction
== "XXXXXXXXXXXXXXX"
>
>The subtle point over which so many of us have tripped (judging by
>the discussions on the mailing list) is that the the adjacent
>quotation marks in the text entered to define 'trick serve simply
>as the INITIAL VALUE of the second element of 'trick, and that
>subsequent modifications (via any reference) to that second element
>will persist as long as 'trick does. That is the only point where
>the concept of "global" appears, as far as I can tell. Values are
>not subject to garbage collection as long as there is any reference
>(or chain of references) to them from the global context. "Local"
>doesn't enter into it at all.
Wrong.
>> false-abstraction-2: [append "" "X" ]
== [append "" "X"]
>> do false-abstraction-2
== "X"
>> do false-abstraction-2
== "XX"
>> do false-abstraction-2
== "XXX"
>> do false-abstraction-2
== "XXXX"
>> do false-abstraction-2
== "XXXXX"
>> false-abstraction-2
== [append "XXXXX" "X"]
Here the literal string in false-abstraction-2 was modified, even though it
wasn't being referenced by anything global.
>
>The fact that one can defeat the attempt to generate a fresh string
>by using self-modifying code doesn't change the fact that, when one
>is allowed to do so, using a fresh string for each invocation is a
>viable way around persistence.
Humph! Oh, my. If you can't explain it, declare it disreputable. Joel, are
you dealing in politics or science? Or does dealing in science require you
to mix in a good degree of politics?
Case 1: Self Modifying Code?
f: func [/local a] [
a: ""
insert a "12345"
]
Case 2: Self Modifying Code?
f: func [/local a b] [
a: ""
insert a "123456"
b: copy a
]
Case 3: Self Modifying Code?
f: func [/local a b] [
b: copy a: ""
insert a "123456"
print b
]
>> f
>> f
123456
>> f
123456123456
>> f
123456123456123456
Case 3 is no more self modifying code than case 1. If you prefer Ladislav's
attempts at de-self-modifying code by making everything global, how do you
like this:
Case 4:
f: func [] [
b: copy a: ""
insert a "123456"
print b
]
>> f
123456
>> f
123456123456
>> f
123456123456123456
No self-modifications here, sir, if I am to believe Ladislav. This stuff
only looks self modifying to you if you follow Ladislav in his statement
that the expression
a: copy ""
is a dynamic 'a initializer, in contrast to what he believes is a static
word initializer:
b: ""
This the discussion Ladislav and I where having, when you joined us:
Ladislav wrote:
>So, where's the difference?
>We must turn to a different notion,
>the "lifetime" of data, which can be
>described in terms like "static" vs. "dynamic".
>
>So, where is the difference between A and B?
>The correct answer is:
>B is created dynamically when the interpreter
>encounters the expression:
>
>copy ""
>
>, which is a sign of dynamic data creation.
>
>, but A is not created when the interpreter encounters the expression:
>
>a: ""
>
>, because in the latter expression the data known as A only get it's name A.
[snip]
>So, the difference between A and B
>lies in the fact, that the creation of B
>is "dynamic" as opposed to A, where the
<creation is "static" and the notions
>of "global" vs. "local" are misleading in this case.
So the expression
copy ""
is a sign of "dynamic data creation". And "dynamic data creation" is
responsible for the difference between A and B. NOT.
A terminology is created based on abstractions. You abstract from,
disregard or ignore non-essential details to reduce complexity, to get to
the heart of the matter, which leaves you with abstractions that collect
the important principles of an observation, or of a series of observations.
You then name the abstractions you came up with and you end up with a
terminology that, if applied correctly, helps you reflect on specific,
isolated, complex cases and understand these cases in simple terms that
relate the isolated event to a system of understood relationships.
The problem is that you have to be careful not to ignore important or
essential details when you create your abstractions. By emphasizing the
role of copy in the creation of a string for b to reference and ignoring
copy's argument, Ladislav - and you with him, are making a false abstraction.
a: copy ""
is not a "dynamic data creator". The word copy returns a copy of its
argument. If you insist on using Ladislav's terminology, then copy as a
"dynamic data creator" is severely limited by the "static" character of its
argument.
And here you see how inappropriate Ladislav's terminology is. It is really
the "dynamic" character of copy's argument, the ability of the literal
string to accumulate values over time, which destroys the illusion that
copy's "dynamic" character controls the assignment. To ignore the argument
to 'copy as something "static" and non-essential, abstract 'copy from its
argument, then declare 'copy a dynamic data creator misses the point. It
misses the point because REBOL is a Relative EBOL and you are bound to pay
a penalty when you ignore deatails in a programming language which prides
itself of being context based.
In other words, the word copy protects
a) its argument against being affected by changes made to the value it
returns, and
b) protects its return value against modifications made to its argument
after copy has been evaluated, but only until copy is evaluated anew.
But the word copy does NOT
a) protect its argument from being affected by changes made to it (the
argument value);
b) guarantee that the value it returns will be the same one every time it
is evaluated.
Therefore 'copy may return an empty string the first time it is evaluated,
because and provided that its argument was an empty string. If its argument
is subsequently modified, it will return a different value. If its argument
is being modified to track the changes made to its return value, then copy
will return a value that is a copy of the latest version of its previous
return value, whenever 'copy is evaluated.
In this last case, the "static", or more appropriately the "dynamic" nature
of its argument neutralizes the "dynamic" nature of 'copy. To rely on
copy's "dynamic" nature is therefore inappropriate. How effective its
dynamics are has to be defined relative to its argument. Ignoring the
argument leads to a false abstraction, the abstraction "copy is a sign of
dynamic data creation", and in turn fails when the literal string it is
copying is modified.
You cannot discuss which value 'b will reference when the following
function is evaluated:
f: func [/local b] [
b: copy ""
]
without including copy's argument in your considerations. Remember that
REBOL prides itself as being a context dependant language. You cannot
isolate a detail and discuss it in absolute terms, because REBOL's behavior
is essentially relative ... to some context. What 'b will evaluate to
depends on the modifications made to the context in which the function is
evaluated. At the time the function is evaluated, the literal string that
will be passed to 'copy may very well have been populated with some data.
Or it may have become a completely different datatype:
f: func [/local a] [
a: copy ""
]
>> change at second :f 3 [make block! [message: "guess what it's no longer
a string."]]
== [
]
>> source f
f: func [/local a][
a: copy make block! [message: "guess what it's no longer a string."]
]
>> f
== [message: "guess what it's no longer a string."]
>>
If you create your terminology by abstracting from copy's argument,
isolating the dynamic nature of 'copy from the possibly "static" nature of
its argument, you end up with a false abstraction, an abstraction that
ignores as non-essential an essential factor. You end up paying a penalty.
The penalty is that your terminology does not reflect the REBOL code you
are discussing and therefore it does not allow you to predict how REBOL
will behave.
In summary, my function was intended to document that the explanation that
was based on the fact that copy returns a new string each time it is
evaluated ignores the fact that new string is a copy of the literal string.
The "dynamic" nature of the string returned by copy is limited by the
"dynamic" nature of its argument.
If you find it disreputable to embed the expression that modifies the value
of the literal string in the function itself, be my guest:
>> f
== ""
>> insert third second :f "No self modifying code involved here."
== ""
>> f
== "No self modifying code involved here."
>> clear third second :f
== ""
No self modifying code here. My point however remains.
1. Literal values are global. This means that they do not require a global
reference to act as though they were being referenced from within the
global context. If you want, they act as though REBOL provided an implied
global reference for them.
2. The term "dynamic data creation" as applied to a word referencing the
return value of copy is misleading, because copy's "dynamic character" is
limited by the "dynamics" of its argument.
3. Isn't it amusing to see self modifying code bashed when the programming
language being discussed prides itself of being context dependant and
expresses as much in the first word of its acronym: R stands for Relative!
4. A terminology that sets out to describe REBOL and outlaws some of
REBOL's properties, such as its relative character, which includes self
modifying code, may end being a wonderful terminology. With the one
drawback - that is not adequate for describing the programming language REBOL.
Elan