Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

: YC ( .. xt xt |0 -- .. ) BEGIN WHILE execute REPEAT ;

306 views
Skip to first unread message

humptydumpty

unread,
Jan 29, 2016, 2:19:28 AM1/29/16
to
: xt latestxt postpone literal ; immediate

\ Use: : wordname .... test-sequence IF xt dup ELSE 0 THEN ;
\ Kick into action: ' wordname dup YC

\ Example:
: downcount dup . 1- dup 0 >= IF xt dup ELSE 0 THEN ;
10 ' downcount dup YC drop

---
*Factored* loop. Small. Beautiful. Deserves to be a primitive.

Enjoying forth,
humptydumpty

humptydumpty

unread,
Jan 29, 2016, 12:49:41 PM1/29/16
to
Fun with YC :
---
: YC ( .. xt xt |0 -- .. ) BEGIN WHILE execute REPEAT ;

0 VALUE States
VARIABLE St
: st++ cell St +! ;
: 0st States St ! ;
: ?++,0 IF st++ ELSE 0st THEN St @ @ dup ;

: <q> KEY [char] q = ?++,0 ;
: <w> KEY [char] w = ?++,0 ;
: <e> KEY [char] e = ?++,0 ;
: <r> KEY [char] r = ?++,0 ;
: <t> KEY [char] t = ?++,0 ;
: <y> KEY [char] y = IF 0 exit ELSE 0st THEN State @ @ dup ;

HERE ' <q> , ' <w> , ' <e> , ' <r> , ' <t> , ' <y> ,
TO States 0st

' <q> dup YC ." GATE DOORS OPENS SLOWLY. YOU'RE IN." cr .s cr bye
---

$> gforth gate.fs
GATE DOORS OPENS SLOWLY. YOU'RE IN.
<0>

Enjoying forth,
humptydumpty

hughag...@gmail.com

unread,
Jan 29, 2016, 11:42:19 PM1/29/16
to
: YC ( .. xt xt |0 -- .. ) BEGIN WHILE execute REPEAT ;

Here is an alternative suggestion:

\ toucher: i*x -- j*x continue?

: YC ( xt -- ) >r begin r@ execute while repeat r> drop ;

This way when your xt executes it has access to the data-stack that the parent set up (the i*x stuff) without anything (including its own xt value) being on top of this data getting in the way.

Also, we get rid of the DUP in your example code --- I found that to be quite confusing.

For the most part though, you are on the right track --- this is similar to the kind of code I have in my novice package --- I rely heavily on passing an xt into a HOF that does some iteration and executes the xt on every pass. :-)

humptydumpty

unread,
Jan 30, 2016, 2:27:25 AM1/30/16
to
Hi!

Both methods gives transparency to data-stack, but responsibility to preserve
'xt' differs.

In my version, responsibility belongs to the word that is fed to the 'YC', in yours belongs to 'YC'. My way does not clutter data-stack and can be used like
this:

: guarded-counted-loop ( .. xt xt |0 HiLim StartIdx -- .. )
DO
0= IF unloop EXIT THEN
execute
LOOP
;

About confusing 'DUP', is part of the game: the sequence 'dup execute'
I see as indication of y-combinators in forth. Hence the name 'YC';
is not a y-combinator (that provides recursion), is a factored-loop.

Enjoying forth,
humptydumpty

humptydumpty

unread,
Jan 30, 2016, 2:30:56 AM1/30/16
to
OOPS!

Please, read

> My way does not clutter data-stack and can be used like
> this:

as

"My way does not clutter RETURN-stack and can be used like this:"

Enjoying forth,
humptydumpty

WJ

unread,
Jan 30, 2016, 4:45:56 AM1/30/16
to
humptydumpty wrote:

> : xt latestxt postpone literal ; immediate
>
> \ Use: : wordname .... test-sequence IF xt dup ELSE 0 THEN ;
> \ Kick into action: ' wordname dup YC
>
> \ Example:
> : downcount dup . 1- dup 0 >= IF xt dup ELSE 0 THEN ;
> 10 ' downcount dup YC drop

Ruby:

def yc(n,xt)
n = xt.call(n) while n
end

yc 10, lambda{|n| n>=0 and (print n, " "; n-1)}

===>
10 9 8 7 6 5 4 3 2 1 0

--
Government is not reason, it is not eloquence, it is force; like fire, a
troublesome servant and a fearful master. Never for a moment should it be left
to irresponsible action. --- George Washington, speech of January 7, 1790
Use this [sword] for me, if I rule well; if not, against me. --- Trajan

Julian Fondren

unread,
Jan 30, 2016, 5:11:24 AM1/30/16
to
On Friday, January 29, 2016 at 1:19:28 AM UTC-6, humptydumpty wrote:
> \ Example:
> : downcount dup . 1- dup 0 >= IF xt dup ELSE 0 THEN ;
> 10 ' downcount dup YC drop
>
> ---
> *Factored* loop. Small. Beautiful. Deserves to be a primitive.

