[REBOL] Re: R: Re: R: Re: Links ? Pointers ?
From: moliad:gm:ail at: 14-Sep-2009 16:56
Hi Giuseppe,
Sorry I didn't check the ML for a few days and yours slipped in the cracks!
Here is a long and verbose explanation of most of what happens. If
something isn't clear, ask a precise question and I'll do my best to
explain it further.
so here we go... saddle-in, this is going to be a big ride into the
depths of some REBOL Guru level secrets. ;-)
I'll start by reposting the original code snippet as a reference:
person: context [
uid: func [/set value][data: [#[none]] either set [data/1: value][data/1]]
name: "me"
]
....
>> person/uid/set 222
== 222
>> managers/ceo
== 222
about functions as values
------------------------------------
Basically, all rebol datatypes are evaluated when they are encountered
by the interpreter. because functions require no more syntax than a
normal variable, they can be silently "slipped-in" as if they WHERE a
simple variable (cause in fact they are! :-).
The 'UID function, when created by the 'FUNC function creates a
context for itself. This context persists for as long as the function
exists.
If you notice the following:
data: [#[none]]
I assign a block! to 'DATA (I **DON'T** copy it at each function
evaluation). This block is actually part of the 'UID function's
context when the function is created. So it persists throughout that
function's existence... This means every time 'UID is evaluated, 'DATA
really is the same block, every time.
When I simply ask for the value of 'UID (person/uid), the function is
evaluated as normal, and it returns the first data of the persistent
block.
When I use the function's /SET refinement, I simply stores a new value
into this persistent block:
data/1: value
As a side note, remember that you can use blocks as indirections, a
bit like double referencing in C. Its a bit like saying get the first
value pointed to by the block pointed to by 'DATA. (I prefer to use
the term "refers" and "contains" respectively though).
About sharing functions across objects:
--------------------------------------------------------
Normally, when you make objects and use another object as the basis,
all of the functions are copied to the new object bound and bound to
it. This means that any words which are present in the body of the
function will now refer to those found in that object, or to those in
the arguments of the function. For example, if I had done this:
employee: make person [
title: "Workaholic"
]
And tried the function trick, the 'UID function inherited from 'PERSON
would not be shared because 'EMPLOYEE would contain a New &
independent copy of the 'UID function with its own persistent function
context.
The trick is to tell 'MAKE, what we we explicitly want the 'UID to be:
('CONTEXT is a mezzanine function which simply does: make object!)
employee: context [
uid: get in person 'uid
title: "Workaholic"
]
Here you see that we tell 'EMPLOYEE to use the exact same function as
the one in person. We 'GET it from the other object, and 'MAKE will
assign it to the new object, at the location referred to by word 'UID
in this new object.
It won't be bound to the new 'EMPLOYEE object, and 'UID will actually
be the same function as person/uid, NOT a copy of it.
This is a VERY powerful feature of REBOL.
From this point on, employee/uid and person/uid, being the same
function, really share the same function context, so will set and get
the same value.
Function hack level 2
-----------------------------
You said you wanted other examples of the function trick... Here we'll
go Fubar weird!!!
Lets refer to values of **another** object using 'SELF... :-)
;------------------------ the code -------------------------
person: context [
full-name: ["luke" "skywalker"]
first-name: func [/set val][either set [self/full-name/1:
val][self/full-name/1]]
last-name: func [/set val][either set [self/full-name/2:
val][self/full-name/2]]
]
employee: context [
first-name: get in person 'first-name
last-name: get in person 'last-name
full-name: func [][reduce [first-name last-name]]
]
;--------------------------------------------------------------
Within 'PERSON, we create a similar function-as-value trick, but this
time, we use 'SELF to refer to the context (object!) the function is
bound to, and then interact with another member of that object using
path notation.
'EMPLOYEE, if you recognize the pattern explained earlier, simply
borrows ('GETs) two of those functions-as-values and stores them in
its own context... but notice a HUGE detail. 'SELF in these
functions, still refers to the 'PERSON object, because the functions
are still bound to 'PERSON !!!
'FULL-NAME within 'EMPLOYEE, is bound normally since it is created by
'EMPLOYEE 's make. Meaning, it will evaluate whatever value is
referred to by the words 'FIRST-NAME and 'LAST-NAME within 'EMPLOYEE.
BUT!!! These, as we just explained, are values taken from the other
object, namely 'PERSON. So basically, you have an object calling
methods from another object, directly. This is far beyond the level
of control you can achieve with traditional inheritance and
polymorphism, IMHO.
You could go a step further and if you really wanted to go nuts with
this, you could change the person which is assigned to the
function-as-value referred by 'EMPLOYEE on the fly (but I won't go
there for risk of loosing you for now ;-)
All of this is AFAIK, a feature very few, if any, other languages
allow you to do so "legally", safely and with so much detail and
dynamic control.
here are a few lines of console code which play around with the above
level 2
function hack:
>> employee/full-name
== ["luke" "skywalker"]
>> employee/first-name/set "bill"
== "bill"
>> employee/full-name
== ["bill" "skywalker"]
>> person/full-name
== ["bill" "skywalker"]
>> same? person/full-name/1 employee/first-name
== true
>> same? person/first-name employee/first-name
== true
'SAME is a mezzanine function that only returns True if its the same
string, not just by its text content like equal?... it really is the
same string "pointer" in memory.
sweet isn't it ? :-D
about other uses of the function hack
-----------------------------------------------------
you might want to replace a value by an SQL statement
function-as-a-value which is executed every time a value is needed...
and if you set the value, it is inserted back directly... this way,
you are sure that your data is coherent.
In VID you can put functions instead of values for some facets...
example are colors, texts & effects which change every time the face
is shown... with a little tweak in the redraw :
example:
view layout [
button with [
texts: reduce [ does [to-string counter] does [random "ABCDE"]]
colors: reduce [ does [random white] does [random white]]
effects: reduce [
does [reduce ['gradient 0x1 color color - 75.75.75]]
does [reduce ['gradient 0x-1 color color - 75.75.75]]
]
]
feel [
redraw: func [face act pos /local state][
counter: counter + 1
if all [face/texts face/texts/2] [
face/text: either face/state [face/texts/2] [face/texts/1]
]
if face/edge [face/edge/effect: pick [ibevel bevel] face/state]
state: either not face/state [face/blinker] [true]
if face/colors [face/color: either face/state
[face/colors/2] [face/colors/1]]
if face/effects [face/effect: either face/state
[face/effects/2] [face/effects/1]]
]
]
]
here face/colors/2 is evaluated each time the face is drawn... so you
might want to put an sql statement which checks if an error occurred
on the db for example. whenever the face is refreshed it will be up
to date... which is pretty useful.
the view face is not a normal object so most of the primitive facets
(like color) will not respond if they are set to functions because the
view engine actually uses the values directly.
as an example if you put a function in face/text it will actually
display ?function? as the text...
but it should give you ideas on other uses... just the same.
-MAx
On Thu, Sep 3, 2009 at 5:38 AM, Giuseppe Chillemi
<gchillemi-aliceposta.it> wrote: