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

Rolling your own ideal looper: does Repeat still have a bug?

 [1/16] from: galtbarber:mailandnews at: 25-Aug-2000 19:31


This is the preamble of a multipart MIME formatted message. If you are reading this text your mail system is most likely not capable of properly decoding MIME messages. To extract the contents of this message, save it to a file and then use an external MIME decoding utility. --mime-boundary-interchange-39b0bccd Content-Type: text/plain; charset="ISO-8859-1" Content-Transfer-Encoding: 7bit Hi. See attached rebol file and output. Seems promising, although it turned up a bug in 'repeat apparently. I thought that bug had been squashed a while ago! Probably this would be interesting for Elan, Sterling, Brett, Ladislav, etc. I included the script in this mail, and as an attachment, too. At first I tried using bind, but I couldn't figure out how to get what I wanted with that. So I was pretty lucky I thought to make it work with word replacement, even dealing with "scoping" properly, so far as my testing indicates to date. -Galt Rebol [ Title: "test looper" File: %test-looper.r Author: "Galt Barber" Purpose: { The person who wanted to have the benefits of foreach, where you can refer to the element by name alone, and yet also be able to get the current index, which is not available with foreach, motivated me to try to write his "ideal" looper, which takes an index word accessible in the block. I am just trying to roll my own enhanced version of foreach. It seemed to work pretty well, but I did notice that 'repeat seems to have a bug, reminiscent of something I thought I remember was a problem reported long ago in an earlier version! } ] ;;; hack to quickly re-load edited source r: func [][do %test-looper.r] rec-replace-words: func [ b [block!] target [word!] replacement [word!] ][ forall b [ if word? first b [ if (first b) = target [ change b replacement ] ] if block? first b [ rec-replace-words first b target replacement ] ] head b ] ideal-looper: func [ 'element [word!] 'index [word!] s [series!] code [block!] /local eee iii ccc ][ ccc: copy/deep code rec-replace-words ccc element 'eee rec-replace-words ccc index 'iii repeat i length? s [ iii: i eee: s/:i do ccc ] ] { Here is what the output looks like, which is wrong!
>> r >> do z
1 h h hello 6 none none hello 6 none none hello 6 none none hello 6 none none hello == false I thought I may have made a mistake, but then I thought to try removing repeat from the definition, and using while instead, and that did work! see below! } x: "hello" z: [ ;;; test function, usage is do z ideal-looper char pos x [ embedit: func [][ prin [pos " " char] ] embedit sillyobj: make object! [ q: char showit: func [][prin [" " q]] ] sillyobj/showit prin " " ideal-looper char pos x [ prin [char] ] print "" ] ] { ;;; this one works, using while instead of repeat! ;;; it is commented out at the moment. ideal-looper: func [ 'element [word!] 'index [word!] s [series!] code [block!] /local eee iii ccc i ][ ccc: copy/deep code rec-replace-words ccc element 'eee rec-replace-words ccc index 'iii i: 0 while [i < length? s] [ i: i + 1 iii: i eee: s/:i do ccc ] ] } { ;;; here is the correct output!
>> r >> do z
1 h h hello 2 e e hello 3 l l hello 4 l l hello 5 o o hello == false
>>
} halt --mime-boundary-interchange-39b0bccd Content-Type: application/octet-stream; name="test-looper.r" Content-disposition: attachment; filename="test-looper.r" Content-Transfer-Encoding: 7bit Rebol [ Title: "test looper" File: %test-looper.r Author: "Galt Barber" Purpose: { The person who wanted to have the benefits of foreach, where you can refer to the element by name alone, and yet also be able to get the current index, which is not available with foreach, motivated me to try to write his "ideal" looper, which takes an index word accessible in the block. I am just trying to roll my own enhanced version of foreach. It seemed to work pretty well, but I did notice that 'repeat seems to have a bug, reminiscent of something I thought I remember was a problem reported long ago in an earlier version! } ] r: func [][do %test-looper.r] rec-replace-words: func [ b [block!] target [word!] replacement [word!] ][ forall b [ if word? first b [ if (first b) = target [ change b replacement ] ] if block? first b [ rec-replace-words first b target replacement ] ] head b ] ideal-looper: func [ 'element [word!] 'index [word!] s [series!] code [block!] /local eee iii ccc ][ ccc: copy/deep code rec-replace-words ccc element 'eee rec-replace-words ccc index 'iii repeat i length? s [ iii: i eee: s/:i do ccc ] ] { Here is what the output looks like, which is wrong!
>> r >> do z
1 h h hello 6 none none hello 6 none none hello 6 none none hello 6 none none hello == false I thought I may have made a mistake, but then I thought to try removing repeat from the definition, and using while instead, and that did work! see below! } x: "hello" z: [ ;;; test function, usage is do z ideal-looper char pos x [ embedit: func [][ prin [pos " " char] ] embedit sillyobj: make object! [ q: char showit: func [][prin [" " q]] ] sillyobj/showit prin " " ideal-looper char pos x [ prin [char] ] print "" ] ] { ;;; this one works, using while instead of repeat! ideal-looper: func [ 'element [word!] 'index [word!] s [series!] code [block!] /local eee iii ccc i ][ ccc: copy/deep code rec-replace-words ccc element 'eee rec-replace-words ccc index 'iii i: 0 while [i < length? s] [ i: i + 1 iii: i eee: s/:i do ccc ] ] } { ;;; here is the correct output!
>> r >> do z
1 h h hello 2 e e hello 3 l l hello 4 l l hello 5 o o hello == false
>>
} halt --mime-boundary-interchange-39b0bccd--

 [2/16] from: g:santilli:tiscalinet:it at: 26-Aug-2000 14:41


Hello [galtbarber--MailAndNews--com]! On 26-Ago-00, you wrote: g> See attached rebol file and output. How about:
>> ideal-looper: func [
[ 'element [word!] [ 'index [word!] [ series [series!] [ code [block!] [ /local f [ ] [ [ f: func reduce [element index] code [ repeat i length? series [f series/:i i] [ ]
>> ideal-looper char pos "hello" [print [pos char]]
1 h 2 e 3 l 4 l 5 o Much simpler, isn't it? :-) You get a new context and the correct bindings for free creating a function. However, there really seems to be some problems with recursion and REPEAT:
>> ideal-looper char pos s: "hello" [
[ prin [pos char] [ ideal-looper char pos s [prin " " prin char] [ prin newline [ ] 1 h h e l l o 6 none h e l l o 6 none h e l l o 6 none h e l l o 6 none h e l l o But:
>> repeat i 10 [prin i repeat i 10 [prin " " prin i] prin newline]
1 1 2 3 4 5 6 7 8 9 10 2 1 2 3 4 5 6 7 8 9 10 3 1 2 3 4 5 6 7 8 9 10 4 1 2 3 4 5 6 7 8 9 10 5 1 2 3 4 5 6 7 8 9 10 6 1 2 3 4 5 6 7 8 9 10 7 1 2 3 4 5 6 7 8 9 10 8 1 2 3 4 5 6 7 8 9 10 9 1 2 3 4 5 6 7 8 9 10 10 1 2 3 4 5 6 7 8 9 10 which makes me wonder... Anyway, using FOR:
>> ideal-looper: func [
[ 'element [word!] [ 'index [word!] [ series [series!] [ code [block!] [ /local f [ ] [ [ f: func reduce [element index] code [ for i 1 length? series 1 [f series/:i i] [ ]
>> ideal-looper char pos s: "hello" [
[ prin [pos char] [ ideal-looper char pos s [prin " " prin char] [ prin newline [ ] 1 h h e l l o 2 e h e l l o 3 l h e l l o 4 l h e l l o 5 o h e l l o I'd suggest reporting that to feedback. HTH, Gabriele. -- Gabriele Santilli <[giesse--writeme--com]> - Amigan - REBOL programmer Amiga Group Italia sez. L'Aquila -- http://www.amyresource.it/AGI/

 [3/16] from: lmecir:geocities at: 27-Aug-2000 10:40


Hi, just a small change: ideal-looper: func [ [throw] 'element [word!] 'index [word!] series [series!] code [block!] /local f i ] [ f: func reduce [element [any-type!] index] code i: 1 while [i <= length? series] [f series/:i i i: i + 1] ] Regards Ladislav

 [4/16] from: galtbarber:mailandnews at: 28-Aug-2000 12:47


Hi, Gabriele, Your version is much nicer and cleaner and probably runs just as fast or faster than mine, too. That's what I was looking for, but you see things clearer!! Anyway, thanks for your insights, I will take that lesson to heart. By the way, do you know how Rebol itself does foreach internally? Do they use a function like you did, or is it done some other way? Your example shows how arriving at the simple solution may be a bit harder but well worth it! I will send a note to feedback about the problem with 'repeat -Galt Here is Gabriele's lovely version: --------------------------- ideal-looper: func [ 'element [word!] 'index [word!] series [series!] code [block!] /local f ] [ f: func reduce [element index] code for i 1 length? series 1 [f series/:i i] ]
>>ideal-looper char pos s: "hello" [
prin [pos char] ideal-looper char pos s [prin " " prin char] prin newline ]

 [5/16] from: g:santilli:tiscalinet:it at: 28-Aug-2000 19:41


Hello [lmecir--geocities--com]! On 27-Ago-00, you wrote: l> Hi, l> just a small change: [...] Perhaps you meant: ideal-looper: func [ 'element [word!] 'index [word!] series [series!] code [block!] /local f i ] [ f: func reduce [[throw] element [any-type!] index] code i: 1 while [i <= length? series] [f series/:i i i: i + 1] ] F is the function that should have the THROW attribute. Regards, Gabriele. -- Gabriele Santilli <[giesse--writeme--com]> - Amigan - REBOL programmer Amiga Group Italia sez. L'Aquila -- http://www.amyresource.it/AGI/

 [6/16] from: lmecir:geocities at: 28-Aug-2000 22:10


Hi, being asked I try to explain my changes. 1. Thanks, Gabriele, I forgot to use one more Throw, but the original is needed too. I used Throw attribute to make sure, that any Return or Exit contained in code like: f: does [ideal-looper elem indx [1 2 3] [exit]] does what it should, ie. causes the exit of F. 2. Any-type! specification is needed, because a series in Rebol can contain even eg. Unset! values, in which case the function without the spec. wouldn't work. 3. For uses While anyway, so it is a shortcut. (see Source For, but look out! It still contains the series bug I reported to feedback.) The corrected version: ideal-looper: func [ [throw] 'element [word!] 'index [word!] series [series!] code [block!] /local f i ] [ f: func reduce [[throw] element [any-type!] index] code i: 1 while [i <= length? series] [f series/:i i i: i + 1] ] Regards Ladislav

 [7/16] from: galtbarber:mailandnews at: 28-Aug-2000 16:27


The original question, which may have been posted by Paul Tretter, but I don't remember exactly, was something like this: a: "101" foreach item a [ print index? item ] Now, we all know why that doesn't work. This does, but probably doesn't really address his concern: forall a [ print index? a ] and now you have to remember to reset a a: head a and you have to use "first a" to get the element everywhere in your code. Sometimes I myself have wished I could have my cake and eat it too. I want the index that forall gives and I want the ease of use of the assigned word for each element. I am pretty dang sure lots of other Rebolers out there beside me have come across the same thing. So I wrote something that did that, but I couldn't figure out how to do that simply. Gabriele came up with a wonderful little gem and Ladislav and he made some more little improvements. So, here it is, a kind of foreach with index access: ideal-looper: func [ 'element [word!] 'index [word!] series [series!] code [block!] /local f i ] [ f: func reduce [[throw] element [any-type!] index] code i: 1 while [i <= length? series] [f series/:i i i: i + 1] ]
>Regards, > Gabriele.
And that's why we love Rebol! And the punchline for Paul would be this usage: a: "101" ideal-looper item indx a [ print [item indx] ] the output is: 1 1 0 2 1 3 == false And of course, there is no harm in having calls to "ideal-looper" multiply nested or used inside recursively called functions. And the words used for the element and index are also just known in the "scope" of the code block, and will not stomp on your global wordspace. I still don't really understand Ladislav's addition of Throw and [any-type!]. Would anyone care to divulge? -Galt

 [8/16] from: g:santilli:tiscalinet:it at: 29-Aug-2000 19:17


Hello [galtbarber--MailAndNews--com]! On 28-Ago-00, you wrote: g> By the way, do you know how Rebol itself g> does foreach internally? Do they use a function g> like you did, or is it done some other way? Probably they're doing something like: foreach: func [ "Evaluates a block for each value(s) in a series." 'word [get-word! word! block!] {Word or block of words to set each time (will be local)} data [series!] "The series to traverse" body [block!] "Block to evaluate each time" /local amount ] [ if get-word? word [word: get word] use compose [(word) this-context] [ ; of course the native code ; doesn't need the extra word bind body 'this-context amount: either block? word [length? word] [1] while [not tail? data] [ set word data do body data: skip data amount ] ] ] Obviously the native code might look a lot different. ;-) Also, since they're probably not calling the WHILE native, there will be some magic code to make BREAK etc. work as expected. Regards, Gabriele. -- Gabriele Santilli <[giesse--writeme--com]> - Amigan - REBOL programmer Amiga Group Italia sez. L'Aquila -- http://www.amyresource.it/AGI/

 [9/16] from: g:santilli:tiscalinet:it at: 29-Aug-2000 19:31


Hello [lmecir--geocities--com]! On 28-Ago-00, you wrote: l> 1. Thanks, Gabriele, I forgot to use one more Throw, but the l> original is needed too. Yup, that was my mistake, sorry. l> 3. For uses While anyway, so it is a shortcut. Indeed. I used FOR just because it was more readable and more similar to the REPEAT version. Regards, Gabriele. -- Gabriele Santilli <[giesse--writeme--com]> - Amigan - REBOL programmer Amiga Group Italia sez. L'Aquila -- http://www.amyresource.it/AGI/

 [10/16] from: galtbarber:mailandnews at: 30-Aug-2000 13:44


Thanks, Gabriele, I have enjoyed studying this foreach code you created! Excellent! I appreciate your help, it's very valuable. I wanted to say, sorry for not getting back to you immediately, there's a lot going on right now!!! Ciao! -Galt p.s. The way you use bind on this-context is something new to me, it seems, I am enjoying this kind of enlightenment.

 [11/16] from: galtbarber:mailandnews at: 30-Aug-2000 18:50


Hello, Ladislav,
>It is telling For, that instead >of taking Exit as a signal for itself to exit, it must throw it >further (to F).
I think I either don't understand the problem properly, or else RT heard you and corrected the problem already. When I tried to do f: does [print "Hello" for i 1 10 1 [exit] print "You won't see this"] and run it, it exits f properly. I am assuming that in an old version it would exit the for loop and keep running, so that the user would in fact see:
>>f
You won't see this Is that the case? If so, RT has fixed it in my View and Core, which are 09931 and 23031 respectively. I may be all wet here, but if they have fixed it, it is probably in part due to your hard-won research and reporting of the problem. -Galt

 [12/16] from: galtbarber:mailandnews at: 30-Aug-2000 19:37


Ladislav, That is an impressive For function! your example of using
>a: [1 2] >for s a tail a 1 [print s]
as a test is very amusing bug, looks like an infinite loop on windows, just keeps printing newlines without ever ending. Just for kicks, I just tried this:
>> for s a back tail a 1 [print s]
1 2 2 so that it stops at the last real position, and that seemed to work, althout the expression is more awkward and wordy! Your other example is this:
>> for s a next a -1 [print s]
1 2 which I am not sure I get. This would never get to next a by skipping -1. Either you have a typo here, or I missed the point. If this were integers, it would look like for x = 1 30 step -1
>> for s a next a 1 [print s]
1 2 2 What is the proper behavior of
>> for s a next a -1 [print s]
??? -Galt p.s. As far as the second-class vs. first-class values goes, I am inclined to agree with you. There are some fishy issues that don't seem entirely cooked. You are right to bring them up for discussion. So far, however, in the programs that have come up naturally in my use of Rebol, I haven't been banging into these issues alot. I have seen the errorhandling stuff e.g. try, disarm seems like it's more awkward than it should be. I could run smack into more of them at any moment, though! p.p.s. I think the desire to share Rebol data and objects across the net between two instances of rebol.exe, or storing them and retrieving them later in native Rebol format, will come up strongly in the near future. Even the RebMail program might be a lot simpler if you could just save your mail objects and reload later. Having to create extra functions for converting from Rebol to some other disk structure and then back again is a pain. I think Rebol can do better. It just needs to make a few new tricks available...

 [13/16] from: lmecir:geocities at: 31-Aug-2000 8:35


Hi,
> Ladislav, > That is an impressive For function!
<<quoted lines omitted: 17>>
> This would never get to next a by skipping -1. > Either you have a typo here, or I missed the point.
Simple, For should stop immediately as for integers, because it is already behind the limit.
> If this were integers, it would look like > for x = 1 30 step -1
<<quoted lines omitted: 18>>
> objects across the net between two instances of rebol.exe, > or storing them and retrieving them later in native Rebol
format,
> will come up strongly in the near future. > Even the RebMail program might be a lot simpler if
<<quoted lines omitted: 3>>
> again is a pain. I think Rebol can do better. > It just needs to make a few new tricks available...
You are right with data saving - even data copying in Rebol may be an unsolvable problem.

 [14/16] from: blazs:orac:au at: 1-Sep-2000 1:22


Hello [lmecir--geocities--com] I only wish I had more time to play around with REBOL :(. On 29-Aug-00, [lmecir--geocities--com] wrote:
> The corrected version: > ideal-looper: func [
<<quoted lines omitted: 9>>
> while [i <= length? series] [f series/:i i i: i + 1] > ]
For what it's worth...would creating another local word to hold the length of the series and then checking it against 'i, as to evaluating the length of the series for each iteration of the while loop? Or is it wise to check the length of the series for each and every iteration, because the series itself could change while iterating across the series? i.e. ideal-looper: func [ [throw] 'element [word!] 'index [word!] series [series!] code [block!] /local f i series-length ] [ f: func reduce [[throw] element [any-type!] index] code i: 1 series-length: length? series while [i <= series-length] [ f series/:i i i: i + 1 ] ] Regards Blaz

 [15/16] from: rebol:techscribe at: 31-Aug-2000 17:32


Hi Galt, it appears I didn't got all the emails in this thread. So the problem may already be solved. you wrote:
>Ladislav, >That is an impressive For function!
<<quoted lines omitted: 4>>
>like an infinite loop on windows, just >keeps printing newlines without ever ending.
I guess you would rather see
>> for s a tail a 1 [print s]
1 2 2 I think that that's reasonable. The patched for version below does that. If you source the for function you'll see why for behaves as it does. The patched version corrects this and the other observed oddities. The results of running the patched version:
>> for s a next a -1 [print s]
== none
>> for s a next a 1 [print s]
1 2 2
>> for s a tail a 1 [print s]
1 2 2
>> for s a back tail a 1 [print s]
1 2 2 I added the following code to for: ;- the following line forces for to interpret ;- "tail of a series" as intended to mean "break at end of a series". if all [positive? bump tail? end ] [ end: back end ] ;- the following either any .. all .. combo catches errors resulting ;- from an end index that is higher than a start index with a ;- negative bump value, and also catches infinite loops that result ;- from passing an empty series either any [ empty? head end all [ if (negative? bump) [ op: :lesser?] op index? start index? end ] ] [ none ][ Note that I moved the expression if (negative? bump) [ op: :lesser?] into the all block of either's any block. The complete pathced for function follows: for: func [ "Repeats a block over a range of values." [catch throw] 'word [word!] "Variable to hold current value" start [number! series! money! time! date! char!] "Starting value" end [number! series! money! time! date! char!] "Ending value" bump [number! money! time! char!] "Amount to skip each time" body [block!] "Block to evaluate" /local result do-body op ][ if (type? start) <> (type? end) [ throw make error! reduce ['script 'expect-arg 'for 'end type? start] ] do-body: func reduce [[throw] word] body op: :greater-or-equal? either series? start [ if not same? head start head end [ throw make error! reduce ['script 'invalid-arg end] ] ;- the following line forces for to interpret ;- "tail of a series" as intended to mean "break at end of a series". if all [positive? bump tail? end ] [ end: back end ] ;- the following either any .. all .. combo catches errors resulting ;- from an end index that is higher than a start index with a ;- negative bump value, and also catches infinite loops that result ;- from passing an empty series either any [ empty? head end all [ if (negative? bump) [ op: :lesser?] op index? start index? end ] ] [ none ][ ;- we are dealing with a valid series while [op index? end index? start] [ set/any 'result do-body start start: skip start bump ] if (negative? bump) [ set/any 'result do-body start ] ] ] [ if (negative? bump) [op: :lesser-or-equal?] while [op end start] [ set/any 'result do-body start start: start + bump ] ] get/any 'result ]
>Just for kicks, I just tried this: >>> for s a back tail a 1 [print s]
<<quoted lines omitted: 89>>
>>] >>
;- Elan [ : - ) ] author of REBOL: THE OFFICIAL GUIDE REBOL Press: The Official Source for REBOL Books http://www.REBOLpress.com visit me at http://www.TechScribe.com

 [16/16] from: lmecir:geocities at: 1-Sep-2000 8:51


Hi Elan, the problem has been solved by the function For2 (you can see it at the end of the mail). Your solution (I call it For1) differs here:
>> for1 i a: [1 2] tail a 1 [print mold i]
[1 2] [2]
>> for2 i a: [1 2] tail a 1 [print mold i]
[1 2] [2] [] Regards Ladislav

Notes
  • Quoted lines have been omitted from some messages.
    View the message alone to see the lines that have been omitted