Hmm. Well, take this code (please don't say, "no thank you!"):

variable matched
: email ( -- ) matched off
emails[ rules-apply? if purge matched ++ then ]emails
matched ? ." emails purged" cr ;

Where emails[ ... ]emails

: emails[ ( -- )
[ queuechars $@ bounds ] 2literal POSTPONE 2literal
POSTPONE do POSTPONE i POSTPONE c@ POSTPONE queue[ ; immediate
: ]emails ( -- )
POSTPONE ]queue POSTPONE loop POSTPONE /queue ; immediate

is built on queue[ ... ]queue

: queue[ ( c -- )
POSTPONE open-queue POSTPONE input-path POSTPONE file[
POSTPONE -H? POSTPONE if ; immediate
: ]queue ( -- )
POSTPONE then POSTPONE ]file ; immediate

is built on file[ ... ]file

: file[ ( path-fid -- )
POSTPONE dir[ POSTPONE file? POSTPONE if ; immediate
: ]file ( -- )
POSTPONE then POSTPONE ]dir ; immediate

is built on dir[ ... ]dir

: dir[ ( path-fid -- )
POSTPONE >r
POSTPONE begin POSTPONE r@ POSTPONE getdents POSTPONE -getdents-error
POSTPONE DUP POSTPONE 0> POSTPONE while POSTPONE :getdents POSTPONE ! POSTPONE dirents[ ; immediate
: ]dir ( -- )
POSTPONE ]dirents POSTPONE repeat POSTPONE r> POSTPONE drop ;
immediate

is built on dirents[ ... ]dirents

\ dirents has already been called and the buffer has content
: dirents[ ( -- )
POSTPONE 'getdents POSTPONE >o POSTPONE begin ; immediate
( -- ) \ the current object is linux-dirent
: ]dirents ( -- )
POSTPONE next POSTPONE empty? POSTPONE until POSTPONE o> ;
immediate


Without testing, I think the following is about what this would look
like in an xt-passing style:

: email ( -- ) matched off
[: rules-apply? if purge matched ++ then ;] do-emails
matched ? ." emails purged" cr ;

: do-emails { xt -- }
[ queuechars $@ bounds ] 2literal do
xt i c@ do-queue
loop ;

: do-queue ( xt c -- )
open-queue input-path
[: ( xt ) >r -H? if r@ execute then r> ;] swap do-files
( xt ) drop ;

: do-files ( xt path-fid -- )
[: ( xt ) >r file? if r@ execute then r> ;] swap do-dir
( xt ) drop ;

: do-dir ( xt path-fid -- )
>r begin r@ getdents -getdents-error dup 0> while
:getdents !
( xt ) dup >r do-dirents r>
repeat r> drop ;

: do-dirents ( xt -- )
'getdents >o >r begin
r@ execute
next empty? until r> drop o> ;


Is that ... better? For all that it isn't littered with POSTPONE,
it seems more complex. I would feel more uncomfortable about having
all of this in an speed-critical loop. (Not that this is one; the
unlinkat() syscalls in PURGE completely dominate the time spent by
the loop.)

It seems to have been easier to see how the POSTPONEs collapsed into
the user of emails[ ... ]emails, than to follow the original
quotation's path through all this. (ciforth's SEE doesn't produce
great results on the POSTPONE code, but probably it would be pretty
clear with gforth's SEE)

How would it be improved with XT and YC ?


-- Julian

hughag...@gmail.com

unread,
Jan 30, 2016, 2:32:55 PM1/30/16
to
On Saturday, January 30, 2016 at 12:27:25 AM UTC-7, humptydumpty wrote:
> On Saturday, January 30, 2016 at 6:42:19 AM UTC+2, hughag...@gmail.com wrote:
> > : YC ( .. xt xt |0 -- .. ) BEGIN WHILE execute REPEAT ;
> >
> > Here is an alternative suggestion:
> >
> > \ toucher: i*x -- j*x continue?
> >
> > : YC ( xt -- ) >r begin r@ execute while repeat r> drop ;
> >
> > This way when your xt executes it has access to the data-stack that the parent set up (the i*x stuff) without anything (including its own xt value) being on top of this data getting in the way.
> >
> > Also, we get rid of the DUP in your example code --- I found that to be quite confusing.

> Both methods gives transparency to data-stack, but responsibility to preserve
> 'xt' differs.
>
> In my version, responsibility belongs to the word that is fed to the 'YC', in yours belongs to 'YC'. My way does not clutter [the return-stack] and can be used like
> this:
>
> : guarded-counted-loop ( .. xt xt |0 HiLim StartIdx -- .. )
> DO
> 0= IF unloop EXIT THEN
> execute
> LOOP
> ;

Well, it doesn't matter if the code for YC is clunky and clutters the return-stack, because that code is written once by the implementer (you or me) and the application-programmer doesn't have to concern himself with how it works under the hood. I don't think there is any difference in speed either way.

> About confusing 'DUP', is part of the game: the sequence 'dup execute'
> I see as indication of y-combinators in forth. Hence the name 'YC';
> is not a y-combinator (that provides recursion), is a factored-loop.

I'm thinking of your YC as being a HOF (higher-order function) in that it takes an xt as a parameter and executes the xt inside of a loop. I have several HOFs in the novice package and they vary in regard to how much internal data they work with (EACH FIND-NODE and FIND-PRIOR all work with a linked list, but they do different things). All of my HOFs leave the data-stack clean when they execute the xt so the application-programmer who writes that function doesn't have to know how the HOF works internally --- hiding the internal data is part of the game for the HOF implementer, not for the application-programmer who is using the HOF.

I'm not familiar with the term "y-combinator" though, so maybe this is different from a HOF --- it looks like a HOF to me --- I should learn up on y-combinators before I criticize your implementation too much.

As I said before, I think you are on the right track. This kind of code is the key to writing general-purpose data-structures. The code that traverses the data-structure is hidden inside of the HOF rather than exposed in the application-program. It is possible to switch out one data-structure for another and only the HOF has to be changed, but the application programs that use the HOF can be recompiled without change and will work. The idea is to spare the application-programmer from having to learn the details of how his data-structure works, but he can just use it having learned only a simple API for it. Of course, this would work better if Forth had quotations so that when the xt is executed it has access to the parent function's local variables --- that is a much cleaner way to communicate with the parent function than fiddling around with the data-stack.

Information hiding --- always a good idea!

humptydumpty

unread,
Jan 30, 2016, 2:33:47 PM1/30/16
to
Hi!

From my humble understanding of yours cod I'll do sometrhing like:

: YCC ( xt HiLim StartIdx -- ) DO I swap execute LOOP drop ;

DEFER payload
: entry ( -- )
file? IF
-H? IF
payload
THEN
THEN
next empty? IF 0 ELSE XT dup THEN
;
: dirents ( -- )
'getdents >o ['] entry dup YC o>
;
: dir ( path-fid -- path-fid )
>R
R@ getdents -getdents-error dup 0>
IF :getdents ! dirents R> XT dup
ELSE R> 0 THEN
;
: (emails) ( u -- )
c@ open-queue input-path ['] dir dup YC drop
XT
;
: emails ( -- )
['] (emails) queuechars $@ bounds YCC /queue
;

VARIABLE Matched
: email ( xt -- )
IS payload
Matched off
emails
Matched ? ." emails purged" cr
;
:noname rules-apply? IF purge Matched ++ THEN; email

Enjoying forth,
humptydumpty

WJ

unread,
Jan 31, 2016, 5:14:25 AM1/31/16
to
Ruby:

Let's say that we want to "purge" files in the current directory
that are larger than 40,000 bytes.

To purge, we'll simply move the file to the subdirectory named
"purged".

def purge_files path
Dir[path].select{|x| File.file? x}.
each{|file_name|
if yield(file_name)
File.rename(file_name, "purged/#{file_name}")
end}
end

purge_files("*"){|f| File.size(f) > 40_000}


--
Amazon bans book. After nearly a month on the site, all traces of the book and
its 80 reviews have been removed.
http://jamesfetzer.blogspot.com/2015/11/debunking-sandy-hook-debunkers-5.html
https://www.youtube.com/watch?v=EEl_1HWFRfo

WJ

unread,
Jan 31, 2016, 5:37:57 AM1/31/16
to
hughag...@gmail.com wrote:

> I rely heavily on passing an xt into a HOF that does
> some iteration and executes the xt on every pass.

+1

Hiding the gory details of iteration unshackles the mind of
the programmer, allowing it to devote all of its power
to the real problem.

Chris Curl

unread,
Jan 31, 2016, 10:47:51 AM1/31/16
to
On Sunday, January 31, 2016 at 5:37:57 AM UTC-5, WJ wrote:
> Hiding the gory details of iteration unshackles the mind of
> the programmer, allowing it to devote all of its power
> to the real problem.
>
Your inability to see that Forth doesn't need to be able to do what Ruby
can do in as few lines of code shows that you have no imagination.
With Forth, you can literally do ANYTHING you want to do with the computer.

My purge files is this:

get.current.folder 40000 bytes purge.files

If you want to see the details behind mine, show me the details behind yours.

Julian Fondren

unread,
Jan 31, 2016, 11:04:16 AM1/31/16
to
On Sunday, January 31, 2016 at 4:14:25 AM UTC-6, WJ wrote:
> Ruby:
>
> Let's say that we want to "purge" files in the current directory
> that are larger than 40,000 bytes.
>
> To purge, we'll simply move the file to the subdirectory named
> "purged".
>
> def purge_files path
> Dir[path].select{|x| File.file? x}.
> each{|file_name|
> if yield(file_name)
> File.rename(file_name, "purged/#{file_name}")
> end}
> end
>
> purge_files("*"){|f| File.size(f) > 40_000}

$ ls
purged/ purge.rb
$ dd if=/dev/zero of=big bs=1000 count=41
41+0 records in
41+0 records out
41000 bytes (41 kB) copied, 0.000299873 s, 137 MB/s
$ ruby purge.rb
$ find
.
./purged
./purged/big
./purge.rb
$ ln -s purged/big big
$ ruby purge.rb
$ find
.
./purged
./purged/big
./purge.rb
$ file purged/big
purged/big: broken symbolic link to purged/big

Given a symlink, this tests the size of the file the symlink
points to -- and then moves the symlink, if that file is large
enough. If there's already a file at the destination of the
rename, then that file is clobbered.

Under Linux, the renameat2 system call accepts a flag that'll
prevent you from clobbering a file on a rename. If you can't
use that, your alternatives are A) to live with the race
conditions involved with separately testing for a file's
existence and renaming a file, or B) forking and asking
something else (like /usr/bin/mv) to do the renameat2 for you.

