6.2.2030 PICK CORE EXT

( xu...x1 x0 u -- xu...x1 x0 xu )

Remove u. Copy the xu to the top of the stack. An ambiguous condition exists if there are less than u+2 items on the stack before PICK is executed.

See:

Rationale:

0 PICK is equivalent to DUP and 1 PICK is equivalent to OVER.

ContributeContributions

ruvavatar of ruv [277] Example implementation for PICKSuggested reference implementation2023-02-10 21:51:27

: pick ( x0 i*x u.i -- x0 i*x x0 )
  dup 0= if drop dup exit then  swap >r 1- recurse r> swap
;

JimPetersonavatar of JimPeterson

I have always thought there could be a companion word to PICK, like this:

: place ( xu xu-1 ... x1 x0 y u -- y xu-1 ... x1 x0 )
  dup if rot >r 1- recurse r> exit then drop nip 
;

I think Anton wants "place" to mean something else to do with strings. Maybe this could be called "put"?

AntonErtlavatar of AntonErtl

I don't use PLACE, and I don't recommend that anybody uses it, but I recognize that there is a widely implemented word with that name that stores a counted string or somesuch.

Ulrich Hoffmann has proposed PLACE for standardization.

UlrichHoffmannavatar of UlrichHoffmann

Yes PLACE has a common meaning in systems influenced by F83. It's used to copy a string to a memory location ( c-addr1 u c-addr2 -- ) and place a length byte at addr2. So addr2 will then be a counted string (addr2 COUNT TYPE)... See the PLACE +PLACE proposal.

ruvavatar of ruvNew Version: Example implementation for PICK

Hide differences

: pick ( x0 ix u.i -- x0 ix x0 )

: pick ( x_u ... x_1 x_0 u -- x_u ... x_1 x_0 x_u )

dup 0= if drop dup exit then swap >r 1- recurse r> swap ;


(the stack diagram is copied from the glossary entry)

ruvavatar of ruvNew Version: Example implementation for PICK

Hide differences
: pick ( x_u ... x_1 x_0 u -- x_u ... x_1 x_0 x_u )
  dup 0= if drop dup exit then  swap >r 1- recurse r> swap
;

(the stack diagram is copied from the glossary entry)

NickMessengeravatar of NickMessenger

Discussion of the PLACE proposal is not relevant to the word that Mr. Peterson posted. To reduce distraction I'll rename it to BURY [see note], except I wish to change the API slightly, adding 1 to the index. It makes the example implementation more clumsy but I have at least one reason below:

: bury ( x_u x_u-1 ... x_1 x_0 u -- x_0 x_u-1 ... x_1 )
  dup 0 = if 2drop exit then
  dup 1 = if drop nip exit then
  rot >r 1- recurse r>
;

\ Rationale: replace x_u with x_0 then drop x_0:
T{ 44 33 22 11 00 4 bury -> 00 33 22 11 }T
T{ 00 0 bury -> }T

PICK fetches a value from down the stack, BURY stores a value to down the stack. I suspect most systems can feasibly implement constant time BURY, one cell load and store, just like PICK and unlike words like SWAP ROT and especially ROLL.

PICK and BURY are sufficient to express every possible stack operation. No need for even >R R>:

: dup   0 pick ;  : drop  0 bury ;
: over  1 pick ;  : nip   1 bury ;

: 2nip  2 bury 2 bury ;
: swap  dup 2 pick 2nip ;

: 3nip  3 bury 3 bury 3 bury ;
: rot   over over 4 pick 3nip ;
( etc etc )

Some of the rarer ones could be demoted to be optional, since the system user can implement them with PICK BURY.

[note] About the name BURY: I seem to recall seeing some system somewhere that used the names DIG and BURY to mean ROT and -ROT. I thought it was retroforth but no, that has a ROT. As an aside: since I am reusing BURY to mean something different, ideally It'd be nice to rename PICK to DIG to match. I assume that's not appropriate for the standard though. The name is important but the functionality is useful whatever its name.

ruvavatar of ruv

@NickMessenger wrote:

I wish to change the API slightly, adding 1 to the index. It makes the example implementation more clumsy but I have at least one reason below

PICK and BURY are sufficient to express every possible stack operation.

I see, but it seems to make bury more clumsy to use.

Could you give some practical examples where the word bury is useful? Especially, where you need to simply drop the argument ( x 0 ).

NickMessengeravatar of NickMessenger

0 BURY is just another way to express DROP, so not useful in-and-of-itself, again except for the fact that it makes PICK/BURY sufficient to express all stack operations. You could picture a super minimal system that no stack words at all except these two, so DROP DUP NIP would be written in forth as above. Implementation-wise you could imagine 123 0 BURY would store 123 in the same cell where it already is then DROP it, so no need for any special cases. It's the dropping effect we're looking for.

