Mailing List Archive: 49091 messages
  • Home
  • Script library
  • AltME Archive
  • Mailing list
  • Articles Index
  • Site search
 

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