The pymdl book
12. Functions
This chapter could be named "fun and games with argument LISTs". Its purpose
is to explain the more complicated things which can be done with FUNCTIONs,
and this involves, basically, explaining all the various tokens which can
appear in the argument LIST of a FUNCTION. Topics are covered in what is
approximately an order of increasing complexity. This order has little to do
with the order in which tokens can actually appear in an argument LIST, so
what an argument LIST "looks like" overall gets rather lost in the shuffle.
To alleviate this problem, section 9.9 is a summary of everything that can go
into an argument LIST, in the correct order. If you find yourself getting
lost, please refer to that summary.
9.1. "OPTIONAL"
MDL provides very convenient means for allowing optional arguments. The
STRING "OPTIONAL" (or "OPT" -- they're totally equivalent) in the
argument LIST allows the specification of optional arguments with values to
be assigned by default. The syntax of the "OPTIONAL" part of the argument
LIST is as follows:
"OPTIONAL" al-1 al-2 ... al-N
First, there is the STRING "OPTIONAL". Then there is any number of either
ATOMs or two-element LISTs, intermixed, one per optional argument. The
first element of each two-element LIST must be an ATOM; this is the dummy
variable. The second element is an arbitrary MDL expression. If there are
required arguments, they must come before the "OPTIONAL".
When EVAL is binding the variables of a FUNCTION and sees "OPTIONAL", the
following happens:
- If an explicit argument was given in the position of an optional one, the
explicit argument is bound to the corresponding dummy
ATOM. - If there is no explicit argument and the
ATOMstands alone, that is, it is not the first element of a two-elementLIST, thatATOMbecomes "bound", but no local value is assigned to it [see below]. A local value can be assigned to it by usingSET. - If there is no explicit argument and the
ATOMis the first element of a two-elementLIST, the MDL expression in theLISTwith theATOMis evaluated and bound to theATOM.
[Until an ATOM is assigned, any attempt to reference its LVAL will produce
an error. The predicate SUBRs BOUND? and ASSIGNED? can be used to check
for such situations. BOUND? returns T if its argument is currently bound
via an argument LIST or has ever been SET while not bound via an argument
LIST. The latter kind of binding is called "top-level binding", because it
is done outside all active argument-LIST binding. ASSIGNED? will return
#FALSE () if its argument is either unassigned or unbound. By the
way, there are two predicates for global values similar to BOUND? and
ASSIGNED?, namely GBOUND? and GASSIGNED?. Each returns T only if its
argument, which (as in BOUND? and ASSIGNED?) must be an ATOM, has a
global value "slot" (chapter 24) or a global value, respectively.]
The bracketed paragraph is the whole of the second bullet, made visible:
<DEFINE OPTB (A "OPTIONAL" B) <BOUND? B>> ⇒ OPTB
<OPTB 1> ⇒ T
<DEFINE OPTA (A "OPTIONAL" B) <ASSIGNED? B>> ⇒ OPTA
<OPTA 1> ⇒ #FALSE ()
B is bound and unassigned: bound because the argument LIST bound it,
unassigned because no argument arrived and no default was written. Chapter 7's
UNASSIGN produces the same state deliberately.
Example:
<DEFINE INC1 (A "OPTIONAL" (N 1)) <SET .A <+ ..A .N>>> ⇒ INC1
<SET B 0> ⇒ 0
<INC1 B> ⇒ 1
<INC1 B 5> ⇒ 6
The manual prints that last answer as ; 0.B was 1 and the increment
was 5, so it is 6.
Here we defined another (not quite working) increment FUNCTION. It now takes
an optional argument specifying how much to increment the ATOM it is given.
If not given, the increment is 1. Now, 1 is a pretty simple MDL
expression: there is no reason why the optional argument cannot be complicated
-- for example, a call to a FUNCTION which reads a file on an I/O device.
9.2. TUPLEs
9.2.1. "TUPLE" and TUPLE (the TYPE)
There are also times when you want to be able to have an arbitrary number of
arguments. You can always do this by defining the FUNCTION as having a
structure as its argument, with the arbitrary number of arguments as elements
of the structure. This can, however, lead to inelegant-looking FORMs and
extra garbage to be collected. The STRING "TUPLE" appearing in the
argument LIST allows you to avoid that. It must follow explicit and optional
dummy arguments (if there are any of either) and must be followed by an ATOM.
The effect of "TUPLE" appearing in an argument LIST is the following: any
arguments left in the FORM, after satisfying explicit and optional arguments,
are EVALed and made sequential elements of an object of TYPE and PRIMTYPE
TUPLE. The TUPLE is then bound to the ATOM following "TUPLE" in the
argument LIST. If there were no arguments left by the time the "TUPLE" was
reached, an empty TUPLE is bound to the ATOM.
An object of TYPE TUPLE is exactly the same as a VECTOR except that a
TUPLE is not held in garbage-collected storage. It is instead held with
ATOM bindings in a control stack. This does not affect manipulation of the
TUPLE within the function generating it or any function called within that
one: it can be treated just like a VECTOR. Note, however, that a TUPLE
ceases to exist when the function which generated it returns. Returning a
TUPLE as a value is a good way to generate an error. (A copy of a TUPLE
can easily be generated by segment-evaluating the TUPLE into something; that
copy can be returned.) The predicate LEGAL? returns #FALSE () if it is
given a TUPLE generated by an APPLICABLE object which has already returned,
and T if it is given a TUPLE which is still "good".
LEGAL? and the copying remedy both work as described:
<DEFINE LG ("TUPLE" T) <LEGAL? .T>> ⇒ LG
<LG 1 2> ⇒ T
<DEFINE ESC ("TUPLE" T) .T> ⇒ ESC
<LEGAL? <ESC 1 2>> ⇒ #FALSE ()
<DEFINE COPYT ("TUPLE" T) [!.T]> ⇒ COPYT
<COPYT 1 2> ⇒ [1 2]
-- T while the Function is running, #FALSE () for the same TUPLE once
it has returned, and a VECTOR copy that is safe to keep.
pymdl's own. "Not held in garbage-collected storage" describes the era's two storage regions, and pymdl has one, so the escaped
TUPLEis not actually reclaimed here:<LENGTH <ESC 1 2>>still answers2where the era would have been reading whatever had since been pushed onto the control stack. What pymdl reproduces is every honest observable --LEGAL?says#FALSE (), and printing an escapedTUPLErefuses withILLEGAL-ARGUMENT-BLOCKrather than showing you a corpse. A program that obeys the manual cannot tell the difference; one that does not gets a definite answer here instead of the era's undefined one. Chapter 25 has the storage the era meant, and chapter 36 has the control stack as machine words.
Example:
<DEFINE NTHARG (N "TUPLE" T)
;"Get all but first argument into T."
<COND (<==? 1 .N> 1)
;"If N is 1, return 1st arg, i.e., .N,
i.e., 1. Note that <1? .N> would be
true even if .N were 1.0."
(<L? <LENGTH .T> <SET N <- .N 1>>>
#FALSE ("DUMMY"))
;"Check to see if there is an Nth arg,
and make N a good index into T while
you're at it.
If there isn't an Nth arg, complain."
(ELSE <NTH .T .N>)>> ⇒ NTHARG
<NTHARG 1 10 20> ⇒ 1
<NTHARG 2 10 20> ⇒ 10
<NTHARG 3 10 20> ⇒ 20
<NTHARG 9 10 20> ⇒ #FALSE ("DUMMY")
NTHARG, above, takes any number of arguments. Its first argument must be of
TYPE FIX. It returns EVAL of its Nth argument, if it has an Nth
argument. If it doesn't, it returns #FALSE ("DUMMY"). (The ELSE is not
absolutely necessary in the last clause. If the Nth argument is a FALSE, the
COND will return that FALSE.) Exercise for the reader: NTHARG will
generate an error if its first argument is not FIX. Where and why? (How
about <NTHARG 1.5 2 3>?) Fix it.
Found along the way. The exercise's answer is worth having, since it is a good miniature of chapter 6's irregular argument checking.
<==? 1 .N>is#FALSE ()for1.5, so the first clause declines.<- .N 1>is happy with aFLOATand gives0.5, and<L? <LENGTH .T> 0.5>is also happy, so the second clause declines too. The error comes from<NTH .T 0.5>in the third, which wants aFIX-- so the complaint namesNTH, three clauses away from the argument that was wrong. Adding<COND (<NOT <TYPE? .N FIX>> <ERROR ...>)>at the top is the fix, and the general lesson is chapter 15's: aDECLonNsays it once and lets the interpreter do the complaining, in the right place.
9.2.2. TUPLE (the SUBR) and ITUPLE
These SUBRs are the same as VECTOR and IVECTOR, except that they build
TUPLEs (that is, vectors on the control stack). They can be used only at top
level in an "OPTIONAL" list or "AUX" list (see below). The clear advantage
of TUPLE and ITUPLE ("implicit tuple") is in storage-management efficiency.
They produce no garbage, since they are flushed automatically upon function
return.
Examples:
<DEFINE F (A B "AUX" (C <ITUPLE 3 3>)) .C> ⇒ F
<F 1 2> ⇒ [3 3 3]
creates a 3-element TUPLE and SETs C to it. (The manual's example makes
ten; three is easier to print.)
<DEFINE H ("OPTIONAL" (A <ITUPLE 3 5>)
"AUX" (B <TUPLE !.A 1 2 3>))
<LENGTH .B>> ⇒ H
<H> ⇒ 6
These are valid uses of TUPLE and ITUPLE. However, the following is
not a valid use of TUPLE, because it is not called at top level of the
"AUX":
<DEFINE NO (A B "AUX" (C <REST <TUPLE .A>>)) .C> ⇒ NO
<NO 1 2> ⇒ *ERROR* NOT-IN-ARG-LIST
However, the desired effect could be achieved by
<DEFINE OK (A B "AUX" (D <TUPLE 1 2>) (C <REST .D>)) .C> ⇒ OK
<OK 1 2> ⇒ [2]
9.3. "AUX"
"AUX" (or "EXTRA" -- they're totally equivalent) are STRINGs which,
placed in an argument LIST, serve to dynamically allocate temporary variables
for the use of a Function.
"AUX" must appear in the argument LIST after any information about explicit
arguments. It is followed by ATOMs or two-element LISTs as if it were
"OPTIONAL". ATOMs in the two-element LISTs are bound to EVAL of the
second element in the LIST. Atoms not in such LISTs are initially
unassigned: they are explicitly given "no" LVAL.
All binding specified in an argument LIST is done sequentially from first to
last, so initialization expressions for "AUX" (or "OPTIONAL") can refer to
objects which have just been bound. For example, this works:
<DEFINE AUXEX ("TUPLE" T
"AUX" (A <LENGTH .T>) (B <* 2 .A>))
![.A .B]> ⇒ AUXEX
<AUXEX 1 2 "FOO"> ⇒ ![3 6!]
9.4. QUOTEd arguments
If an ATOM in an argument LIST which is to be bound to a required or
optional argument is surrounded by a call to QUOTE, that ATOM is bound to
the unevaluated argument. Example:
<DEFINE Q2 (A 'B) (.A .B)> ⇒ Q2
<Q2 <+ 1 2> <+ 1 2>> ⇒ (3 <+ 1 2>)
A QUOTEd ATOM may also head an "OPTIONAL" 2-list, in which case the
supplied argument is not evaluated but the default is (section 9.9):
<DEFINE Q3 ("OPTIONAL" ('A 9)) .A> ⇒ Q3
<Q3 <+ 1 2>> ⇒ <+ 1 2>
<Q3> ⇒ 9
It is not often appropriate for a function to take its arguments unevaluated,
because such a practice makes it less modular and harder to maintain: it and
the programs that call it tend to need to know more about each other, and a
change in its argument structure would tend to require more changes in the
programs that call it. And, since few functions, in practice, do take
unevaluated arguments, users tend to assume that no functions do (except
FSUBRs of course), and confusion inevitably results.
pymdl's own. There is a harder reason to avoid it than modularity, and chapter 34 is where it bites: a
QUOTEd parameter is the first thing the native compiler refuses.<COMPILE QU>on such aFUNCTIONanswers#FALSE ("a QUOTEd parameter")and leaves theFUNCTIONalone, because compiled code receives evaluated arguments and there is no honest way to hand it an unevaluated one. The era's compiler declined the same shapes; it just said so less clearly.
9.5. "ARGS"
The indicator "ARGS" can appear in an argument LIST with precisely the same
syntax as "TUPLE". However, "ARGS" causes the ATOM following it to be
bound to a LIST of the remaining unevaluated arguments.
"ARGS" does not cause any copying to take place. It simply gives you
<REST application:form fix>
with an appropriate fix. The TYPE change to LIST is a result of the
REST. Since the LIST shares all its elements with the original FORM,
PUTs into the LIST will change the calling program, however dangerous that
may be.
Examples:
<DEFINE QIT (N "ARGS" L) <.N .L>> ⇒ QIT
<QIT 2 <+ 3 4> <LENGTH ,QALL> FOO> ⇒ <LENGTH ,QALL>
The manual writes that call as , which
has one <QIT 2 <+ 3 4 <LENGTH ,QALL> FOO>> too few and one > misplaced; the reconstruction above is the call
that produces the manual's answer. Note what it demonstrates: the FORM
<LENGTH ,QALL> is handed back unevaluated, and QALL need not exist.
<DEFINE FUNCT1 ("ARGS" ARGL-AND-BODY)
<CHTYPE .ARGL-AND-BODY FUNCTION>> ⇒ FUNCT1
<FUNCT1 (A B) <+ .A .B>> ⇒ #FUNCTION ((A B) <+ .A .B>)
The last example is a perfectly valid equivalent of the FSUBR FUNCTION.
Found along the way. That answer was
#FUNCTION <(A B) <+ .A .B>>here until 2026-09-10 -- angle brackets -- and the sentence that finds the bug is in this very section: "TheTYPEchange toLISTis a result of theREST." The object really is aLIST; only the internal representation still remembered being aFORM, and the printer took the brackets from that rather than from theTYPE. Section 6.2's rule is that what follows a#TYPEis the object as itsPRIMTYPE, and<TYPEPRIM FUNCTION>isLIST, so(...)it must be.This is the same defect chapter 10 found at
CHUTYPE, in its other half: chapter 10 fixed the no-prefix path and this fixed the#TYPEpath. The regression net caught a third instance in passing -- an"ARGS"LISTreturned bare had been printing#LIST <...>, which is wrong twice over, since aLISTneeds no prefix at all.
9.6. "CALL"
The indicator "CALL" is an ultimate "ARGS". If it appears in an argument
LIST, it must be followed by an ATOM and must be the only thing used to
gather arguments. "CALL" causes the ATOM which follows it to become bound
to the actual FORM that is being evaluated -- that is, you get the "function
call" itself. Since "CALL" binds to the FORM itself, and not a copy, PUTs
into that FORM will change the calling code.
<DEFINE CALLED ("CALL" C) .C> ⇒ CALLED
<CALLED <+ 1 2> FOO> ⇒ <CALLED <+ 1 2> FOO>
"CALL" exists as a Catch-22 for argument manipulation. If you can't do it
with "CALL", it can't be done.
9.7. EVAL and "BIND"
Obtaining unevaluated arguments, for example, for QUOTE and "ARGS", very
often implies that you wish to evaluate them at some point. You can do this by
explicitly calling EVAL, which is a SUBR. Example:
<SET F '<+ 1 2>> ⇒ <+ 1 2>
<EVAL .F> ⇒ 3
EVAL can take a second argument, of TYPE ENVIRONMENT (or others, see
section 20.8). An ENVIRONMENT consists basically of a state of ATOM
bindings; it is the "world" mentioned in chapter 5. Now, since binding changes
the ENVIRONMENT, if you wish to use EVAL within a FUNCTION, you probably
want to get hold of the environment which existed before that FUNCTION's
binding took place. The indicator "BIND", which must, if it is used, be the
first thing in an argument LIST, provides this information. It binds the
ATOM immediately following it to the ENVIRONMENT existing "at call time" --
that is, just before any binding is done for its FUNCTION. Example:
<SET A 0> ⇒ 0
<DEFINE WRONG ('B "AUX" (A 1)) <EVAL .B>> ⇒ WRONG
<WRONG .A> ⇒ 1
<DEFINE RIGHT ("BIND" E 'B "AUX" (A 1))
<EVAL .B .E>> ⇒ RIGHT
<RIGHT .A> ⇒ 0
WRONG sees its own "AUX" binding of A; RIGHT evaluates .A in the
caller's world and sees the caller's.
9.7.1. Local Values versus ENVIRONMENTs
SET, LVAL, VALUE, BOUND?, ASSIGNED?, and UNASSIGN all take a final
optional argument which has not previously been mentioned: an ENVIRONMENT (or
other TYPEs, see section 20.8). If this argument is given, the SET or
LVAL is done in the ENVIRONMENT specified. LVAL cannot be abbreviated by
. (period) if it is given an explicit second argument.
This feature is just what is needed to cure the INC bug mentioned in chapter
5. A "correct" INC can be defined as follows:
<SET A 0> ⇒ 0
<DEFINE INC ("BIND" OUTER ATM)
<SET .ATM <+ 1 <LVAL .ATM .OUTER>> .OUTER>> ⇒ INC
<INC A> ⇒ 1
.A ⇒ 1
<INC A> ⇒ 2
.A ⇒ 2
Chapter 7's box on SET is the other side of this: the era rejects a
non-ENVIRONMENT in that final position rather than ignoring it, so this
argument cannot be passed by accident.
9.8. ACTIVATION, "NAME", "ACT", AGAIN, and RETURN
EVALuation of a FUNCTION, after the argument LIST has been taken care of,
normally consists of EVALuating each of the objects in the body in the order
given, and returning the value of the last thing EVALed. If you want to vary
this sequence, you need to know, at least, where the FUNCTION begins.
Actually, EVAL normally hasn't the foggiest idea of where its current
FUNCTION began. "Where'd I start" information is bundled up with a TYPE
called ACTIVATION. In "normal" FUNCTION EVALuation, ACTIVATIONs are
not generated: one can be generated, and bound to an ATOM, in either of the
two following ways:
- Put an
ATOMimmediately before the argumentLIST. TheACTIVATIONof the Function will be bound to thatATOM. - As the last thing in the argument
LIST, insert either of theSTRINGs"NAME"or"ACT"and follow it with anATOM. TheATOMwill be bound to theACTIVATIONof the Function.
In this document "Function" (capitalized) will designate anything that can
generate an ACTIVATION; besides TYPE FUNCTION, this class includes the
FSUBRs PROG, BIND, and REPEAT, yet to be discussed.
Each ACTIVATION refers explicitly to a particular evaluation of a Function.
For example, if a recursive FUNCTION generates an ACTIVATION, a new
ACTIVATION referring explicitly to each recursion step is generated on every
recursion.
Like TUPLEs, ACTIVATIONs are held in a control stack. Unlike TUPLEs,
there is no way to get a copy of an ACTIVATION which can usefully be
returned as a value. (This is a consequence of the fact that ACTIVATIONs
refer to evaluations; when the evaluation is finished, the ACTIVATION no
longer exists.) ACTIVATIONs can be tested, like TUPLEs, by LEGAL? for
legality. They are used by the SUBRs AGAIN and RETURN.
AGAIN can take one argument: an ACTIVATION. It means "start doing this
again", where "this" is specified by the ACTIVATION. Specifically, AGAIN
causes EVAL to return to where it started working on the body of the
Function in the evaluation specified by the ACTIVATION. The evaluation is
not redone completely: in particular, no re-binding (of arguments, "AUX"
variables, etc.) is done.
RETURN can take two arguments: an arbitrary expression and an ACTIVATION,
in that order. It causes the Function evaluation whose ACTIVATION it is
given to terminate and return EVAL of RETURN's first argument. That is,
RETURN means "quit doing this and return that", where "this" is the
ACTIVATION -- its second argument -- and "that" is the expression -- its
first argument. Example:
<DEFINE MY+ ("TUPLE" T "AUX" (M 0) "NAME" NM)
<COND (<EMPTY? .T> <RETURN .M .NM>)>
<SET M <+ .M <1 .T>>>
<SET T <REST .T>>
<AGAIN .NM>> ⇒ MY+
<MY+ 1 3 <LENGTH "FOO">> ⇒ 7
<MY+> ⇒ 0
Note that AGAIN does not re-bind: M is not reset to 0 on each pass, which
is exactly what makes the accumulation work.
Note: suppose an ACTIVATION of one Function (call it F1) is passed to
another Function (call it F2) -- for example, via an application of F2
within F1 with F1's ACTIVATION as an argument. If F2 RETURNs to
F1's ACTIVATION, F2 and F1 terminate immediately, and F1
returns the RETURN's first argument. This technique is suitable for error
exits. AGAIN can clearly pull a similar trick. In the following example,
F1 computes the sum of F2 applied to each of its arguments; F2 computes
the product of the elements of its structured argument, but it aborts if it
finds an element that is not a number.
<DEFINE F1 ACT ("TUPLE" T "AUX" (T1 .T))
<COND (<NOT <EMPTY? .T1>>
<PUT .T1 1 <F2 <1 .T1> .ACT>>
<SET T1 <REST .T1>>
<AGAIN .ACT>)
(ELSE <+ !.T>)>> ⇒ F1
<DEFINE F2 (S A "AUX" (S1 .S))
<REPEAT MY-ACT ((PRD 1))
<COND (<NOT <EMPTY? .S1>>
<COND (<NOT <TYPE? <1 .S1> FIX FLOAT>>
<RETURN #FALSE ("NON-NUMBER") .A>)
(ELSE <SET PRD <* .PRD <1 .S1>>>)>
<SET S1 <REST .S1>>)
(ELSE <RETURN .PRD>)>>> ⇒ F2
<F1 '(1 2) '(3 4)> ⇒ 14
<F1 '(T 2) '(3 4)> ⇒ #FALSE ("NON-NUMBER")
The manual writes the inner test as , whose
brackets put <NOT <TYPE? 1 .S1> FIX FLOAT>>FIX FLOAT outside the TYPE?; it is <NOT <TYPE? <1 .S1> FIX FLOAT>>. F1 is the definition-form of rule 1 above, with the ACTIVATION
ATOM written before the argument LIST; F2 gets its own from REPEAT.
9.9. Argument List Summary
The following is a listing of all the various tokens which can appear in the
argument LIST of a FUNCTION, in the order in which they can occur. Short
descriptions of their effects are included. All of them are optional --
that is, any of them (in any position) can be left out or included -- but the
order in which they appear must be that of this list. "QUOTEd ATOM",
"matching object", and "2-list" are defined below.
(1) "BIND"
must be followed by an ATOM. It binds that ATOM to the ENVIRONMENT which
existed when the FUNCTION was applied.
(2) ATOMs and QUOTEd ATOMs (any number)
are required arguments. QUOTEd ATOMs are bound to the matching object.
ATOMs are bound to EVAL of the matching object in the ENVIRONMENT
existing when the FUNCTION was applied.
(3) "OPTIONAL" or "OPT" (they're equivalent)
is followed by any number of ATOMs, QUOTEd ATOMs, or 2-lists. These are
optional arguments. If a matching object exists, an ATOM -- either standing
alone or the first element of a 2-list -- is bound to EVAL of the object,
performed in the ENVIRONMENT existing when the FUNCTION was applied. A
QUOTEd ATOM -- alone or in a 2-list -- is bound to the matching object
itself. If no such object exists, ATOMs and QUOTEd ATOMs are left
unbound, and the first element of each 2-list is bound to EVAL of the
corresponding second element. (This EVAL is done in the new ENVIRONMENT of
the Function as it is being constructed.)
(4) "ARGS" (and not "TUPLE")
must be followed by an ATOM. The ATOM is bound to a LIST of all the
remaining arguments, unevaluated. (If there are no more arguments, the
LIST is empty.) This LIST is actually a REST of the FORM applying the
FUNCTION. If "ARGS" appears in the argument LIST, "TUPLE" should not
appear.
(4) "TUPLE" (and not "ARGS")
must be followed by an ATOM. The ATOM is bound to a TUPLE ("VECTOR on
the control stack") of all the remaining arguments, evaluated in the
environment existing when the FUNCTION was applied. (If no arguments remain,
the TUPLE is empty.) If "TUPLE" appears in the argument LIST, "ARGS"
should not appear.
(5) "AUX" or "EXTRA" (they're equivalent)
is followed by any number of ATOMs or 2-lists. These are auxiliary
variables, bound away from the previous environment for the use of this
Function. ATOMs are bound in the ENVIRONMENT of the Function, but they are
unassigned; the first element of each 2-list is both bound and assigned to
EVAL of the corresponding second element. (This EVAL is done in the new
ENVIRONMENT of the Function as it is being constructed.)
(6) "NAME" or "ACT" (they're equivalent)
must be followed by an ATOM. The ATOM is bound to the ACTIVATION of the
current evaluation of the Function.
ALSO -- in place of sections (2), (3) and (4), you can have
(2-3-4) "CALL"
which must be followed by an ATOM. The ATOM is bound to the FORM which
caused application of this FUNCTION.
The special terms used above mean this:
"QUOTEd ATOM" -- a two-element FORM whose first element is the ATOM
QUOTE, and whose second element is any ATOM. (Can be typed -- and will be
PRINTed -- as 'atom.)
"Matching object" -- that element of a FORM whose position in the FORM
matches the position of a required or optional argument in an argument LIST.
"2-list" -- a two-element LIST whose first element is an ATOM (or QUOTEd
ATOM: see below) and whose second element can be anything but a SEGMENT.
EVAL of the second element is assigned to a new binding of the first element
(the ATOM) as the "value by default" in "OPTIONAL" or the "initial value"
in "AUX". In the case of "OPTIONAL", the first element of a 2-list can be
a QUOTEd ATOM; in this case, an argument which is supplied is not EVALed,
but if it is not supplied the second element of the LIST is EVALed and
assigned to the ATOM.
pymdl's own. This summary is the specification pymdl's binder was written against, token by token, and it is worth saying which line does the most work: the parenthetical "(This
EVALis done in the newENVIRONMENTof the Function as it is being constructed.)" That is whyAUXEXin section 9.3 can computeBfromA-- the environment is half-built while the argumentLISTis being walked, and each default sees everything bound before it. Section 9.9's ordering is not presentation; it is the order of construction.
9.10. APPLY
Occasionally there is a valid reason for the first element of a FORM not to
be an ATOM. For example, the object to be applied to arguments may be chosen
at run time, or it may depend on the arguments in some way. While EVAL is
perfectly happy in this case to EVALuate the first element and go on from
there, the compiler (Lebling, 1979) can generate more efficient code if it
knows whether the result of the evaluation will (1) always be of TYPE FIX,
(2) always be an applicable non-FIX object that evaluates all its arguments,
or (3) neither. The easiest way to tell the compiler if (1) or (2) is true is
to use the ATOM NTH (section 7.1.2) or PUT (section 7.1.4) in case (1) or
APPLY in case (2) as the first element of the FORM. (Note: case (1) can
compile into in-line code, but case (2) compiles into a fully mediated call
into the interpreter.)
<APPLY object arg-1 ... arg-N>
evaluates object and all the arg-i's and then applies the former to all the
latter. An error occurs if object evaluates to something not applicable, or to
an FSUBR, or to a FUNCTION (or user Subroutine -- chapter 22) with "ARGS"
or "CALL" or QUOTEd arguments.
<DEFINE ADD-ALL ("TUPLE" T) <+ !.T>> ⇒ ADD-ALL
<APPLY ,ADD-ALL 1 2 3> ⇒ 6
<APPLY <NTH [,ADD-ALL] 1> 5 6> ⇒ 11
Example:
<APPLY <NTH .ANALYZERS
<LENGTH <MEMQ <TYPE .ARG> .ARGTYPES>>>
.ARG>
calls a function to analyze .ARG. Which function is called depends on the
TYPE of the argument; this represents the idea of a dispatch table.
pymdl's own. "The compiler can generate more efficient code if it knows" is a live concern here rather than a historical one, and chapter 34 is where it lands: a call whose first element is a general expression is what the native compiler must compile to a mediated call, and writing
APPLY(or aFIX) is how the program tells it which of the three cases it is in. This is one of the few places the manual documents a construct whose only purpose is to inform a compiler, and it still serves that purpose.
9.11. CLOSURE
<CLOSURE function a1 ... aN>
where function is a FUNCTION, and a1 through aN are any number of ATOMs,
returns an object of TYPE CLOSURE. This can be applied like any other
function, but, whenever it is applied, the ATOMs given in the call to
CLOSURE are first bound to the VALUEs they had when the CLOSURE was
generated, then the function is applied as normal. This is a "poor man's
funarg".
<SET X 100> ⇒ 100
<DEFINE GET-X () .X> ⇒ GET-X
<SET CL <CLOSURE ,GET-X X>> ⇒ #CLOSURE (#FUNCTION (() .X) ((X 100)))
<SET X 200> ⇒ 200
<GET-X> ⇒ 200
<CL> ⇒ 100
The CLOSURE prints its captured bindings, which is the clearest possible
statement of what it is: the FUNCTION, and a LIST of ATOM/value pairs
remembered from when it was made.
A CLOSURE is useful when a FUNCTION must have state information remembered
between calls to it, especially in these two cases: when the LVALs of
external state ATOMs might be compromised by other programs, or when more
than one distinct sequence of calls are active concurrently. Example of the
latter: each object of a structured NEWTYPE might have an associated
CLOSURE that coughs up one element at a time, with a value in the CLOSURE
that is a structure containing all the relevant information.