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 indicatedTYPEwith elements formed by applyingREADto each of their representations in turn.PRINT, when applied to a structured object, produces a representation of the object, with its elements represented asPRINTapplied 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: aFORMwhose first element is anATOMwith no value at all, on theINITIALoblist, 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,PUREQand their kin, chapter 33) plus pymdl's own doors,PYCODEand thePYCOMPILEfamily (chapter 34), each named so it cannot be mistaken for MDL's.A
SUBRhere 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 aSUBRwas not loadable and not a package; anRSUBR-- compiled MDL -- was aVECTORyou could take theLENGTHof. pymdl keeps that distinction, so a package's compiled entry is anRSUBRand only a genuine primitive is aSUBR(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;BACKblames its second argument even when the structure is what you got wrong;<EVAL>and<BYTE-SIZE>sayWRONG-NUMBER-OF-ARGUMENTSwhere everything else saysTOO-FEW-ARGUMENTS-SUPPLIED;<INSERT 5 <ROOT>>isFIRST-ARG-WRONG-TYPEwhile<LOOKUP 5 <ROOT>>is a plainARG-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 smallestFIX, 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.
RANDOMhere is MDL's own generator, not Python's: the two-word shift-and-exclusive-or of the interpreter'sarith.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 answers5rather than the manual's4because the answer depends on how far into the sequence you are, and the book's examples each start from a fresh interpreter.--random=SEEDon the command line (chapter 2) seeds it before anything runs, which is how a program that consultsRANDOMis made reproducible from outside.