9
votes

What is the general rule (rule of thumb) of choosing the order of the arguments when defining a word in Forth?

For example, in case of controlling a few servos, let's define SERVO! which will set the position of a servo channel.

Following the ! way, it should be : SERVO! ( val #ch -- ), but on the other hand doesn't it look more native to be : SERVO! ( #ch val -- )?

2

2 Answers

9
votes

How to choose arguments order? Good question! In Forth this question should cover the order of results as well.

Obviously any rules should have some rationales. They can be a consequence of some basic principles or should solve some problems.

I think we can start from convenience of code reusing (any source code fragment, including any single word). The essential formal parts of this convenience are consistency and source code minimization.

Regarding the arguments order these parts have the following meaning.

  • Consistency: we should use the similar order in the similar cases (more formal: retain some substantial isomorphism among the similar cases).
  • Minimization: we should choose an order that minimizes the total lexical size of all source codes (practically, the right order should minimize the total number of stack manipulations).

So, at the first, we should be consistent with the existing conventions (or with existing code), and at the second — find an optimal ordering. Of course, there can be exceptions when an inconsistent convention is already used in some required old code.

Some of the known conventions can be found in the famous "Thinking Forth" book by Leo Brodie. Two of them are as follows.

Let addresses precede counts. (Tip 4.18)

Example: ERASE ( addr u -- )

Let sources precede destinations. (Tip 4.19)

Example: MOVE ( source destination count -- )

There is also one well known rule that is not only a convention but also an optimization (confirmed by practice):

Let less permanent arguments precede more permanent.

Usually it leads to less stack manipulations. This rule can be found in many many standard words. For example, WRITE-FILE ( addr u file-id -- ior ) or SEARCH-WORDLIST ( addr u wid -- 0 | xt flag ) — there file-id and wid are more permanent than addr u pair. More permanent arguments are tended to be kept in variables on a topper level and so there is easier to pass them as top argument. Example: ... GET-CURRENT SEARCH-WORDLIST ...

This rule is also implicitly reflected in the following tip from Leo Brodie

  • When determining which arguments to handle via data structures rather than via the stack, choose the arguments that are the more permanent or that represent a current state. (Tip 7.3)

In case of the results this rule become inverse.

For returned items, let more permanent item precede less permanent.

For example, ior, flag, etc — are usually returned on the top.

Variations

In some cases it is convenient to have several variants. For example, well known words ROT and -ROT. Other examples:

\ the different order of input paramenters
for-list-node ( i*x list xt -- j*x )
foreach-list-node ( i*x xt list -- j*x )

\ the different order of output parameters
split ( sd-txt sd-key -- sd-txt false | sd-left sd-right true )
split- ( sd-txt sd-key -- sd-txt false | sd-right sd-left true )
4
votes

The ! way you mentioned of : SERVO! ( val #ch -- ) is probably the best, since you don't need to mentally keep track of which servo you are using while calculating the value you want the servo to move to.

Also, since it's similar to ! (you're storing a value into the servo), and you're naming the word SERVO!, it would be confusing if the parameter order were opposite of !.