TECHNICAL ESSAY/CHALLENGE(s) - "Expression Based"?
[1/11] from: joel::neely::fedex::com at: 2-Oct-2000 23:59
It's late; I'll try to be brief (for me ;-).
DEFINITION:
Language geeks use the term "referential transparency" to mean that
things that have the same value are freely interchangeable. This
property of a programming language is usually held to be A Good Thing,
as it keeps down the number of exceptions one has to learn to be able
to use the language. REBOL seems to break referential transparency
in several places. I'm very interested in minimizing the effects
of this.
INTENT:
What can we do with expressions? One important thing is that they
let us describe a pattern of computation which can be re-used easily
(either by substituting a new value in a copy of the expression, or
by changing a variable's value and re-evaluating a copy of the same
expression). Most programming languages use expressions in places
where the designer expects us (the programmers) to want to change
things; most programming languages avoid expressions in places where
the designer expects us (the programmers) NOT to want to change
things (or where the designer doesn't want us to do so).
All else being equal, we'd expect an "Expression Based" language to
be very generous about where we are allowed to use expressions.
Sometimes REBOL breaks that expectation.
EXAMPLE (FOR SAKE OF ILLUSTRATION ONLY):
Suppose I want to take a bunch of (natural) numbers and separately
total the even and odd members of the bunch. One way to do this
is as follows...
>> bunchanums: [3 1 35 8 4 5 52 42 19 13 32 43 81 2 6 34 46]
== [3 1 35 8 4 5 52 42 19 13 32 43 81 2 6 34 46]
>> tally: make object! [
[ tot: 0
[ zero: does [tot: 0]
[ up: func [/by n [number!]] [tot: tot + either by [n] [1]]
[ down: func [/by n [number!]] [tot: tot - either by [n] [1]]
[ now?: does [tot]
[ ]
We now have a nice reusable concept, which we can apply thus...
>> use [evens odds] [
[ evens: make tally []
[ odds: make tally []
[ foreach num bunchanums [
[ either even? num [evens/up/by num] [odds/up/by num]
[ ]
[ print ["evens:" evens/now? " odds:" odds/now?]
[ ]
evens: 226 odds: 200
There's something ugly inside that loop (don't be fooled by the
brevity or triviality of this example!) We really want to invoke
the same method and arguments to whichever tally object we select
for each number in the bunch. In other words, what varies is the
object, not the method or argument(s). Having to repeat method
names and argument lists between the two alternatives is tedious,
error-prone, and a potential maintenance landmine. (Imagine how
this gets progressively worse with more than two alternatives!)
We might try to work around that by...
>> use [evens odds] [
[ evens: make tally []
[ odds: make tally []
[ foreach num bunchanums [
[ use [whichone] [
[ whichone: either even? num [evens] [odds]
[ whichone/up/by num
[ ]
[ ]
[ print ["evens:" evens/now? " odds:" odds/now?]
[ ]
evens: 226 odds: 200
Well, this solves the duplication problem, but at the cost of another
ugliness: we need a whole new name just to remember the object we've
selected long enough to make a method call against it! The observant
wizards among us will notice that I blew right past...
>> use [evens odds] [
[ evens: make tally []
[ odds: make tally []
[ foreach num bunchanums [
[ use [whichmeth] [
[ whichmeth: get in either even? num [evens] [odds] 'up
[ whichmeth/by num
[ ]
[ ]
[ print ["evens:" evens/now? " odds:" odds/now?]
[ ]
evens: 226 odds: 200
...as it is even more obscure (but we'll revisit it later).
What would be nice here would be to invoke a method call against an
object which is the value of AN EXPRESSION...
(*WARNING* *HYPOTHETICAL SYNTAX FOLLOWS*)
use [evens odds] [
evens: make tally []
odds: make tally []
foreach num bunchanums [
(either even? num [evens] [odds])/up/by num
]
print ["evens:" evens/now? " odds:" odds/now?]
]
...but, alas, that isn't REBOL anymore!
How do we get rid of that pesky name? How about...
>> use [evens odds] [
[ evens: make tally []
[ odds: make tally []
[ foreach num bunchanums [
[ do reduce [
[ make path! reduce [
[ either even? num ['evens] ['odds]
[ 'up 'by
[ ]
[ num
[ ]
[ ]
[ print ["evens:" evens/now? " odds:" odds/now?]
[ ]
evens: 226 odds: 200
Yowch! We got rid of the name, but at what cost!?!?! In addition
to being severely obscure, the loop body doesn't scale well AT ALL!
(For example, suppose we were fetching the object out of a data
structure -- e.g., via select or indexing into an array -- and
didn't even have a name for it.)
Thus we come to...
CHALLENGE #1:
Given an object (which is determined as the value of an expression,
not by having "it's name" in hand), how can we call a predetermined
method of that object, complete with argument(s) and refinement(s)?
In other words, the object may vary, but the function and arguments
will not.
VARIATION ON THE THEME:
Suppose we want to tally up the sum of the even numbers less the
sum of the odd numbers in our previous bunch. Using our handy-
dandy Acme tally object, we can write...
>> use [diffs] [
[ diffs: make tally []
[ foreach num bunchanums [
[ either even? num [diffs/up/by num] [diffs/down/by num]
[ ]
[ print ["net sum:" diffs/now?]
[ ]
net sum: 26
There's that duplication rearing its ugly head again! Now the only
thing that changes -- that we need to compute -- is which method to
call, and NOT which object in which to call it nor the argument(s).
We can fall back to using a temporary name...
>> use [diffs] [
[ diffs: make tally []
[ foreach num bunchanums [
[ use [whichfunc] [
[ whichfunc: either even? num ['up] ['down]
[ diffs/:whichfunc/by num
[ ]
[ ]
[ print ["net sum:" diffs/now?]
[ ]
net sum: 26
...or use the pseudo-function-reference trick from previously...
>> use [diffs] [
[ diffs: make tally []
[ foreach num bunchanums [
[ use [whichmeth] [
[ whichmeth: get in diffs either even? num ['up] ['down]
[ whichmeth/by num
[ ]
[ ]
[ print ["net sum:" diffs/now?]
[ ]
net sum: 26
...but these still seem awkward and unnatural, IMHO. What we're
really trying to express is simply...
(*WARNING* *HYPOTHETICAL SYNTAX FOLLOWS*)
use [diffs] [
diffs: make tally []
foreach num bunchanums [
diffs/(either even? num [up] [down])/by num
]
print ["net sum:" diffs/now?]
]
...give or take a couple of ticks. ;-)
Thus we close with...
CHALLENGE #2:
Given a specific object in hand, how can we invoke one of its
methods (which one is determined by an evaluation) with a given
list of arguement(s)/refinement(s)? In other words, the choice
of function is varying, but the object and arguments are not.
THANKS...
...in advance to all who give this little matter some thought
and post their ideas!
-jn-
[2/11] from: al:bri:xtra at: 2-Oct-2000 23:10
> What would be nice here would be to invoke a method call against an object
which is the value of AN EXPRESSION...
> (*WARNING* *HYPOTHETICAL SYNTAX FOLLOWS*)
> use [evens odds] [
<<quoted lines omitted: 6>>
> ]
> ...but, alas, that isn't REBOL anymore!
I believe this is called "continuation". This is present in Scheme, I
believe and it's quite nice. I believe "continuations" were in an earlier
version of Rebol at one time, but was dropped in subsequent versions.
I agree this would be very nice to have in Rebol.
> ...but these still seem awkward and unnatural, IMHO. What we're really
trying to express is simply...
> (*WARNING* *HYPOTHETICAL SYNTAX FOLLOWS*)
> use [diffs] [
<<quoted lines omitted: 5>>
> ]
> ...give or take a couple of ticks. ;-)
Would this do?
[
Rebol [
File: %Tally.r
]
tally: make object! [
tot: 0
zero: does [tot: 0]
up: func [/by n [number!]] [print "up!" tot: tot + either by [n] [1]]
down: func [/by n [number!]] [print "down!" tot: tot - either by [n][1]]
now?: does [tot]
]
bunchanums: [3 1 35 8 4 5 52 42 19 13 32 43 81 2 6 34 46]
use [diffs] [
diffs: make tally []
foreach num bunchanums [
do probe reduce [
make path! compose [
diffs (either even? num ['up] ['down]) by
] num
]
]
diffs/now?
]
]
>> do %tally.r
[diffs/down/by 3]
down!
[diffs/down/by 1]
down!
[diffs/down/by 35]
down!
[diffs/up/by 8]
up!
[diffs/up/by 4]
up!
[diffs/down/by 5]
down!
[diffs/up/by 52]
up!
[diffs/up/by 42]
up!
[diffs/down/by 19]
down!
[diffs/down/by 13]
down!
[diffs/up/by 32]
up!
[diffs/down/by 43]
down!
[diffs/down/by 81]
down!
[diffs/up/by 2]
up!
[diffs/up/by 6]
up!
[diffs/up/by 34]
up!
[diffs/up/by 46]
up!
== 26
This should solve the other challenges as well. Just shift around the
parenthesis to suit.
Andrew Martin
And,... I even had time to eat dinner! :-)
Andrew Martin
ICQ: 26227169
http://members.nbci.com/AndrewMartin/
http://members.xoom.com/AndrewMartin/
[3/11] from: al:bri:xtra at: 3-Oct-2000 0:22
This is a bit shorter, but still not as nice as the Rebol way as used in
System and View.
[
Rebol [
File: %Tally.r
]
tally: make object! [
tot: 0
zero: does [tot: 0]
up: func [/by n [number!]] [print "up!" tot: tot + either by [n] [1]]
down: func [/by n [number!]] [print "down!" tot: tot - either by [n]
[1]]
now?: does [tot]
]
bunchanums: [3 1 35 8 4 5 52 42 19 13 32 43 81 2 6 34 46]
; A better name for this function would be appreciated.
mpc: func [Block [block!]] [
make path! compose Block
]
; A better name for this function would be appreciated.
dr: func [Block [block!]] [
do reduce Block
]
use [diffs] [
diffs: make tally []
evens: make tally []
odds: make tally []
foreach num bunchanums [
dr [mpc [diffs (either even? num ['up] ['down]) by] num]
dr [mpc [(either even? num ['evens] ['odds]) up by] num]
]
print ["diffs/now?" diffs/now?]
print ["evens/now?" evens/now?]
print ["odds/now?" odds/now?]
]
]
>> do %Tally.r
down!
up!
down!
up!
down!
up!
up!
up!
up!
up!
down!
up!
up!
up!
up!
up!
down!
up!
down!
up!
up!
up!
down!
up!
down!
up!
up!
up!
up!
up!
up!
up!
up!
up!
diffs/now? 26
evens/now? 226
odds/now? 200
AllenK has a better grasp of the ideal Rebol way to do this. But he says it
is difficult to explain. More about this later, or you could pester Allen.
:-)
Andrew Martin
While chatting to fellow Rebolians...
ICQ: 26227169
http://members.nbci.com/AndrewMartin/
http://members.xoom.com/AndrewMartin/
[4/11] from: al:bri:xtra at: 3-Oct-2000 0:45
And this is more like the Rebol way, as I imperfectly understand it:
[
Rebol [
File: %"Tally Ho.r"
]
Tally!: make object! [
make-Tally: does [make object! [Total: 0]]
Zero: func [Tally [object!]] [Tally/Total: 0 Tally]
Up: func [Tally [object!] /by N [number!]] [
Tally/Total: Tally/Total + either by [N] [1] Tally
]
Down: func [Tally [object!] /by N [number!]] [
Tally/Total: Tally/Total - either by [N] [1] Tally
]
Now?: func [Tally [object!]] [Tally/Total]
]
bunchanums: [3 1 35 8 4 5 52 42 19 13 32 43 81 2 6 34 46]
do bind [
use [diffs evens odds] [
diffs: make-Tally
evens: make-Tally
odds: make-Tally
foreach num bunchanums [
either even? num [
Up/by Diffs num
Up/by Evens num
][
Down/by Diffs num
Up/by Odds num
]
]
print now? Diffs
print now? evens
print now? odds
]
] in Tally! 'self
]
>> do %"Tally Ho.r"
26
226
200
Note how small the actual tally object is: "[Total: 0]". Also note that the
binding is used like a dialect, but allows arbitrary Rebol script to be
used.
Andrew Martin
ICQ: 26227169
http://members.nbci.com/AndrewMartin/
http://members.xoom.com/AndrewMartin/
[5/11] from: g:santilli:tiscalinet:it at: 3-Oct-2000 10:19
[joel--neely--fedex--com] wrote:
> CHALLENGE #1:
> Given an object (which is determined as the value of an expression,
<<quoted lines omitted: 4>>
>> do-in: func [obj [object!] code [block!]] [do bind/copy code in obj 'self]
>> tally: make object! [
[ tot: 0
[ zero: does [tot: 0]
[ up: func [/by n [number!]] [tot: tot + any [n 1]]
[ down: func [/by n [number!]] [tot: tot - any [n 1]]
[ now?: does [tot]
[ ]
>> bunchanums: [3 1 35 8 4 5 52 42 19 13 32 43 81 2 6 34 46]
== [3 1 35 8 4 5 52 42 19 13 32 43 81 2 6 34 46]
>> use [evens odds] [
[ evens: make tally []
[ odds: make tally []
[ foreach num bunchanums [
[ do-in either even? num [evens] [odds] [up/by num]
[ ]
[ foreach tally [evens odds] [prin [tally ":" do-in get tally
[now?] " "]]
[ prin newline
[ ]
evens : 226 odds : 200
> CHALLENGE #2:
>
> Given a specific object in hand, how can we invoke one of its
> methods (which one is determined by an evaluation) with a given
> list of arguement(s)/refinement(s)? In other words, the choice
> of function is varying, but the object and arguments are not.
>> use [diffs] [
[ diffs: make tally []
[ foreach num bunchanums [
[ do-in diffs either even? num [[up/by num]] [[down/by
num]]
[ ]
[ print ["net sum:" diffs/now?]
[ ]
net sum: 26
(using COMPOSE here you may get closer to your hypothetical
syntax...)
Doing-in,
Gabriele.
--
Gabriele Santilli <[giesse--writeme--com]> - Amigan - REBOL programmer
Amiga Group Italia sez. L'Aquila -- http://www.amyresource.it/AGI/
[6/11] from: joel:neely:fedex at: 3-Oct-2000 7:33
[giesse--dsiaq1--ing--univaq--it] wrote:
> >> do-in: func [obj [object!] code [block!]] [do bind/copy code in obj 'self]
> >> tally: make object! [
<<quoted lines omitted: 17>>
> [ ]
> evens : 226 odds : 200
Excellent! (I also like the rewrite of the final report!)
> > CHALLENGE #2:
> >
<<quoted lines omitted: 11>>
> [ ]
> net sum: 26
Well, this works, but notice that the "/by num" part still has
to be duplicated between the alternatives. Do you have a way
around that?
> (using COMPOSE here you may get closer to your hypothetical
> syntax...)
>
Being a Bear of Small Brain (and I was up late last night and
haven't had sufficient coffee yet this morning), I must ask for
an example of what you had in mind here.
Thanks!
-jn-
[7/11] from: joel:neely:fedex at: 3-Oct-2000 8:04
Another fine response!
[Al--Bri--xtra--co--nz] wrote:
> This is a bit shorter, but still not as nice as the Rebol way as
> used in System and View.
<<quoted lines omitted: 15>>
> make path! compose Block
> ]
Not very inspired, but I'd suggest compose-path as the best I
can come up with.
> ; A better name for this function would be appreciated.
> dr: func [Block [block!]] [
> do reduce Block
> ]
>
Well, inspired by repend I might suggest do-re-me but then I'd
have the entire von Trapp family (and Julie Andrews) accusing me of
theft of "intellectual property"! ;-)
Seriously, we could take inspiration from LISP and call this
function apply as it allows one to apply a (dynamically
determined) function to a (dynamically determined) argument list.
Or perhaps eval would fit???
> use [diffs] [
> diffs: make tally []
<<quoted lines omitted: 10>>
> ]
> >> do %Tally.r
[...tracing output snipped...]
> diffs/now? 26
> evens/now? 226
> odds/now? 200
>
All told, I think the two functions above are quite nice! They
certainly handle the diffs case well. I'm a little less cheerful
about how they handle the evens/odds case however, in that they
require that I have a NAME for an object, in order to construct
a path to the needed object/method.
Gabriele's do-in allows me to get at a method of an object with
only the object itself (and the name of the method) in hand, not
requiring a name for the object. This becomes more of an issue
if we generalize my dinky example to other ways we might have
worked with a collection of tally instances. Given Gabriele's
do-in and my tally and data:
>> do-in: func [obj [object!] code [block!]] [do bind/copy code in
obj 'self]
>> tally: make object! [
[ tot: 0
[ zero: does [tot: 0]
[ up: func [/by n [number!]] [tot: tot + either by [n] [1]]
[ down: func [/by n [number!]] [tot: tot - either by [n] [1]]
[ now?: does [tot]
[ ]
>> bunchanums: [3 1 35 8 4 5 52 42 19 13 32 43 81 2 6 34 46]
== [3 1 35 8 4 5 52 42 19 13 32 43 81 2 6 34 46]
...we can say...
>> use [tallies] [
[ tallies: copy []
[ loop 3 [append tallies make tally []]
[ foreach num bunchanums [
[ do-in pick tallies (num // 3 + 1) [up/by num]
[ ]
[ foreach tally tallies [print tally/now?]
[ ]
132
212
82
...without needing to name the members of tallies.
At any rate, between you and Gabriele, we now have nicer solutions
to BOTH variations of my question!
Thanks!
> AllenK has a better grasp of the ideal Rebol way to do this. But
> he says it is difficult to explain. More about this later, or you
> could pester Allen.
> :-)
>
Hey, Allen! Are you there?
Please chime in before Andrew makes me pester you! ;-)
-jn-
[8/11] from: joel:neely:fedex at: 3-Oct-2000 8:31
[Al--Bri--xtra--co--nz] wrote:
[...code snipped...]
> Note how small the actual tally object is: "[Total: 0]". Also note that the
> binding is used like a dialect, but allows arbitrary Rebol script to be
> used.
>
Another interesting way to construct REBOL code! (Although I'd be
tempted to do this...
>> using: func [obj [object!] blk [block!]] [
[ do bind blk in obj 'self
[ ]
...then...
>> using Tally! [
[ use [diffs evens odds] [
[ diffs: make-Tally
[ evens: make-Tally
[ odds: make-Tally
[ foreach num bunchanums [
[ either even? num [
[ Up/by diffs num
[ Up/by evens num
[ ][
[ Down/by diffs num
[ Up/by Odds num
[ ]
[ ]
[ print Now? diffs
[ print Now? evens
[ print Now? odds
[ ]
[ ]
26
226
200
...if I were planning to use that technique at least once.) This
technique allows me to select an object within which to work...
>> using Tally! [
[ use [tallies] [
[ tallies: copy []
[ loop 3 [append tallies make-Tally]
[ foreach num bunchanums [
[ Up/by pick tallies (num // 3 + 1) num
[ ]
[ foreach tally tallies [print Now? tally]
[ ]
[ ]
132
212
82
...but still seems to require that pesky duplication of refinement
and argument in the diffs case...
either even? num [Up/by diffs num][Down/by diffs num]
which Challenge#2 called for eliminating.
You've all been very kind to supply very interesting ideas in
response to this little puzzle!
To sum up thus far, it appears that handling these three cases
1) same object, same method, computed argument(s)
2) same object, computed method, same argument(s)
3) computed object, same method, same argument(s)
involves the use of three (radically!) different techniques. I'm
very grateful for the responses thus far. I would be even happier
if we could come up with a single general approach that would
handle all three of these cases.
-jn-
[9/11] from: g:santilli:tiscalinet:it at: 4-Oct-2000 15:44
[joel--neely--fedex--com] wrote:
[do-in diffs either even? num [[up/by num]] [[down/by num]]]
> > (using COMPOSE here you may get closer to your hypothetical
> > syntax...)
>
> Being a Bear of Small Brain (and I was up late last night and
> haven't had sufficient coffee yet this morning), I must ask for
> an example of what you had in mind here.
>> num: 1
== 1
>> reduce [make path! compose [(pick [up down] even? num) by] 'num]
== [down/by num]
>> num: 2
== 2
>> reduce [make path! compose [(pick [up down] even? num) by] 'num]
== [up/by num]
Not as elegant as I'd like it to be...
Regards,
Gabriele.
--
Gabriele Santilli <[giesse--writeme--com]> - Amigan - REBOL programmer
Amiga Group Italia sez. L'Aquila -- http://www.amyresource.it/AGI/
[10/11] from: g:santilli:tiscalinet:it at: 4-Oct-2000 16:02
[joel--neely--fedex--com] wrote:
> To sum up thus far, it appears that handling these three cases
> 1) same object, same method, computed argument(s)
<<quoted lines omitted: 4>>
> if we could come up with a single general approach that would
> handle all three of these cases.
This is the best I can think of:
>> do-meth: func [obj [object!] meth [word! path! block!] args [block!]] [
[ do compose [(to-path compose [obj (to-block :meth)])
(args)]
[
]
>> do-meth obj 'f [1]
1 none none
>> do-meth obj 'f/ref [1 2]
1 true 2
>> do-meth obj compose [f (pick [ref []] random 2)] [1 2]
1 true 2
>> do-meth obj compose [f (pick [ref []] random 2)] [1 2]
1 none none
== 2
>> do-meth obj compose [f (pick [ref []] random 2)] [1 2]
1 none none
== 2
>> do-meth obj compose [f (pick [ref []] random 2)] [1 2]
1 none none
== 2
>> do-meth obj compose [f (pick [ref []] random 2)] [1 2]
1 true 2
Regards,
Gabriele.
--
Gabriele Santilli <[giesse--writeme--com]> - Amigan - REBOL programmer
Amiga Group Italia sez. L'Aquila -- http://www.amyresource.it/AGI/
[11/11] from: kgozlin:gis:pl at: 4-Oct-2000 17:33
> (*WARNING* *HYPOTHETICAL SYNTAX FOLLOWS*)
> use [diffs] [
<<quoted lines omitted: 4>>
> print ["net sum:" diffs/now?]
> ]
i like this hypothetical stuff, so you got it
almost as you wish :
REBOL []
imbc: func [ items [block!] /local imbc-blok][
items: copy/deep items
forall items [
if path? first items [
imbc-blok: next make block! first items
forall imbc-blok [
if tag? first imbc-blok [
change imbc-blok do bind make block! first imbc-blok
'print
]
]
change/only items make path! head imbc-blok
]
]
head items
]
bunchanums: [3 1 35 8 4 5 52 42 19 13 32 43 81 2 6 34 46]
tally: make object! [
tot: 0
zero: does [tot: 0]
up: func [/by n [number!]] [tot: tot + either by [n] [1]]
down: func [/by n [number!]] [tot: tot - either by [n] [1]]
now?: does [tot]
]
use [diffs ][
diffs: make tally []
foreach num bunchanums [
num2: num ; due to binding/context problem
do probe imbc [diffs/<either even? num2 ['up] ['down ]>/by
num2] ; <-- SEE?
]
print ["net sum:" diffs/now?]
]
halt
[diffs/down/by num2]
[diffs/down/by num2]
[diffs/down/by num2]
[diffs/up/by num2]
[diffs/up/by num2]
[diffs/down/by num2]
[diffs/up/by num2]
[diffs/up/by num2]
[diffs/down/by num2]
[diffs/down/by num2]
[diffs/up/by num2]
[diffs/down/by num2]
[diffs/down/by num2]
[diffs/up/by num2]
[diffs/up/by num2]
[diffs/up/by num2]
[diffs/up/by num2]
net sum: 26
>>
and somthing strange:
>> imbc [ a/<"REBOL">]
[a/"REBOL"]
== [a/"REBOL"]
>> [a/"REBOL"]
** Syntax Error: Invalid path -- a/.
** Where: (line 1) [a/"REBOL"]
>>
karol
Notes
- Quoted lines have been omitted from some messages.
View the message alone to see the lines that have been omitted