[REBOL] TECHNICAL ESSAY/CHALLENGE(s) - "Expression Based"?
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-