ruby is reasonable though with its syscalls when it finally
gets around to executing your program (before it gets to your
program, it wanders around inspecting gems, and it opens some
pipes and spawns a thread). It doesn't stat files twice, once
for the size test and once for the filetype test. If both are
removed (replaced f.e. with tests against the filename) it
doesn't stat the files at all.

(My perl competitor has loads of unnecessary stats and ioctls,
with no requirement for them in the code itself.)

But ruby builds a giant list of files just to throw it away,
and it does so before your program can do anything with the
files.

In an empty directory:

$ /usr/bin/time -f "real %e\nuser %U\nsys %S\nmem %M kB\n" ruby ../purge.rb
real 0.03
user 0.03
sys 0.00
mem 8996 kB

In a directory with 300k files:

$ /usr/bin/time -f "real %e\nuser %U\nsys %S\nmem %M kB\n" ruby purge.rb
real 0.37
user 0.27
sys 0.10
mem 32520 kB

That's not a big deal. It's just kinda lame.


Anyway, I don't actually want a stat, but for completeness will
probably add one anyway. It'd look something like this, with
unwritten words in all caps:

files[ LSTAT FILE.SIZE 40000 > ?rm ]files

( PURGE is an email-specific word that removes three separate
files from the exim spool directory, per email removed.)


-- Julian

Julian Fondren

unread,
Jan 31, 2016, 11:31:57 AM1/31/16
to
On Sunday, January 31, 2016 at 10:04:16 AM UTC-6, Julian Fondren wrote:
> files[ LSTAT FILE.SIZE 40000 > ?rm ]files

Incidentally, the entire program would really just be the
following definition in a file by itself:

: fourty-thousand-bytes-is-enough-for-anybody ( -- )
"." open-path
file[ LSTAT FILE.SIZE 40000 > ?rm ]file ;

And would be compiled to an executable with

./lina -c 40000-limiter.fs

The first thing my build system does is to build the Forth the
app needs. The probably doesn't sound like a big deal, but it
really does a number on boilerplate.


-- Julian

humptydumpty

unread,
Jan 31, 2016, 1:02:41 PM1/31/16
to
Hi!

For users sake, I think is good thing to have the same stack effect
for all kinds of loops. I don't tested for speed any of them.
Of course, this is Forth and everyone can rewrite them for his/her needs.

: XT latestxt postpone literal ; immediate
: ?YC postpone IF postpone XT postpone ELSE 0 postpone LITERAL postpone THEN ; immediate
\ --- 9 LOOPS
: YC ( .. xt|0 -- .. ) BEGIN ?dup WHILE execute REPEAT ;
: YCC ( .. xt|0 -- .. ) DO execute LOOP drop ;
: YCC+ ( .. xt|0 -- .. ) DO execute +LOOP drop ;
: YCCI ( .. xt|0 -- .. ) DO I swap execute LOOP drop ;
: YCCI+ ( .. xt|0 -- .. ) DO I swap execute +LOOP drop ;
: YCG ( .. xt|0 -- .. ) DO ?dup IF execute ELSE unloop exit THEN LOOP drop ;
: YCG+ ( .. xt|0 -- .. ) DO ?dup IF execute ELSE unloop exit THEN +LOOP drop ;
: YCGI ( .. xt|0 -- .. ) DO ?dup IF I swap execute ELSE unloop exit THEN LOOP drop ;
: YCGI+ ( .. xt|0 -- .. ) DO ?dup IF I swap execute ELSE unloop exit THEN +LOOP drop ;