As for practical examples, I listed the above words. I can list some more:

: -rot  dup 3 pick 3 pick 3nip ;
: flip ( a b c -- c b a ) dup 3 pick 2 bury 3 bury ;

: 6nip  6 bury 6 bury 6 bury 6 bury 6 bury 6 bury ;
: 2over  3 pick 3 pick ;
: 2rot  2over 2over 9 pick 9 pick 6nip ;

I feel like PICK BURY could lessen perceived need for local variables. Ideally you rewrite your words to need fewer parameters but if you can't then there's a need to move values somewhere, be it locals, globals, the rstack, etc. SWAP ROT ROLL require N loads and stores so are less efficient, but this would allow you to PICK what you need to work with, BURY the results where they go and DROP any intermediates.

NickMessengeravatar of NickMessenger

A system like gforth with SP@:

: pick ( xu..x0 u -- xu..x0 xu ) 1+ cells sp@ + @ ;
: bury ( xu..x1 x0 u -- x0..x1 ) 1+ cells sp@ + ! ;
 
cr 22 11 00 2 pick .s \ <4> 22 11 0 22
cr 77 2 bury .s \ <4> 22 11 77 22

ruvavatar of ruv

this would allow you to PICK what you need to work with, BURY the results where they go and DROP any intermediates.

Do you have any programs that use bury? I mean, other than a super-minimal system that doesn't provide any stack words other than pick and bury—that's interesting, but impractical )

NickMessengeravatar of NickMessenger

gforth-internal does in fact define the 2 + variant which they call STICK and use a few times. My favorite system, durexForth, uses a split stack and so cannot support SP@ but does have PICK. I wrote a BURY in its provided assembler wordset, then used BURY to implement 2SWAP 2ROT 2NIP.

...I don't actually have any programs that use those words though. And I just benchmarked it and the PICK/BURY version of 2SWAP took about triple the time of the ROT >R ROT R> version. If I use VALUEs instead of literals it's only double the time. So maybe it's pointless. I just think it's weird that the standard has a fetch-from-downstack but no store-to-downstack.

ruvavatar of ruv

I just think it's weird that the standard has a fetch-from-downstack but no store-to-downstack.

Yes, this can be considered as a slight gap in orthogonality. But since such a word can be defined using standard words, I don't think there's any point in standardizing it before it's used in practice.

Concerning the stack effect (API)

I would prefer the original one, because it is similar to other fetch/store words like (@, !), (2@, 2!), (defer@, defer!), etc.

The general stack diagrams of these words are:

  • fetch ( identifier -- data-object )
  • store ( data-object identifier -- )

And if you store a data object by an identifier, you should then read the same data by the same identifier. Your option violates this rule.

A feature of pick is that it interprets the underneath stack items as an array on which it operates. The same should be for the word that stores a value.

Concerning naming

In comp.lang.forth, this word was mentioned (Andrew Haley, 2013-01-29) as poke.

In the paper "Symbolic Stack Addressing" (Adin Tevet, 1989), this word is called post. The idea there is that a phrase n pick n post, where n is a non-negative integer less than the stack depth, does not change the state of the stack.

I would prefer the name poke because:

  • it is a historical name for a function that stores a value in memory, so it has the corresponding semantic association (unlike post, stick and bury);
  • it is mainly a verb (unlike stick, which is mostly a noun);
  • its sound is closer to pick than post and bury;

The second, but much less preferable, option is stick.

JimPetersonavatar of JimPeterson

But since such a word can be defined using standard words, I don't think there's any point in standardizing it before it's used in practice.

I don't think it will be used in practice much unless it's standardized. This feels like a chicken-egg thing. Also, pick can be defined using standard words, yet it has been standardized, so I don't think that's a good argument.

On my specialized system, I have a form of poke (called place, there), and it is convenient to be able to pick stack values to operate on and then poke the results back in place. This happens in words that have more than two parameters, and is further provoked by my system's inability to implement locals (efficiently). It's also more common in loops, where I need to get the stack back to the way it was at the top of the loop.

I try to avoid using poke when I'm writing higher-level code that I want to be portable, so all the examples where I use poke are very machine-specific, internal definitions.

ruvavatar of ruv

@JimPeterson wrote:

This feels like a chicken-egg thing. Also, pick can be defined using standard words, yet it has been standardized, so I don't think that's a good argument.

If someone researches how many systems provide such a word and prepares a proposal, I will support it.

One argument for including some well-known words in the standard is that this ensures that if a Forth system provides a word with that name, that word has the specified behavior (rather than some system-specific behavior). Therefore, programs can check weather a word is provided by the system and define it if it is missing, or confidently use it if it's already present.

