Anonymous definitions - some questions

Topics relating to various Forth models on the 6502, 65816, and related microprocessors and microcontrollers.
Post Reply
User avatar
commodorejohn
Posts: 299
Joined: 21 Jan 2016
Location: Placerville, CA
Contact:

Anonymous definitions - some questions

Post by commodorejohn »

So I'm learning my way around Forth, and me being me, I naturally gravitate to the more eccentric features. I'm currently trying to scheme out a way to do a simple object-oriented model in which selector bindings can be changed at runtime.

(To answer the obvious "why!?," it's for a text-adventure project. I want game objects to be able to assign different handlers/responses to the same commands without having to create elaborate if-then/case structures.)

And I'd like to do this A. without cluttering up the dictionary and B. as transparently as possible within the code itself. I like the look of anonymous definitions using :noname for this - I can have any number of execution tokens to map to selectors without having to have dictionary definitions for all of them.

But I'd like to take it a bit further, if possible: it'd be nice if one handler routine could simply :noname up a definition to assign to a selector within the body of the handler itself, but I'm not up enough on low-level Forth monkeying to be certain that that's how this works. If, say, I have:

Code: Select all

: handler-A
    some-code
	:noname
	    code-for-another-handler ;
	selector-B !
	;
If that even parses correctly, is it going to roll up a new copy of the anonymous function every time it's executed? I'm thinking of this in a Lisp-y or Smalltalk-y context where symbols/lists are unique and immutable so there's only ever one copy of a given function, but I know Forth doesn't work that way by nature.
whartung
Posts: 1004
Joined: 13 Dec 2003

Re: Anonymous definitions - some questions

Post by whartung »

Well, you may not be polluting the dictionary, but you're polluting something. Ideally you have some reference to the code to be executed being stored somewhere. You can certainly just use variables (or an array) for that.

You would probably be best empowered by doing some chicanery with some custom compiler word.

instead of just : , come up with another defining word.

Perhaps one that takes the address to store the reference to the code.

In your example:

Code: Select all

b-handler LAMBDA[ + dup ];
Where the LAMBDA[ .. ]; combination compiles the code, and stores the reference in to the address on the stack (in this case, b-handler), rather than creating an entry in the dictionary.

if you're just going to stuff the handlers in to variables, you don't gain anything. So you want something like:

Code: Select all

action-vector
  dup      lambda[ ... ];
  dup 2  + lambda[ .. ];
  dup 4  + lambda[ .. ];
...
  dup 10 + lambda[ .. ];
Then action-vector contains a list of code vectors.

In the end, depending on your Forth, vocabulary entries aren't spectacularly expensive all told, and if you have your action words (or whatever they are) in a different vocabulary, then they don't "pollute" your working one, as they're, indeed, in a separate one. So, you can just "hide" them that way.
User avatar
commodorejohn
Posts: 299
Joined: 21 Jan 2016
Location: Placerville, CA
Contact:

Re: Anonymous definitions - some questions

Post by commodorejohn »

Yeah, that's kinda what I figured. Mostly I just wanted to avoid having to either create unique names for each definition or store an array of pointers to anonymous definitions if I could hack up something like Smalltalk's "code blocks" instead and leave them in-place, but that may not be particularly feasible here.
whartung
Posts: 1004
Joined: 13 Dec 2003

Re: Anonymous definitions - some questions

Post by whartung »

commodorejohn wrote:
Yeah, that's kinda what I figured. Mostly I just wanted to avoid having to either create unique names for each definition or store an array of pointers to anonymous definitions if I could hack up something like Smalltalk's "code blocks" instead and leave them in-place, but that may not be particularly feasible here.
Well a "code block" is just a pointer to code, plus an environment. Given that Forth doesn't really have "environments" (the environment, i.e. the stack, belongs to the caller, not the callee), then a code block in the end IS just that -- a pointer.

So, what you want is something that will compile the enclosed code (at, duh, compile time), but when it's "executed", just returns the pointer.

Thus you could "embed" that in to other definitions and simply pass it along.

Something like:

Code: Select all

: invoke ( execute the code vector on the top of the stack ) ... ;
: simpleif ( bool if-vector else-vector -- if bool is true, invoke if-vector, else invoke else-vector )
    ROT IF
        SWAP INVOKE 
    ELSE
        SWAP DROP INVOKE 
    THEN ;

: iftest 
    LAMBDA[ ." if passed" ] LAMBDA[ ." else passed" ] // we now have two code vectors on the stack
    2DUP 1 ROT ROT simpleif // should print "if passed"
    0 ROT ROT simpleif // should print "else passed"
;
In the iftest word, the LAMBDAs compile their enclosed code, and at runtime they "jump around it", and push the code vector on the stack.

There's no reason you shouldn't be able to do something like that. You may well be able to do this with stock BUILD DOES>, might have to refactor some of the compiling words to work in both situations.

For that vector thing we did before, you could do something like:

Code: Select all

: do-action ( action -- )
    >R  // Save the action number
    LAMBDA[ ... ] // action 3
    LAMBDA[ ... ] // action 2
    LAMBDA[ ... ] // action 1
    <R  // restore the action number
    PICK // use the action number to pick the action vector from the stack
    INVOKE ;
This isn't quite an "ON GOSUB" or a "CASE" statement, on the surface they look like that, but implementation wise it's quite different.

But you get the gist.
User avatar
commodorejohn
Posts: 299
Joined: 21 Jan 2016
Location: Placerville, CA
Contact:

Re: Anonymous definitions - some questions

Post by commodorejohn »

Ah, interesting. I really do need to sit down and properly wrap my head around the whole DOES> thing...
User avatar
GARTHWILSON
Forum Moderator
Posts: 8773
Joined: 30 Aug 2002
Location: Southern California
Contact:

Re: Anonymous definitions - some questions

Post by GARTHWILSON »

I have not been able to give this any time; but I would recommend reading (or re-reading) Leo Bordie's book "Thinking Forth." It has a never-ending wealth of non-obvious but really efficient ways to do a lot more than you could ever imagine. It's about programming philosophy, and opening up your mind to take advantage of the range of possibilities beyond what's built into Forth, ones that Forth lets you invent, because it takes away the limits. Ideally I'd re-read it every couple of years if time were no object.
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
JimBoyd
Posts: 931
Joined: 05 May 2017

Re: Anonymous definitions - some questions

Post by JimBoyd »

commodorejohn wrote:
it'd be nice if one handler routine could simply :noname up a definition to assign to a selector within the body of the handler itself, but I'm not up enough on low-level Forth monkeying to be certain that that's how this works. If, say, I have:

Code: Select all

: handler-A
    some-code
	:noname
	    code-for-another-handler ;
	selector-B !
	;
If that even parses correctly, is it going to roll up a new copy of the anonymous function every time it's executed? I'm thinking of this in a Lisp-y or Smalltalk-y context where symbols/lists are unique and immutable so there's only ever one copy of a given function, but I know Forth doesn't work that way by nature.
If :NONAME is not an immediate word, it will get compiled into the body of handler-A. handler-A's definition will conclude when the first semicolon is reached. Some value on the stack will be stored in selector-B possibly causing an empty stack error. If the stack wasn't empty, interpretation will continue to the second semicolon which will cause an error because the state is no longer compiling.

I also would recommend reading (or re-reading) Leo Brodie's book "Thinking Forth." You may find Leo Brodie's DOER/MAKE interesting.

Cheers,
Jim
JimBoyd
Posts: 931
Joined: 05 May 2017

Re: Anonymous definitions - some questions

Post by JimBoyd »

commodorejohn wrote:
it'd be nice if one handler routine could simply :noname up a definition to assign to a selector within the body of the handler itself, but I'm not up enough on low-level Forth monkeying to be certain that that's how this works. If, say, I have:

Code: Select all

: handler-A
    some-code
	:noname
	    code-for-another-handler ;
	selector-B !
	;
If that even parses correctly, is it going to roll up a new copy of the anonymous function every time it's executed? I'm thinking of this in a Lisp-y or Smalltalk-y context where symbols/lists are unique and immutable so there's only ever one copy of a given function, but I know Forth doesn't work that way by nature.
Here is your example using Leo Brodie's DOER/MAKE with real code in place of some-code and code-for-another-handler .

Code: Select all

DOER SELECTOR-B
: HANDLER-A
   .S
   MAKE SELECTOR-B
      ." THIS HAS NO NAME." ;
And here is a log of when it is loaded and executed. The computer's responses are in italics.

4D LOAD OK
SELECTOR-B OK
HANDLER-A STACK EMPTY OK
SELECTOR-B THIS HAS NO NAME. OK

And no, a new version is not compiled each time HANDLER-A is run.

To give you an example of what's possible with DOER/MAKE , here is an example from "Thinking Forth".

Code: Select all

// TODDLER
DOER ANSWER
: RECITAL
   CR ." YOUR DADDY IS STANDING ON THE TABLE.  ASK HIM 'WHY?' "
   MAKE ANSWER  ." TO CHANGE THE LIGHT BULB."
   BEGIN
      MAKE ANSWER  ." BECAUSE IT'S BURNED OUT."
      MAKE ANSWER  ." BECAUSE IT WAS OLD."
      MAKE ANSWER  ." BECAUSE WE PUT IT IN THERE A LONG TIME AGO."
      MAKE ANSWER  ." BECAUSE IT WAS DARK!"
      MAKE ANSWER  ." BECAUSE IT WAS NIGHT TIME!!"
      MAKE ANSWER  ." STOP SAYING WHY?"
      MAKE ANSWER  ." BECAUSE IT'S DRIVING ME CRAZY."
      MAKE ANSWER  ." JUST LET ME CHANGE THIS LIGHT BULB!"
   AGAIN ;
: WHY?   CR ANSWER QUIT ;
And here is a sample run.

Code: Select all

RECITAL 
YOUR DADDY IS STANDING ON THE TABLE.  ASK HIM 'WHY?'  OK
WHY? 
TO CHANGE THE LIGHT BULB.
WHY? 
BECAUSE IT'S BURNED OUT.
WHY? 
BECAUSE IT WAS OLD.
WHY? 
BECAUSE WE PUT IT IN THERE A LONG TIME AGO.
WHY? 
BECAUSE IT WAS DARK!
WHY? 
BECAUSE IT WAS NIGHT TIME!!
WHY? 
STOP SAYING WHY?
WHY? 
BECAUSE IT'S DRIVING ME CRAZY.
WHY? 
JUST LET ME CHANGE THIS LIGHT BULB!
WHY? 
BECAUSE IT'S BURNED OUT.
WHY? 
BECAUSE IT WAS OLD.
WHY? 
BECAUSE WE PUT IT IN THERE A LONG TIME AGO.
WHY? 
BECAUSE IT WAS DARK!
WHY? 
BECAUSE IT WAS NIGHT TIME!!
WHY? 
STOP SAYING WHY?
WHY? 
BECAUSE IT'S DRIVING ME CRAZY.
WHY? 
JUST LET ME CHANGE THIS LIGHT BULB!
WHY? 
BECAUSE IT'S BURNED OUT.
WHY? 
BECAUSE IT WAS OLD.
WHY? 
BECAUSE WE PUT IT IN THERE A LONG TIME AGO.
Cheers,
Jim
User avatar
commodorejohn
Posts: 299
Joined: 21 Jan 2016
Location: Placerville, CA
Contact:

Re: Anonymous definitions - some questions

Post by commodorejohn »

Hah, brilliant example :D Thanks, that looks like exactly what I was looking for.
JimBoyd
Posts: 931
Joined: 05 May 2017

Re: Anonymous definitions - some questions

Post by JimBoyd »

As I said, the example is straight out of "Thinking Forth". It's in Appendix B where Leo Brodie explains how DOER/MAKE works and gives implementations for various Forth systems. I don't think any of them were subroutine threaded systems though. I don't know if the Forth you're using is ITC ( indirect threaded ), DTC ( direct threaded ), or STC ( subroutine threaded ), or possibly some other threading technique. I successfully ported DOER/MAKE to my Forth, Fleet Forth for the Commodore 64, a short while ago. It's an ITC Forth. Last night I took a look at Durex Forth for the Commodore 64. It appears to use subroutine threaded code. I managed to port DOER/MAKE to it. Be warned! it has had minimal testing! That said, it should work with other subroutine threaded Forths for the 6502 based processors. Maybe someone using Tali Forth or Liara Forth could test it? :D

Code: Select all

( DOER/MAKE For A Subroutine Threaded Forth)
: NOTHING ;
: DOER
   CREATE ['] NOTHING , DOES> 
      @ 1- >R ;
VARIABLE DMARKER  \ Doer marker
: (MAKE)
   R>  1+  DUP 2+ DUP 3 +
   SWAP 1+ @ >BODY !
   @ ?DUP IF  >R  THEN ;
: MAKE
   STATE @ 0= ABORT" COMPILING ONLY!"
   POSTPONE (MAKE)
   HERE DMARKER ! 0 , ; IMMEDIATE
: ;AND
   POSTPONE EXIT
   HERE 1- DMARKER @ ! ; IMMEDIATE
: UNDO
   ['] NOTHING  ' >BODY ! ;
If you've had time to read "Thinking Forth", you'll notice one ability that's missing. The ability to use MAKE outside a colon definition to create an unnamed section of high level code that the DOER word performs. That ability is not needed in the examples I've shown and it would require knowledge of how a given system implements : ( colon) and ; ( semicolon).

Cheers,
Jim

[Edit: Corrected two typos.]
Last edited by JimBoyd on Wed Jan 23, 2019 9:24 pm, edited 1 time in total.
SamCoVT
Posts: 344
Joined: 13 May 2018

Re: Anonymous definitions - some questions

Post by SamCoVT »

JimBoyd wrote:
Maybe someone using Tali Forth or Liara Forth could test it? :D
It works on Tali, but I had to put a "T" in POSTPONE in two spots and add a definition for 2+. Here's the version that works on Tali Forth 2:

Code: Select all

( DOER/MAKE For A Subroutine Threaded Forth - Tali Forth version)
: 2+ 2 + ;
: NOTHING ;
: DOER
   CREATE ['] NOTHING , DOES> 
      @ 1- >R ;
VARIABLE DMARKER  \ Doer marker
: (MAKE)
   R>  1+  DUP 2+ DUP 3 +
   SWAP 1+ @ >BODY !
   @ ?DUP IF  >R  THEN ;
: MAKE
   STATE @ 0= ABORT" COMPILING ONLY!"
   POSTPONE (MAKE)
   HERE DMARKER ! 0 , ; IMMEDIATE
: ;AND
   POSTPONE EXIT
   HERE 1- DMARKER @ ! ; IMMEDIATE
: UNDO
   ['] NOTHING  ' >BODY ! ;

Code: Select all

recital
YOUR DADDY IS STANDING ON THE TABLE.  ASK HIM 'WHY?'  ok
why?
TO CHANGE THE LIGHT BULB.why?
BECAUSE IT'S BURNED OUT.why?
BECAUSE IT WAS OLD.why?
BECAUSE WE PUT IT IN THERE A LONG TIME AGO.why?
BECAUSE IT WAS DARK!
One oddity is that the " ok" message was missing after each run, leaving the cursor just after the printed message but accepting new input. It looks like the OK message is similarly missing in the example from Thinking Forth that JimBoyd showed a few posts back. Tali doesn't normally print carriage returns at the end of a string, so the new "why?" that I typed ended up on the end of each message, but the doer/make behavior seems to be working properly.
SamCoVT
Posts: 344
Joined: 13 May 2018

Re: Anonymous definitions - some questions

Post by SamCoVT »

SamCoVT wrote:
One oddity is that the " ok" message was missing after each run, leaving the cursor just after the printed message but accepting new input. It looks like the OK message is similarly missing in the example from Thinking Forth that JimBoyd showed a few posts back. Tali doesn't normally print carriage returns at the end of a string, so the new "why?" that I typed ended up on the end of each message, but the doer/make behavior seems to be working properly.
The missing " ok" message is because of the QUIT in WHY?. Sneaking an extra CR in there makes it work identically to the Thinking Forth version.

I changed why? to:

Code: Select all

: WHY? CR ANSWER CR QUIT ;
and now it works like:

Code: Select all

recital
YOUR DADDY IS STANDING ON THE TABLE.  ASK HIM 'WHY?'  ok
why?
TO CHANGE THE LIGHT BULB.
why?
BECAUSE IT'S BURNED OUT.
why?
BECAUSE IT WAS OLD.
JimBoyd
Posts: 931
Joined: 05 May 2017

Re: Anonymous definitions - some questions

Post by JimBoyd »

SamCoVT wrote:
It works on Tali, but I had to put a "T" in POSTPONE in two spots and add a definition for 2+.
Oops. I just fixed my typos. I already had DOER/MAKE working on Fleet Forth, an ITC Forth. I didn't know what Forth commodorejohn was using so I ported it to Durex Forth for the Commodore 64 which appears to be an STC Forth. I got frustrated with the Durex editor, specifically how to get out of insert mode and back to the Forth interpreter, so I entered the code interactively. I copied the working code to a text file by hand and somehow missed the 'T' in POSTPONE in both places!
Post Reply