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