Cons: slower than original loop, especially for small loops.
Pro: enforce factoring, easy testing: 'execute .S' is all that is
needed to test the word to be fed to a factored-loop.

Enjoying forth,
humptydumpty

hughag...@gmail.com

unread,
Jan 31, 2016, 11:37:20 PM1/31/16
to
I think that WJ is right that it is a good idea to hide iteration inside of a HOF so the application programmer can focus the application program rather than the details of explicit loops --- iteration is the most common source of bugs, so hiding the iteration should simplify programs considerably and make them easier to test.

I also think that WJ is a jackass. This business of posting Ruby code (now) or Gauche Scheme code (previously) is very annoying. If he likes those languages so much, he should just program in them and leave us alone. I have no interest in dynamic-OOP languages because tagged data makes the language slow (Ruby is abysmally slow even by scripting-language standards).

BTW: Why does WJ not post Gauche Scheme code anymore? Most likely Shiro Kawai or some other Gauche Scheme honcho told him that he was a jackass and that his constant promotion of Gauche Scheme on comp.lang.lisp was making the Gauche Scheme community look bad.

People should just program in whatever language they like. The point of programming is to write programs, hopefully that do something useful, or at least that are fun to write and/or to use --- telling everybody on the internet that they are programming in the wrong language is pointless --- being the "leading expert" on a programming language and authoring the Standard for that programming language, while never writing any programs, is also pointless.

hughag...@gmail.com

unread,
Feb 1, 2016, 12:19:47 AM2/1/16
to
On Saturday, January 30, 2016 at 12:27:25 AM UTC-7, humptydumpty wrote:
> On Saturday, January 30, 2016 at 6:42:19 AM UTC+2, hughag...@gmail.com wrote:
> > : YC ( .. xt xt |0 -- .. ) BEGIN WHILE execute REPEAT ;
> >
> > Here is an alternative suggestion:
> >
> > \ toucher: i*x -- j*x continue?
> >
> > : YC ( xt -- ) >r begin r@ execute while repeat r> drop ;
> >
> > This way when your xt executes it has access to the data-stack that the parent set up (the i*x stuff) without anything (including its own xt value) being on top of this data getting in the way.

> Both methods gives transparency to data-stack, but responsibility to preserve
> 'xt' differs.
>
> In my version, responsibility belongs to the word that is fed to the 'YC', in yours belongs to 'YC'.

There is some history here. I had EACH in the novice package that is defined like this:

: each ( i*x head 'toucher -- j*x ) \ toucher: i*x node -- j*x
>r
begin dup while \ -- node \r: -- 'toucher
r@ over .fore @ >r \ -- node 'toucher \r: -- 'toucher next
execute r> repeat drop
rdrop ;

When I released the novice package, everybody said that FFL was far better in every way. FFL had a HOF like EACH that did not hold its internal data on the return-stack, so when the xt was executed all that internal data was sitting on top of the user's data getting in the way. It was the user's responsibility to know how much internal data the HOF had and to push it to the return-stack during its execution and then pull it off again before exiting. So, the user had to know all the details of how the HOF worked internally --- the user couldn't just use the HOF and know what it did. I pointed out this problem with FFL and so the HOF in FFL got fixed. This was for singly-linked lists which my EACH is for --- I assume that the problem infects FFL throughout and that it hasn't been fixed everywhere (I haven't bothered to check because I have no interest in FFL).

FFL (Forth Foundation Library) certainly does have a grandiose name. The use of the word "Foundation" is very comparable to the word "Standard" --- the Forth-200x committee love to declare themselves to be the Standard, and the Foundation, etc..

Anyway, Humptydumpty --- you started this thread out by saying: "deserves to be a primitive." This implies that it has to be part of the standard, because primitives are written in assembly-language rather than Forth. I agree that YC should be a primitive, but it needs some improvement --- don't be like the Forth-200x committee who are unable to learn anything, but instead declare their screw-ups to be "Standard" and "Foundation" --- Forth is more enjoyable when you write robust code, and you did say that you want to enjoy Forth. :-)

humptydumpty

unread,
Feb 1, 2016, 3:28:45 AM2/1/16
to
Hi!

Improvements are already welcomed from everyone :-)
Enjoying robust Forth code is for everyone too!

For Forth coding fun, here is an attempt to write 'each' using YC:
\ ---
: XT latestxt postpone literal ; immediate
: YC ( .. xt|0 -- .. ) BEGIN ?dup WHILE execute REPEAT ;
: each ( xt node -- xt node xt-each|0 )
dup IF
2>R
2R@ swap execute
2R> .fore
xt \ loops again
ELSE 0 THEN \ terminate loop
;
\ Use: ( xt node ) ['] each YC ( xt 0 ) 2drop
\ Test: ( xt node xt-each ) execute .S ( xt node xt-each ;repeat that at heart wish or to list-end :-)
\ ---

Enjoying Forth,
humptydumpty

Julian Fondren

unread,
Feb 1, 2016, 4:32:39 AM2/1/16
to
On Sunday, January 31, 2016 at 12:02:41 PM UTC-6, humptydumpty wrote:
> Hi!
>
> For users sake, I think is good thing to have the same stack effect
> for all kinds of loops. I don't tested for speed any of them.
> Of course, this is Forth and everyone can rewrite them for his/her needs.
>
> : XT latestxt postpone literal ; immediate
> : ?YC postpone IF postpone XT postpone ELSE 0 postpone LITERAL postpone THEN ; immediate
> \ --- 9 LOOPS
> : YC ( .. xt|0 -- .. ) BEGIN ?dup WHILE execute REPEAT ;
> : YCC ( .. xt|0 -- .. ) DO execute LOOP drop ;
> : YCC+ ( .. xt|0 -- .. ) DO execute +LOOP drop ;
> : YCCI ( .. xt|0 -- .. ) DO I swap execute LOOP drop ;
> : YCCI+ ( .. xt|0 -- .. ) DO I swap execute +LOOP drop ;
> : YCG ( .. xt|0 -- .. ) DO ?dup IF execute ELSE unloop exit THEN LOOP drop ;
> : YCG+ ( .. xt|0 -- .. ) DO ?dup IF execute ELSE unloop exit THEN +LOOP drop ;
> : YCGI ( .. xt|0 -- .. ) DO ?dup IF I swap execute ELSE unloop exit THEN LOOP drop ;
> : YCGI+ ( .. xt|0 -- .. ) DO ?dup IF I swap execute ELSE unloop exit THEN +LOOP drop ;
>
> Cons: slower than original loop, especially for small loops.
> Pro: enforce factoring, easy testing: 'execute .S' is all that is
> needed to test the word to be fed to a factored-loop.
>
> Enjoying forth,
> humptydumpty

Thanks for this list of words. I didn't like your YC , and this
list helped me to say why.

I know, when I start to write a word, whether it'll have a loop
or not. Then it's just a question of whether I want to use DO
or not. If I'm basically iterating over an array - there is a
start and endpoint and they are connected by definite increments
from one to other - then I use DO. Otherwise, BEGIN.

I don't say, I'll use one of BEGIN ... WHILE ... REPEAT or else
BEGIN ... UNTIL or else BEGIN ... AGAIN. Because these are all
really just conveniences upon BEGIN ... AGAIN. If you need to
do some post-loop work then you can EXIT to a caller that does
it or you can branch past the loop to do it.

And likewise I don't *think* about which variants of DO ... LOOP
I want. It just works out that the code requires one of them.

So I approach the word with an understanding that it'll be
looping, I decide on DO or BEGIN , and then I no longer care
about looping structures, I just care about what the word
requires.

With your words, I would have to ask: do I want YC, or YCC, or
YCC+, or YCCI, or YCCI+, or YCG, or YCG+, or YCGI, or YCGI+ .
Even if I'd memorized all of these, I would still have to decide
between them. And I still have to decide between them before I
write the body of the loop, because it must pass flags or
increments to them.

Of factoring, instead of having a bunch of words that work
together to form loops, you have a fixed list of different kinds
of loops that don't work with each other. And the words are
still tightly bound to what would be the body of the loop: you
can't just switch YC and YCC and get a different looping
behavior, for example. And the those original words have a lot
of value that's lost: you can no longer EXIT or UNLOOP EXIT and
you can't just put -> I <- precisely wherever it's needed and if
you have what would be a BEGIN loop you can't put state on the
return stack before the loop begins, access it during the loop,
and grab it after the loop.

On teaching, if I were to teach Forth, even at the level of
answering an isolated question about looping, I would say what I
said above: when you start to write a looping word, ask these
questions. But if Forth only had YC words I would have to say
"well, memorize these words and and the fine differences between
them".

On the look of the language, DO I LOOP isn't so pretty, but it
at least uses short real words that you can say to someone
instead of spelling them out. The BEGIN words can actually look
pretty good. YCG+ is of course "why see gee plus" and you might
fail to distinguish between it and YCC+.


-- Julian

humptydumpty

unread,
Feb 1, 2016, 5:23:38 AM2/1/16
to
Hi!
That list is not imposed to anyone, but give an ideea of what
could be done.

> I know, when I start to write a word, whether it'll have a loop
> or not. Then it's just a question of whether I want to use DO
> or not. If I'm basically iterating over an array - there is a
> start and endpoint and they are connected by definite increments
> from one to other - then I use DO. Otherwise, BEGIN.
>
> I don't say, I'll use one of BEGIN ... WHILE ... REPEAT or else
> BEGIN ... UNTIL or else BEGIN ... AGAIN. Because these are all
> really just conveniences upon BEGIN ... AGAIN. If you need to
> do some post-loop work then you can EXIT to a caller that does
> it or you can branch past the loop to do it.
>

Then nothing stop to write:

: BA BEGIN execute AGAIN ;

and inside word whom XT is fed to BA to write 'XT' to loop again,
'XT EXIT' to an earlier loop again and 'RDROP EXIT' to exit BA
whenever wish to.

> And likewise I don't *think* about which variants of DO ... LOOP
> I want. It just works out that the code requires one of them.
>
> So I approach the word with an understanding that it'll be
> looping, I decide on DO or BEGIN , and then I no longer care
> about looping structures, I just care about what the word
> requires.
>
> With your words, I would have to ask: do I want YC, or YCC, or
> YCC+, or YCCI, or YCCI+, or YCG, or YCG+, or YCGI, or YCGI+ .
> Even if I'd memorized all of these, I would still have to decide
> between them. And I still have to decide between them before I
> write the body of the loop, because it must pass flags or
> increments to them.
>

The names for those loops I think myself that somehow are poor chosen ...
But everyone can rewrite/use them as he/she wish. Important is ideea behind names.

> Of factoring, instead of having a bunch of words that work
> together to form loops, you have a fixed list of different kinds
> of loops that don't work with each other. And the words are
> still tightly bound to what would be the body of the loop: you
> can't just switch YC and YCC and get a different looping
> behavior, for example. And the those original words have a lot
> of value that's lost: you can no longer EXIT or UNLOOP EXIT and
> you can't just put -> I <- precisely wherever it's needed and if

When you have to factor inside a DO .. LOOP and have to invoke I
you also must pass it as parameter to helper-factoring word.

> you have what would be a BEGIN loop you can't put state on the
> return stack before the loop begins, access it during the loop,
> and grab it after the loop.
>

That way of using return-stack can enforce to write non-factored code.
As a cure for that, you can use something like:

: >{ postpone dup postpone @ postpone 2>R ; immediate
: } postpone R> postpone R> postpone ! ; immediate
VARIABLE State
\ inside factored-word use 'State' as an usual variable
... State >{ ( words modifying State ) } ( return to initial State value )

> On teaching, if I were to teach Forth, even at the level of
> answering an isolated question about looping, I would say what I
> said above: when you start to write a looping word, ask these
> questions. But if Forth only had YC words I would have to say
> "well, memorize these words and and the fine differences between
> them".
>
> On the look of the language, DO I LOOP isn't so pretty, but it
> at least uses short real words that you can say to someone
> instead of spelling them out. The BEGIN words can actually look
> pretty good. YCG+ is of course "why see gee plus" and you might
> fail to distinguish between it and YCC+.
>

> -- Julian

Enjoying Forth,
humptydumpty

hughag...@gmail.com

unread,
Feb 1, 2016, 11:41:17 PM2/1/16
to
On Monday, February 1, 2016 at 3:23:38 AM UTC-7, humptydumpty wrote:
> On Monday, February 1, 2016 at 11:32:39 AM UTC+2, Julian Fondren wrote:
> > Thanks for this list of words. I didn't like your YC , and this
> > list helped me to say why.
> > ...
I agree with Julien.

The problem here is the idea behind the names --- your HOFs' names describe what the loop does --- your HOFs' names should instead describe what kind of data the HOF iterates over.

For example, my EACH tells the user that his xt is executed for each item in the list. The user doesn't have to know what kind of loop is used internally (whether it is a DO loop or a BEGIN loop) --- the whole point of having a HOF is to hide the internal workings of iteration from the user, which includes hiding whether it is a DO loop or a BEGIN loop --- your code isn't really hiding anything, but it is just a shorthand for what the user is already able to do with explicit iteration code.

FIND-NODE and FIND-PRIOR are also examples of HOFs whose names tell the user what is being done with the data --- not what is being done with the code --- the goal is that the user should be able to think in terms of data rather than code, and his program should be a description of the resultant data rather than a procedural description of the code that produces this result.

Think of your description of the data as being an axiomatic system. Saying that the items in the list are ordered from first to last is an axiom. The user could actually drop this axiom and say that the items are unordered like a set. In this case EACH and FIND-NODE still describe what is done with the data (although duplicates make for a sticky point). FIND-PRIOR no longer makes sense though. Dropping an axiom like this from our definition of a list is similar to how the parallel-line axiom can be dropped form Euclid's geometry and you still get a useful system.

The point here is that we are describing the data, not the code.

This post has been somewhat philosophical --- but you did say: "Important is idea behind names" --- naming things is a big part of philosophy, and many would say the entirety of philosophy, so you totally asked for a philosophical response! ;-)

humptydumpty

unread,
Feb 2, 2016, 2:36:47 AM2/2/16
to
Hi!

I wanted just to be pragmatic of reusing allready defined DO LOOP. Maybe I failed at that.

> For example, my EACH tells the user that his xt is executed for each item in the list. The user doesn't have to know what kind of loop is used internally (whether it is a DO loop or a BEGIN loop) --- the whole point of having a HOF is to hide the internal workings of iteration from the user, which includes hiding whether it is a DO loop or a BEGIN loop --- your code isn't really hiding anything, but it is just a shorthand for what the user is already able to do with explicit iteration code.
>

That is no problem, you can define (I reuse name 'each' here just for brevity of post):

: each ( .. xt node -- .. ) ['] each YC 2drop ; \ and hide implementation details

> FIND-NODE and FIND-PRIOR are also examples of HOFs whose names tell the user what is being done with the data --- not what is being done with the code --- the goal is that the user should be able to think in terms of data rather than code, and his program should be a description of the resultant data rather than a procedural description of the code that produces this result.
>
> Think of your description of the data as being an axiomatic system. Saying that the items in the list are ordered from first to last is an axiom. The user could actually drop this axiom and say that the items are unordered like a set. In this case EACH and FIND-NODE still describe what is done with the data (although duplicates make for a sticky point). FIND-PRIOR no longer makes sense though. Dropping an axiom like this from our definition of a list is similar to how the parallel-line axiom can be dropped form Euclid's geometry and you still get a useful system.
>
> The point here is that we are describing the data, not the code.
>
> This post has been somewhat philosophical --- but you did say: "Important is idea behind names" --- naming things is a big part of philosophy, and many would say the entirety of philosophy, so you totally asked for a philosophical response! ;-)

The idea presented is beyond HOF. To express it: "Words (functions) that return handles of themselves that can be used to call again that words (functions), gives a alternative to think/use loops."

In classic way I think of a loop as a *monolithic block* (ex.) BEGIN WHILE REPEAT that should be tested in its wholeness, in alternative way as *single-word* (hmm, like a server) and word that use it (like a client).

I don't say that alternative way should substitute the classic way, but may be a viable alternative when loop is too complex and hard to test.

And I enjoy I discovered that using Forth :-)
humptydumpty