Reply New Version

gethboavatar of gethbo [363] Clarification on what constitutes an ambiguous condition for this word.Request for clarification2024-09-19 14:31:15

The description for this word says: "...An ambiguous condition exists if there are less than u+2 items on the stack before PICK is executed"

In this example with 3 items on the stack: (x2 x1 x0 2 -- x2 x1 x0 x2)

This example worked in gforth - However u+2 = 4 and since 3 < 4 this is an ambiguous condition.

I might be missing something here but shouldn't this read : "An ambiguous condition exists if there are less than u+1 items on the stack before PICK is executed"

AntonErtlavatar of AntonErtl

In your example there are 4 items on the stack. You have to count the 2, too.

Closed
Reply New Version

ruvavatar of ruv [414] Interpretation of the top input parameter of PICKComment2025-10-07 08:43:07

The word pick has the type ( x.0 u.cnt*x u.cnt -- x.0 u.cnt*x x.0 ).

The word roll has the type ( x.0 u.cnt*x u.cnt -- u.cnt*x x.0 ).

The discussed word poke has the type ( x.0 u.cnt*x x.1 u.cnt -- x.1 u.cnt*x ).

In my other comment I wrote that pick "interprets the underneath stack items as an array on which it operates", and then u.cnt is an index in this array.

A more fundamental interpretation is that the input parameter u.cnt (in pick, poke, roll) represents the number of stack items that need to be "skipped" to locate the target input parameter x.0 (that is copied, taken, or overwritten).

A consequence of this is that:

  • 2pick must have the type ( xd.0 u.cnt*x u.cnt -- xd.0 u.cnt*x xd.0 );
  • 2poke must have the type ( xd.0 u.cnt*x xd.1 u.cnt -- xd.1 u.cnt*x );
  • 2roll must have the type ( xd.0 u.cnt*x u.cnt -- u.cnt*x xd.0 );

Rationale: the number of "skipped" stack items should not depend on the data type of the target parameter.

See also my other comment on ForthHub (2024-12-02) on this regard.

EricBlakeavatar of EricBlake

The word pick has the type ( x.0 u.cntx u.cnt -- x.0 u.cntx x.0 ).

and it additionally documents that 0 pick is the same as dup, and 1 pick is the same as over.

The word roll has the type ( x.0 u.cntx u.cnt -- u.cntx x.0 ).

and it additionally documents that 1 roll is the same as swap, and 2 roll is the same as rot.

2pick must have the type ( xd.0 u.cntx u.cnt -- xd.0 u.cntx xd.0 );

2roll must have the type ( xd.0 u.cntx u.cnt -- u.cntx xd.0 );

But the standard also already has 2dup, 2over, 2swap, and 2rot. If I try to extrapolate the same mappings on the larger 2-cell types, without paying attention to your proposed stack diagram for 2pick and 2roll, my first guess would have been:

0 2pick would be the same as 2dup, and 1 2pick the same as 2over. 1 2roll would be the same as 2swap, and 2 2roll the same as 2rot.

That is, I would have assumed that since existing 2XXX words have stack effects that force the entire stack to be paired off up to the point of impact, that the same pairing effect would be present in your proposed 2pick and 2roll.

But I was pleasantly surprised to note that your proposal does NOT do that, but is actually more powerful by letting me control how many single-cell slots to skip before finally accessing a 2-cell object.

In particular, while 0 2pick does end up being the same as 2dup, your 1 2pick has an interesting single-cell stack effect ( x.a x.b x.c -- x.a x.b x.c x.a x.b ) that is not available from any one single standard word, but which I have wanted in my own code (I've ended up with things like dup 2over rot drop or over 3 pick swap to get the same effect). And it is not until you get to 2 2pick that you get the effect of 2over.

Similarly, your 1 2roll has the single-cell stack effect ( x.a x.b x.c -- x.c x.a x.b ) which is the well-known -rot (aka rot rot in the standard), and it is not until you get to 2 2roll that you finally accomplish 2swap, or 4 2roll that you accomplish 2rot.

ruvavatar of ruv

@EricBlake wrote

your 1 2pick has an interesting single-cell stack effect ( x.a x.b x.c -- x.a x.b x.c x.a x.b ) that is not available from any one single standard word,

Similarly, your 1 2roll has the single-cell stack effect ( x.a x.b x.c -- x.c x.a x.b ) which is the well-known -rot

Nice finding! This makes me think that it is even worth including them into the standard :-)

JimPetersonavatar of JimPeterson

I feel like the 2(verb) words have too much mental baggage, like EricBlake says, in implying that it's viewing/treating the stack as a collection of doubles. Would it be better to tend towards calling these words something like (verb)2 or D(verb) in order to avoid this implication?:

PICK2    DPICK
POKE2    DPOKE
ROLL2    DROLL

Or (I hesitate to suggest this route) they could be named PICKPICK, POKEPOKE and ROLLROLL, where 5 PICKPICK is equivalent to 5 PICK 5 PICK, etc. This would confuse the issue, because the stack element being indexed would be the least significant, deeper cell of the double, contrary to the previously proposed stack effect:

: 3PICKPICK ( x5 x4 X3 X2 x1 x0 -- x5 x4 X3 X2 x1 x0 X3 X2 )
  3 PICK 3 PICK
;

: 3POKEPOKE ( X5 X4 x3 x2 x1 x0 -- x1 x0 x3 x2 )
  3 POKE 3 POKE
;

: 3ROLLROLL ( x5 x4 X3 X2 x1 x0 -- x5 x4 x1 x0 X3 X2 )
  3 ROLL 3 ROLL
;

At the least, knowing that this sort of doubling of the operation will take place allows me to mentally count through the stack as single cells and determine what index I need. Unfortunately, it makes the stack effect difficult to document succinctly:

PICKPICK ( xd.0 (u.cnt-1)*x u.cnt -- xd.0 (u.cnt-1)*x xd.0 )

etc. Or maybe the document avoids talking about doubles altogether:

PICKPICK (      xu xu-1 ... x0 u.cnt       --    xu xu-1 ... x0 xu xu-1 )
POKEPOKE ( xu+1 xu xu-1 ... x2 x1 x0 u.cnt -- x1 x0 xu-1 ... x2         )
ROLLROLL ( xu xu-1 xu-2 ... x0 u.cnt       --       xu-2 ... x0 xu xu-1 )

Also, it leads to some degenerate situations when u.cnt is 0:

: example ( x2 x1 X0 -- x2 x1 X0 X0 X0 )
  0 PICK 0 PICK
;

: example ( X2 X1 x0 -- x0 )
  0 POKE 0 POKE
;

Simple reference implementations:

: PICKPICK  dup >r pick r> pick ;
: POKEPOKE  dup >r poke r> poke ;
: ROLLROLL  dup >r roll r> roll ;

Variants that treat the stack as a collection of doubles:

: 2PICK  2* 1+ pickpick ;
: 2POKE  2* 1+ pokepoke ;
: 2ROLL  2* 1+ rollroll ;

ruvavatar of ruv

@JimPeterson wrote:

I feel like the 2(verb) words have too much mental baggage, like EricBlake says, in implying that it's viewing/treating the stack as a collection of doubles. Would it be better to tend towards calling these words something like (verb)2 or D(verb)

  • (verb)2 — there are no such precedents in the naming of standard words. In practice, the numbers at the end of a name are used for different implementation/versions for a switchable word (a word created with defer).

  • D(verb) — the prefix D in names means the data type "d" (a double-cell signed number, see Data types), like in d+, d-, d., d=, d0<, dabs, etc.

At the moment, the only way (among the standard words) to say that a word operates on a parameter whose type is xd (an unspecified cell pair, 2*x) is to use the prefix "2" in its name.

I understand that at first glance it might seem like 2pick treats the entire stack as a an array of unspecified cell pairs. But after a little study, the reader should realize that 2 (2*x) in its name refers not to the type of the location of the parameter, but to the type of the parameter itself.

Furthermore, there is no practical justification for having a separate word whose only difference is that it counts the stack items that need to be "skipped" in pairs.

Overall, I'm currently only suggesting the specific API for the words 2pick, 2roll, and 2poke, in case anyone wants to implement them. I'm not proposing standardization at this point. We'll see how many systems implement them.

JimPetersonavatar of JimPeterson

I really feel like this naming is asking for trouble. I understand that the following words treat the top of the stack as just a pair of cells, not necessarily a double:

 2! 2>R 2@ 2R> 2R@ 2CONSTANT 2DROP 2LITERAL 2VALUE 2VARIABLE 

But having called them names like D!, or DLITERAL, or DVALUE, etc., would have been a good choice that had some reasonable consistency with F! and FLITERAL and FVALUE, etc., but I guess that ship has sailed, and there's absolutely no correcting it without a significant overhaul and deprecation warnings.

My main concern, though, is calling something 2ROLL in the context of a system that has words like 2OVER, 2ROT, and 2SWAP. Those three existing words strongly imply that words starting with 2 will treat the stack as though it is a stack of double-cell values and perform well-understood stack operations on them, such that users may (incorrectly) assume that the index supplied to 2ROLL would be how many double-cell values are affected (minus 1). It feels only slightly less likely that they would make the same mistake with 2PICK.

Reply New Version