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.

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.

Reply New Version