The pymdl book

6. Built-in functions

3.1. Representation

Up to this point, all the objects we have been concerned with have had no internal structure discernible in MDL. While the characteristics of objects with internal structure differ greatly, the way READ and PRINT handle them is uniform, to wit:

  • READ, when applied to the representation of a structured object, builds and returns an object of the indicated TYPE with elements formed by applying READ to each of their representations in turn.
  • PRINT, when applied to a structured object, produces a representation of the object, with its elements represented as PRINT applied to each of them in turn.

A MDL object which is used to represent the application of a function to its arguments is an object of TYPE FORM. Its printed representation is

< func arg-1 arg-2 ... arg-N >

where func is an object which designates the function to be applied, and arg-1 through arg-N are objects which designate the arguments or "actual parameters" or "inputs". A FORM is just a structured object which is stored and can be manipulated like a LIST (its "primitive type" is LIST -- chapter 9). The application of the function to the arguments is done by EVAL. The usual meaning of "function" (uncapitalized) in this document will be anything applicable to arguments.

<TYPE '<+ 1 2>>            ⇒ FORM
<TYPEPRIM FORM>            ⇒ LIST
<LENGTH '<+ 1 2>>          ⇒ 3
<1 '<+ 1 2>>               ⇒ +
<REST '<+ 1 2>>            ⇒ (1 2)

The last line is chapter 10's rule arriving early: a RESTed structure whose TYPE is not its PRIMTYPE prints CHTYPEd to that PRIMTYPE, so the remainder of a FORM prints as a LIST.

3.2. Evaluation

EVAL applied to a FORM acts as if following these directions:

First, examine the func (first element) of the FORM. If it is an ATOM, look at its "value" (global or local, in that order -- see next chapter). If it is not an ATOM, EVAL it and look at the result of the evaluation. If what you are looking at is not something which can be applied to arguments, complain (via the ERROR function). Otherwise, inspect what you are looking at and follow its directions in evaluating or not evaluating the arguments (chapters 12 and 33) and then "apply the function" -- that is, EVAL the body of the object gotten from func.

Found along the way. "Global or local, in that order" is the rule the manual names the G/LVAL in the next chapter, and it is the opposite of VALUE's order. It is also the rule that makes chapter 30's dynamic loader possible: a FORM whose first element is an ATOM with no value at all, on the INITIAL oblist, is what the library system's error handler catches.

3.3. Built-in Functions (TYPE SUBR, TYPE FSUBR)

The built-in functions of MDL come in two varieties: those which have all their arguments EVALed before operating on them (TYPE SUBR, for "subroutine", pronounced "subber") and those which have none of their arguments EVALed (TYPE FSUBR, historically from Lisp, pronounced "effsubber"). Collectively they will be called F/SUBRs, although that term is not meaningful to the interpreter. See appendix A for a listing of all F/SUBRs and short descriptions. The term "Subroutine" will be used herein to mean both F/SUBRs and compiled user programs (RSUBRs and RSUBR-ENTRYs -- chapter 33).

Unless otherwise stated, every MDL built-in Subroutine is of TYPE SUBR. Also, when it is stated that an argument of a SUBR must be of a particular TYPE, note that this means that EVAL of what is there must be of the particular TYPE.

Another convenient abbreviation which will be used is "the SUBR pname" in place of "the SUBR which is initially the 'value' of the ATOM of PNAME pname". "The FSUBR pname" will be used with a similar meaning.

<TYPE ,+>                  ⇒ SUBR
<TYPE ,COND>               ⇒ FSUBR
<APPLICABLE? ,+>           ⇒ T

pymdl's own. Appendix A of this book is the manual's appendix 2, generated from the running interpreter: every built-in with the manual's own entry beside it, its kind, the module it lives in and whether its arguments are checked. All 275 of the manual's entries have a built-in here, and there are 349 built-ins in all -- the surplus is the era's own unlisted machinery (ATOSQ, SQUOTA, PUREQ and their kin, chapter 33) plus pymdl's own doors, PYCODE and the PYCOMPILE family (chapter 34), each named so it cannot be mistaken for MDL's.

A SUBR here is a Python function, where the era's was PDP-10 code assembled into the interpreter. That is the substrate, and it shows in one place a program can see: what a compiled MDL routine is. On the era a SUBR was not loadable and not a package; an RSUBR -- compiled MDL -- was a VECTOR you could take the LENGTH of. pymdl keeps that distinction, so a package's compiled entry is an RSUBR and only a genuine primitive is a SUBR (chapter 30).