Gerry Jackson

unread,
Feb 2, 2016, 5:00:10 AM2/2/16
to
Continuing the fun, an alternative (use gforth)

: YC ( .. xt xt |0 -- .. ) BEGIN WHILE execute REPEAT ;

0 value start

: ns ( xt ch -- xt xt ) key <> if drop start then dup ; \ ns = next-state

[: [: [: [: [: [: [: 0 ;] 'y' ns ;] 't' ns ;] 'r' ns ;] 'e' ns ;] 'w' ns
;] 'q' ns ;] to start

start dup YC cr .( GATE DOORS OPEN SLOWLY. YOU'RE IN.) cr .s cr


--
Gerry

humptydumpty

unread,
Feb 2, 2016, 5:32:26 AM2/2/16
to
Wow! Cool telescoping! :-)

Enjoying Forth,
humptydumpty

Anton Ertl

unread,
Feb 2, 2016, 12:43:36 PM2/2/16
to
Julian Fondren <julian....@gmail.com> writes:
>With your words, I would have to ask: do I want YC, or YCC, or
>YCC+, or YCCI, or YCCI+, or YCG, or YCG+, or YCGI, or YCGI+ .

Somewhere between standard Forth loops and the YC words, there are the
Postscript looping words:

General loop:

{ ... } loop

You can leave it with "exit", which you can call in an "if". Note
that loop does not push anything on the stack or consume anything from
the stack in each iteration (in particular, not the xt for the next
iteration).

Counted loop:

start inc end { ... } for

for pushes the current index on the stack before calling the procedure
(the { ... } part) in each iteration.

Counted loop without index:

count { ... } repeat

repeat does not push or pop anything on the stack in each iteration.
There is no way to get the index with repeat.

- anton
--
M. Anton Ertl http://www.complang.tuwien.ac.at/anton/home.html
comp.lang.forth FAQs: http://www.complang.tuwien.ac.at/forth/faq/toc.html
New standard: http://www.forth200x.org/forth200x.html
EuroForth 2015: http://www.rigwit.co.uk/EuroForth2015/

hughag...@gmail.com

unread,
Feb 2, 2016, 11:29:46 PM2/2/16
to
On Tuesday, February 2, 2016 at 12:36:47 AM UTC-7, humptydumpty wrote:
> The idea presented is beyond HOF. To express it: "Words (functions) that return handles of themselves that can be used to call again that words (functions), gives a alternative to think/use loops."

Actually, you do have a good idea here that I didn't think of.

This was my suggestion:

\ toucher: i*x -- j*x continue?

: YC ( xt -- ) >r begin r@ execute while repeat r> drop ;

Your idea is that the word (I call it a "toucher" in my documentation) should return an xt or a FALSE rather than just a TRUE or a FALSE. The xt is typically of itself. This is a good idea because it opens the door for to having the toucher return the xt of another function rather than of itself. For example, two of them could return each other's xt in order to alternate back and forth.

So, here is my new suggestion:

\ toucher: i*x -- j*x xt|false

: YC ( xt -- )
begin execute dup while repeat drop ;

You still have the advantage that the data-stack is free of clutter when the toucher executes, so it can access the data of the parent function. As I've said 100 times here and elsewhere, this is essential. You now have the additional advantage of one toucher staging another one, rather than itself --- this is somewhat similar to a state-machine or a paced-loop in a micro-controller.

The problem here is that we need to have LATEST available --- unfortunately, the badly crippled ANS-Forth standard doesn't support LATEST so there is no easy way for the toucher to obtain its own xt.

> In classic way I think of a loop as a *monolithic block* (ex.) BEGIN WHILE REPEAT that should be tested in its wholeness, in alternative way as *single-word* (hmm, like a server) and word that use it (like a client).
>
> I don't say that alternative way should substitute the classic way, but may be a viable alternative when loop is too complex and hard to test.

One advantage of a HOF is that the toucher can be tested in isolation. If you write a loop explicitly all the code inside hasn't been factored out, so there is no easy way to test it in isolation (I usually put a QUERY INTERPRET inside the loop to single-step through the loop; this was back in Forth-83 when we had QUERY and INTERPRET available).

BTW: I really need to think of a better term than "toucher" --- I suspect that everybody who reads that word imagines a pervert groping women in crowds.
Message has been deleted

humptydumpty

unread,
Feb 3, 2016, 3:26:32 AM2/3/16
to
On Wednesday, February 3, 2016 at 6:29:46 AM UTC+2, hughag...@gmail.com wrote:
> On Tuesday, February 2, 2016 at 12:36:47 AM UTC-7, humptydumpty wrote:
> > The idea presented is beyond HOF. To express it: "Words (functions) that return handles of themselves that can be used to call again that words (functions), gives a alternative to think/use loops."
>
> Actually, you do have a good idea here that I didn't think of.
>
> This was my suggestion:
>
> \ toucher: i*x -- j*x continue?
>
> : YC ( xt -- ) >r begin r@ execute while repeat r> drop ;
>
> Your idea is that the word (I call it a "toucher" in my documentation) should return an xt or a FALSE rather than just a TRUE or a FALSE. The xt is typically of itself. This is a good idea because it opens the door for to having the toucher return the xt of another function rather than of itself. For example, two of them could return each other's xt in order to alternate back and forth.
>
> So, here is my new suggestion:
>
> \ toucher: i*x -- j*x xt|false
>
> : YC ( xt -- )
> begin execute dup while repeat drop ;
>

As suits your needs. I think I make-up my mind about my proposition of YC:

: YC ( .. selfxt n -- .. ) BEGIN WHILE execute REPEAT drop ;

,where n could be selfxt, a link, an data address, a count ...etc OR 0 to stop the loop.


> You still have the advantage that the data-stack is free of clutter when the toucher executes, so it can access the data of the parent function. As I've said 100 times here and elsewhere, this is essential. You now have the additional advantage of one toucher staging another one, rather than itself --- this is somewhat similar to a state-machine or a paced-loop in a micro-controller.
>
> The problem here is that we need to have LATEST available --- unfortunately, the badly crippled ANS-Forth standard doesn't support LATEST so there is no easy way for the toucher to obtain its own xt.
>
> > In classic way I think of a loop as a *monolithic block* (ex.) BEGIN WHILE REPEAT that should be tested in its wholeness, in alternative way as *single-word* (hmm, like a server) and word that use it (like a client).
> >
> > I don't say that alternative way should substitute the classic way, but may be a viable alternative when loop is too complex and hard to test.
>
> One advantage of a HOF is that the toucher can be tested in isolation. If you write a loop explicitly all the code inside hasn't been factored out, so there is no easy way to test it in isolation (I usually put a QUERY INTERPRET inside the loop to single-step through the loop; this was back in Forth-83 when we had QUERY and INTERPRET available).
>
> BTW: I really need to think of a better term than "toucher" --- I suspect that everybody who reads that word imagines a pervert groping women in crowds.

Uh oh! :-) Maybe a "client", "using-word" ?

Enjoying Forth,
humptydumpty


franck....@gmail.com

unread,
Feb 3, 2016, 4:02:30 AM2/3/16
to
Le mercredi 3 février 2016 05:29:46 UTC+1, hughag...@gmail.com a écrit :

> The problem here is that we need to have LATEST available ---

YC is really a great feature, but a rather theorical one.
Until now, I didn't found any operational application (I wish I could
one day).

It allows (among other things) to demonstrate that a non recursive
functional language can implement recursivity just with elements of
the language.

So, "theorically", LASTEST is allowed to write a YC.
(practically, nobody cares).

And YC is a subject where closures are interesting to use.

For instance, in Oforth, a (recursive) version of YC could be :

: YC(f) // word -- bl
#[ f YC f perform ] ;

And it is now possible to transform a non-recursive word into
a recursive one. For instance (almost-fact is not recursive) :

: almost-fact(f, n) -- n1
n ifZero: [ 1 ] else: [ n n 1 - f perform * ] ;

#almost-fact YC => fact

5 fact .
120 ok

Here, this demonstrates nothing, because YC itself is a recursive word,
but it is easy to write a non-recursive version of YC.

Franck

franck....@gmail.com

unread,
Feb 3, 2016, 4:23:27 AM2/3/16
to
Le mercredi 3 février 2016 10:02:30 UTC+1, franck....@gmail.com a écrit :

> So, "theorically", LASTEST is allowed to write a YC.
> (practically, nobody cares).
>

So, "theorically", LASTEST is <not> allowed to write a YC.

Franck

Albert van der Horst

unread,
Feb 3, 2016, 7:17:51 AM2/3/16
to
humptydumpty <oua...@gmail.com> writes:

>On Wednesday, February 3, 2016 at 6:29:46 AM UTC+2, hughag...@gmail.com wro=
>te:
>> On Tuesday, February 2, 2016 at 12:36:47 AM UTC-7, humptydumpty wrote:
>> > The idea presented is beyond HOF. To express it: "Words (functions) th=
>at return handles of themselves that can be used to call again that words (=
>functions), gives a alternative to think/use loops."
>>=20
>> Actually, you do have a good idea here that I didn't think of.=20
>>=20
>> This was my suggestion:
>>=20
>> \ toucher: i*x -- j*x continue?=20
>>=20
>> : YC ( xt -- ) >r begin r@ execute while repeat r> drop ;=20
>>=20
>> Your idea is that the word (I call it a "toucher" in my documentation) sh=
>ould return an xt or a FALSE rather than just a TRUE or a FALSE. The xt is =
>typically of itself. This is a good idea because it opens the door for to h=
>aving the toucher return the xt of another function rather than of itself. =
>For example, two of them could return each other's xt in order to alternate=
> back and forth.
>>=20
>> So, here is my new suggestion:
>>=20
>> \ toucher: i*x -- j*x xt|false
>>=20
>> : YC ( xt -- ) =20
>> begin execute dup while repeat drop ;
>>

>As suits your needs. I think I make-up my mind about my proposition of YC:

>: YC ( .. selfxt n -- .. ) BEGIN WHILE execute REPEAT drop ;

> ,where n could be selfxt, a link, an data address, a count ...etc O=
>R 0 to stop the loop.

>=20
>> You still have the advantage that the data-stack is free of clutter when =
>the toucher executes, so it can access the data of the parent function. As =
>I've said 100 times here and elsewhere, this is essential. You now have the=
> additional advantage of one toucher staging another one, rather than itsel=
>f --- this is somewhat similar to a state-machine or a paced-loop in a micr=
>o-controller.
>>=20
>> The problem here is that we need to have LATEST available --- unfortunate=
>ly, the badly crippled ANS-Forth standard doesn't support LATEST so there i=
>s no easy way for the toucher to obtain its own xt.
>>=20
>> > In classic way I think of a loop as a *monolithic block* (ex.) BEGIN WH=
>ILE REPEAT that should be tested in its wholeness, in alternative way as *s=
>ingle-word* (hmm, like a server) and word that use it (like a client).
>> >=20
>> > I don't say that alternative way should substitute the classic way, but=
> may be a viable alternative when loop is too complex and hard to test.
>>=20
>> One advantage of a HOF is that the toucher can be tested in isolation. If=
> you write a loop explicitly all the code inside hasn't been factored out, =
>so there is no easy way to test it in isolation (I usually put a QUERY INTE=
>RPRET inside the loop to single-step through the loop; this was back in For=
>th-83 when we had QUERY and INTERPRET available).
>>=20
>> BTW: I really need to think of a better term than "toucher" --- I suspect=
> that everybody who reads that word imagines a pervert groping women in cro=
>wds.

The idea that the to be repeated action should return its own xt
could be extended with the idea that execution addresses belong
on the return stack. So

{ .... } loop
{ } puts an xt of sorts on the return stack.
Then loop executes that xt, until it is zero.
The IF EXIT THEN would be replaced by break? that replaces (
conditionally) the top of the return stack with a zero, a
very light action.

:NONAME ... ;
could be replaced by
{ ... } R>

>Uh oh! :-) Maybe a "client", "using-word" ?

>Enjoying Forth,
>humptydumpty
--
Albert van der Horst, UTRECHT,THE NETHERLANDS
Economic growth -- being exponential -- ultimately falters.
albert@spe&ar&c.xs4all.nl &=n http://home.hccnet.nl/a.w.m.van.der.horst

humptydumpty

unread,
Feb 3, 2016, 9:17:15 AM2/3/16
to
On Wednesday, February 3, 2016 at 2:17:51 PM UTC+2, Albert van der Horst wrote:
Looks that is implementation dependent. AFAIK gforth-itc and lina
has in return-stack the xt of next word in definition:

: continuation R@ ; ok
' dup :noname continuation dup drop @ ; execute = . -1 ok

But appears the problem of testing in isolation of step { ... },
as '{ ... }' affects return-stack.

The only loops I founded using return-stack manipulations:

1. The simplest way to provides loop using return-stack is
'continuation >R step-sequence-of-code R@ ;', exit with 'rdrop exit',
continue with 'exit'.

2. Another, a little more complex, generator-filter:

: ENTER >R ;
: | IF EXIT THEN RDROP ;
: START BEGIN R@ ENTER AGAIN ;
: STOP IF RDROP RDROP RDROP THEN ;

I need to think more about that..

> :NONAME ... ;
> could be replaced by
> { ... } R>
>
> >Uh oh! :-) Maybe a "client", "using-word" ?
>
> >Enjoying Forth,
> >humptydumpty
> --
> Albert van der Horst, UTRECHT,THE NETHERLANDS
> Economic growth -- being exponential -- ultimately falters.
> albert@spe&ar&c.xs4all.nl &=n http://home.hccnet.nl/a.w.m.van.der.horst

Enjoying Forth,
humptydumpty

humptydumpty

unread,
Feb 3, 2016, 9:19:54 AM2/3/16
to
should be continue with 'R@ exit', sorry

humptydumpty
0 new messages