Measured, MDL 55. "When it is stated that an argument must be of a particular TYPE" understates how irregular the checking is, and the irregularity is the era's, not a simplification here. <REST 5> names the argument's position while <SUBSTRUC "AB" "X"> does not; BACK blames its second argument even when the structure is what you got wrong; <EVAL> and <BYTE-SIZE> say WRONG-NUMBER-OF-ARGUMENTS where everything else says TOO-FEW-ARGUMENTS-SUPPLIED; <INSERT 5 <ROOT>> is FIRST-ARG-WRONG-TYPE while <LOOKUP 5 <ROOT>> is a plain ARG-WRONG-TYPE. pymdl's table of checks was not derived from the manual: every entry was measured by running the form through both MDL 55 and pymdl and diffing the error banners, 720 forms over five generated batteries (chapter 37). Appendix A's last column says which built-ins that table covers.

3.4. Examples (+ and FIX; Arithmetic)

<+ 2 4 6>                  ⇒ 12

The SUBR + adds numbers. Most of the usual arithmetic functions are MDL SUBRs: +, -, *, /, MIN, MAX, MOD, SIN, COS, ATAN, SQRT, LOG, EXP, ABS. (See appendix A for short descriptions of these.) All except MOD, which wants FIXes, are indifferent as to whether their arguments are FLOAT or FIX or a mixture. In the last case they exhibit "contagious FLOATing": one argument of TYPE FLOAT forces the result to be of TYPE FLOAT.

<FIX 1.0>                  ⇒ 1
<FLOAT 1>                  ⇒ 1.0

The SUBR FIX explicitly returns a FIXed-point number corresponding to a FLOATing-point number. FLOAT does the opposite.

<+ 5 <* 2 3>>                  ⇒ 11
<SQRT <+ <* 3 3> <* 4 4>>>     ⇒ 5.0
<- 5 3 2>                      ⇒ 0
<- 5>                          ⇒ -5
<MIN 1 2.0>                    ⇒ 1.0
</ 11 7 2.0>                   ⇒ 0.5

Note this last result: the division of two FIXes gives a FIX with truncation, not rounding, of the remainder; the intermediate result remains a FIX until a FLOAT argument is encountered.

3.5. Arithmetic Details

+, -, *, /, MIN, and MAX all take any number of arguments, doing the operation with the first argument and the second, then with that result and the third argument, etc. If called with no arguments, each returns the identity for its operation (0, 0, 1, 1, the greatest FLOAT, and the least FLOAT, respectively); if called with one argument, each acts as if the identity and the argument had been supplied. They all will cause an overflow or underflow error if any result, intermediate or final, is too large or too small for the machine's capacity. (That error can be disabled if necessary -- section 19.9.)

<+>                        ⇒ 0
<->                        ⇒ 0
<*>                        ⇒ 1
</>                        ⇒ 1
<MIN>                      ⇒ 1.7014118E+38
<MAX>                      ⇒ -1.7014118E+38
<MOD 17 5>                 ⇒ 2
<ABS -3>                   ⇒ 3

MIN and MAX with no arguments really do answer the identities the manual gives, which is to say the wrong way round from what their names suggest: the identity for "smallest so far" is the largest FLOAT there is. Those two values are the PDP-10 single-precision limits of 5.2.2, and pymdl answers them though its FLOATs reach further.

Measured, MDL 55. Overflow is on by default and is a real error, so <* 34359738367 2> signals rather than wrapping; <OVERFLOW <>> turns it off (chapter 19). Chapter 21 has the arithmetic that is supposed to wrap -- the machine word -- and the octal literal that reaches the smallest FIX, which chapter 5 showed cannot be typed in decimal.

One arithmetic function that always requires some discussion is the pseudo-random-number generator. MDL's is named RANDOM, and it always returns a FIX, uniformly distributed over the whole range of FIXes. If RANDOM is never called with arguments, it always returns the exact same sequence of numbers, for convenience in debugging. "Debugged" programs should give RANDOM two arguments on the first call, which become seeds for a new sequence. Popular choices of new seeds are the numbers given by TIME (which see), possibly with bits modified (chapter 21). Example ("pick a number from one to ten"):

<+ 1 <MOD <RANDOM> 10>>    ⇒ 5

pymdl's own. RANDOM here is MDL's own generator, not Python's: the two-word shift-and-exclusive-or of the interpreter's arith.mbd092, ported instruction for instruction, so a fresh interpreter walks the same default sequence the manual promises and <RANDOM seed1 seed2> sets the two words. The example answers 5 rather than the manual's 4 because the answer depends on how far into the sequence you are, and the book's examples each start from a fresh interpreter. --random=SEED on the command line (chapter 2) seeds it before anything runs, which is how a program that consults RANDOM is made reproducible from outside.