* All source files converted to LF Also, HPRINT: EQUALALL knows about CL arrays FILEIO: STREAM record with fields for external format functions * Delete makeinit.dribble * Converted CR to LF on internal/library and docs/Documentation Tools
3722 lines
162 KiB
Plaintext
3722 lines
162 KiB
Plaintext
[This file is an on-line textual version of MIT/AIM-958a. It is not as
|
||
readable as the printed version of the document, but is more convenient in
|
||
some ways. In order to locate the doumentation for a particular function or
|
||
error message, search for two carriage returns followed by the name of the
|
||
function or the number of the error. A printed copy of MIT/AIM-958a can
|
||
easily be obtained from the MIT AI Lab publications office;
|
||
5454 Tech SQ Room 818; Cambridge MA 02139; (617)-253-6773.]
|
||
|
||
|
||
MASSACHUSETTS INSTITUTE OF TECHNOLOGY
|
||
ARTIFICIAL INTELLIGENCE LABORATORY
|
||
|
||
A.I. Memo No. 958a March 1988
|
||
|
||
Obviously Synchronizable Series Expressions:
|
||
Part I: User's Manual for the OSS Macro Package
|
||
|
||
by
|
||
|
||
Richard C. Waters
|
||
|
||
Abstract
|
||
|
||
The benefits of programming in a functional style are well known. In
|
||
particular, algorithms that are expressed as compositions of functions
|
||
operating on series/vectors/streams of data elements are much easier to
|
||
understand and modify than equivalent algorithms expressed as loops.
|
||
Unfortunately, many programmers hesitate to use series expressions, because
|
||
they are typically implemented very inefficiently.
|
||
|
||
A Common Lisp macro package (OSS) has been implemented which supports a
|
||
restricted class of series expressions, obviously synchronizable series
|
||
expressions, which can be evaluated very efficiently by automatically
|
||
converting them into loops. Using this macro package, programmers can obtain
|
||
the advantages of expressing computations as series expressions without
|
||
incurring any run-time overhead.
|
||
|
||
Copyright Massachusetts Institute of Technology, 1988
|
||
|
||
This report describes research done at the Artificial Intelligence Laboratory
|
||
of the Massachusetts Institute of Technology. Support for the laboratory's
|
||
artificial intelligence research has been provided in part by the National
|
||
Science Foundation under grant IRI-8616644, in part by the IBM Corporation, in
|
||
part by the NYNEX Corporation, and in part by the Advanced Research Projects
|
||
Agency of the Department of Defense under Office of Naval Research contract
|
||
N00014-85-K-0124.
|
||
|
||
The views and conclusions contained in this document are those of the authors,
|
||
and should not be interpreted as representing the policies, neither expressed
|
||
nor implied, of the National Science Foundation, of the IBM Corporation, of the
|
||
NYNEX Corporation, or of the Department of Defense.
|
||
|
||
Acknowledgments.
|
||
Both the OSS macro package and this report have benefited from the assistance
|
||
of a number of people. In particular, C. Rich, A. Meyer, Y. Feldman, D.
|
||
Chapman, and P. Anagnostopoulos made suggestions which led to a number of very
|
||
significant improvements in the clarity and power of obviously synchronizable
|
||
series expressions.
|
||
|
||
1. All You Need To Know to Get Started
|
||
|
||
This first section describes everything you need to know to start using the OSS
|
||
macro package. It then presents a detailed example. Section 2 is a
|
||
comprehensive reference manual. It describes the functions supported by the
|
||
OSS macro package in detail. Section 3 contains the bibliography. Section 4
|
||
explains the warning and error messages that can be produced by the OSS macro
|
||
package. Section 5 is both an index into Section 2 and an abbreviated
|
||
description of the OSS functions. [This section is omitted in the on-line
|
||
version. Use searching to find individual function descriptions.]
|
||
|
||
A companion paper [6] gives an overview of the theory underlying the OSS macro
|
||
package. It explains why things are designed the way they are and compares the
|
||
OSS macro package with other systems that support operations on series. In
|
||
addition, the companion paper gives a brief description of the algorithms used
|
||
to implement the OSS macro package. As part of this, it describes a number of
|
||
subprimitive constructs which are provided for advanced users of the OSS macro
|
||
package.
|
||
|
||
The OSS data type.
|
||
A series is an ordered linear sequence of elements. Vectors,
|
||
lists, and streams are examples of series data types. The advantages
|
||
(with respect to conciseness, understandability, and modifiability) of
|
||
expressing algorithms as compositions of functions operating on series,
|
||
rather than as loops, are well known. Unfortunately, as typically
|
||
implemented, series expressions are very inefficient---so inefficient,
|
||
that programmers are forced to use loops whenever efficiency matters.
|
||
|
||
Obviously Synchronizable Series (OSS) is a special series data type that
|
||
can be implemented extremely efficiently by automatically converting OSS
|
||
expressions into loops. This allows programmers to gain the benefit of
|
||
using series expressions without paying any price in efficiency.
|
||
|
||
The OSS macro package adds support for the OSS data type to Common Lisp [4].
|
||
The macro package was originally developed under version 7 of the Symbolics
|
||
Lisp Machine software [7]. However, it is written in standard Common Lisp and
|
||
should be able to run in any implementation of Common Lisp. (It has been
|
||
tested in DEC Common Lisp and Sun Common Lisp as well as Symbolics Common
|
||
Lisp.)
|
||
|
||
The basic functionality provided by the OSS macro package is similar to the
|
||
functionality provided by the Common Lisp sequence functions. However, in
|
||
addition to being much more efficient, the OSS macro package is more powerful
|
||
than the sequence functions, because it includes almost all of the operations
|
||
supported by APL [3] and by the Loop macro [2]. As a result, OSS expressions
|
||
go much farther than the sequence functions towards the goal of eliminating the
|
||
need for explicit loops.
|
||
|
||
Predefined OSS functions.
|
||
The heart of the OSS macro package is a set of several dozen functions which
|
||
operate on OSS series. These functions divide naturally into three classes.
|
||
Enumerators produce series without consuming any. Transducers compute series
|
||
from series. Reducers consume series without producing any. As a mnemonic
|
||
device, the name of each predefined OSS function begins with a letter code that
|
||
indicates the type of operation. These letters are intended to be pronounced
|
||
as separate syllables.
|
||
|
||
Predefined enumerators include Elist which enumerates successive elements of a
|
||
list, Evector which enumerates the elements of a vector, and Eup which
|
||
enumerates the integers in a range. (The notation [...] is used to represent
|
||
an OSS series.)
|
||
|
||
(Elist '(a b c)) => [a b c]
|
||
(Evector '#(a b c)) => [a b c]
|
||
(Eup 1 :to 3) => [1 2 3]
|
||
|
||
Predefined transducers include Tpositions which returns the positions of the
|
||
non-null elements in a series and Tselect which selects the elements of its
|
||
second argument which correspond to non-null elements of its first argument.
|
||
|
||
(Tpositions [a nil b c nil nil]) => [0 2 3]
|
||
(Tselect [nil T T nil] [1 2 3 4]) => [2 3]
|
||
|
||
Predefined reducers include Rlist which combines the elements of a series into
|
||
a list, Rsum which adds up the elements of a series, Rlength which computes the
|
||
length of a series, and Rfirst which returns the first element of a series.
|
||
|
||
(Rlist [a b c]) => (a b c)
|
||
(Rsum [1 2 3]) => 6
|
||
(Rlength [a b c]) => 3
|
||
(Rfirst [a b c]) => a
|
||
|
||
As simple illustrations of how OSS functions are used, consider the following.
|
||
|
||
(Rsum (Evector '#(1 2 3))) => 6
|
||
(Rlist (Tpositions (Elist '(a nil b c nil)))) => (0 2 3)
|
||
|
||
Higher-Order OSS functions.
|
||
The OSS macro package provides a number of higher-order functions which
|
||
support general classes of OSS operations. (Each of these functions end
|
||
in the suffix "F", which is pronounced separately.)
|
||
|
||
For example, enumeration is supported by (EnumerateF init step test). This
|
||
enumerates an OSS series of elements starting with init by repeatedly applying
|
||
step. The OSS series consists of the values up to, but not including, the
|
||
first value for which test is true.
|
||
|
||
Reduction is supported by (ReduceF init function items) which is analogous to
|
||
the sequence function reduce. The elements of the OSS series items are
|
||
combined together using function. The quantity init is used as an initial seed
|
||
value for the accumulation.
|
||
|
||
Mapping is supported by (TmapF function items) which is analogous to the
|
||
sequence function map. A series is computed by applying function to each
|
||
element of items.
|
||
|
||
(EnumerateF 3 #'1- #'minusp) => [3 2 1 0]
|
||
(ReduceF 0 #'+ [1 2 3]) => 6
|
||
(TmapF #'sqrt [4 9 16]) => [2 3 4]
|
||
|
||
Implicit mapping.
|
||
The OSS macro package contains a special mechanism that makes mapping
|
||
particularly easy. Whenever an ordinary Lisp function is applied to
|
||
an OSS series, it is automatically mapped over the elements of the OSS
|
||
series. For example, in the expression below, the function sqrt
|
||
is mapped over the OSS series of numbers created by Evector.
|
||
|
||
(Rsum (sqrt (Evector '#(4 16))))
|
||
== (Rsum (TmapF #'sqrt (Evector '#(4 16)))) => 6
|
||
|
||
To a considerable extent, implicit mapping is a peripheral part of the OSS
|
||
macro package---one can always use TmapF instead. However, due to the
|
||
ubiquitous nature of mapping, implicit mapping is extremely convenient. As
|
||
illustrated below, its key virtue is that it reduces the number of literal
|
||
lambda expressions that have to be written.
|
||
|
||
(Rsum (expt (abs (Evector '#(2 -2 3))) 3))
|
||
== (Rsum (TmapF #'(lambda (x) (expt (abs x) 3))
|
||
(Evector '#(2 -2 3)))) => 43
|
||
|
||
Creating OSS variables.
|
||
The OSS macro package provides two forms (letS and letS*) which are analogous
|
||
to let and let*, except that they make it possible to create variables that can
|
||
hold OSS series. (The suffix "S", pronounced separately, is used to indicate
|
||
primitive OSS forms.) As shown in the example below, letS can be used to bind
|
||
both ordinary variables (e.g., n) and OSS variables (e.g., items).
|
||
|
||
(defun average (v)
|
||
(letS* ((items (Evector v))
|
||
(sum (Rsum items))
|
||
(n (Rlength items)))
|
||
(/ sum n)))
|
||
|
||
(average '#(1 2 3)) => 2
|
||
|
||
User-defined OSS functions.
|
||
New OSS functions can be defined by using the form defunS which is analogous to
|
||
defun. Explicit declarations are required inside defunS to indicate which
|
||
arguments receive OSS series. The following example shows the definition of an
|
||
OSS function which computes the product of the numbers in an OSS series.
|
||
|
||
(defunS Rproduct (numbers)
|
||
(declare (type oss numbers))
|
||
(ReduceF 1 #'* numbers))
|
||
|
||
(Rproduct [2 4 6]) => 48
|
||
|
||
Restrictions on OSS expressions.
|
||
As illustrated by the examples above, OSS expressions are constructed
|
||
in the same way as any other Lisp expression---i.e., OSS functions are
|
||
composed together in any way desired. However, in order to guarantee
|
||
that OSS expressions can always be converted into highly efficient
|
||
loops, a few restrictions have to be followed. These restrictions are
|
||
summarized in the beginning of Section 2 and discussed
|
||
in detail in [6].
|
||
|
||
Here, it is sufficient to note that the OSS macro package is designed so that
|
||
it is impossible to violate most of the restrictions. The remaining
|
||
restrictions are checked by the macro package and any violations are
|
||
automatically fixed. However, warning messages are issued whenever a violation
|
||
is detected, because, as discussed in the beginning of Section 2, it is often
|
||
possible for the user to fix a violation in a way which is much more efficient
|
||
than the automatic fix supplied by the macro package.
|
||
|
||
The best approach for programmers to take is to simply write OSS expressions
|
||
without worrying about the restrictions. In this regard, it should be noted
|
||
that simple OSS expressions are very unlikely to violate any of the
|
||
restrictions. In particular, it is impossible for an OSS expression to violate
|
||
any of the restrictions unless it contains a variable bound by letS or defunS.
|
||
When violations do occur, they can either be ignored (since they cannot lead to
|
||
incorrect results) or dealt with on an individual basis (which is advisable
|
||
since violations can lead to significant inefficiencies).
|
||
|
||
Benefits.
|
||
The benefit of OSS expressions is that they retain most of the advantages of
|
||
functional programming using series, while eliminating the costs. However,
|
||
given the restrictions alluded to above, the question naturally arises as to
|
||
whether OSS expressions are applicable in a wide enough range of situations to
|
||
be of real pragmatic benefit.
|
||
|
||
An informal study [5] was undertaken of the kinds of loops programmers actually
|
||
write. This study suggests that approximately 80% of the loops programmers
|
||
write are constructed by combining a few common kinds of looping algorithms.
|
||
The OSS macro package is designed so that all of these algorithms can be
|
||
represented as OSS functions. As a result, it appears that approximately 80%
|
||
of loops can be trivially rewritten as OSS expressions. Many more can be
|
||
converted to this form with only minor modification.
|
||
|
||
Moreover, the benefits of using OSS expressions go beyond replacing individual
|
||
loops. A major shift toward using OSS expressions would be a significant
|
||
change in the way programming is done. At the current time, most programs
|
||
contain one or more loops and most of the interesting computation in these
|
||
programs occurs in these loops. This is quite unfortunate, since loops are
|
||
generally acknowledged to be one of the hardest things to understand in any
|
||
program. If OSS expressions were used whenever possible, most programs would
|
||
not contain any loops. This would be a major step forward in conciseness,
|
||
readability, verifiability, and maintainability.
|
||
|
||
Example
|
||
|
||
The following example shows what it is like to use OSS expressions in a
|
||
realistic programming context. The example consists of two parts: a pair of
|
||
functions which convert between sets represented as lists and sets represented
|
||
as bits packed into an integer and a graph algorithm which uses the integer
|
||
representation of sets.
|
||
|
||
Bit sets.
|
||
Small sets can be represented very efficiently as binary integers where each 1
|
||
bit in the integer represents an element in the set. Below, sets represented
|
||
in this fashion are referred to as bit sets.
|
||
|
||
Common Lisp provides a number of bitwise operations on integers which can be
|
||
used to manipulate bit sets. In particular, logior computes the union of two
|
||
bit sets while logand computes their intersection.
|
||
|
||
The functions in Figure 1.1 convert between sets represented as lists and bit
|
||
sets. In order to perform this conversion, a mapping has to be established
|
||
between bit positions and potential set elements. This mapping is specified by
|
||
a universe. A universe is a list of elements. If a bit set b is associated
|
||
with a universe u, then the ith element in u is in the set represented by b iff
|
||
the ith bit in b is 1.
|
||
|
||
For example, given the universe (a b c d e), the integer #b01011 represents the
|
||
set {a,b,d}. (By Common Lisp convention, the 0th bit in an integer is the
|
||
rightmost bit.)
|
||
|
||
Given a bit set and its associated universe, the function bset->list converts
|
||
the bit set into a set represented as a list of its elements. It does this by
|
||
enumerating the elements in the universe along with their positions and
|
||
constructing a list of the elements which correspond to 1s in the integer
|
||
representing the bit set. (When no :to argument is supplied, Eup counts up
|
||
forever.)
|
||
|
||
The function list->bset converts a set represented as a list of its elements
|
||
into a bit set. Its second argument is the universe which is to be associated
|
||
with the bit set created. For each element of the list, the function
|
||
bit-position is called in order to determine which bit position should be set
|
||
to 1. The function ash is used to create an integer with the correct bit set
|
||
to 1. The function ReduceF is used to combine the integers corresponding to
|
||
the individual elements together into a bit set corresponding to the list.
|
||
|
||
The function bit-position takes an item and a universe and returns the bit
|
||
position corresponding to the item. The function operates in one of two ways
|
||
depending on whether or not the item is in the universe. The first line of the
|
||
function contains an OSS expression which determines the position of the item
|
||
in the universe. If the item is not in the universe, the expression returns
|
||
nil. (The function Rfirst returns nil if it is passed a series of length
|
||
zero.)
|
||
|
||
If the item is not in the universe, the second line of the function adds the
|
||
item onto the end of the universe and returns its position. The extension of
|
||
the universe is done be side-effect so that it will be permanently recorded in
|
||
the universe.
|
||
|
||
(defun bset->list (bset universe)
|
||
(Rlist (Tselect (logbitp (Eup 0) bset) (Elist universe))))
|
||
|
||
(defun list->bset (list universe)
|
||
(ReduceF 0 #'logior (ash 1 (bit-position (Elist list) universe))))
|
||
|
||
(defun bit-position (item universe)
|
||
(or (Rfirst (Tpositions (eq item (Elist universe))))
|
||
(1- (length (nconc universe (list item))))))
|
||
|
||
(Figure 1.1: Converting between lists and bit sets.)
|
||
|
||
Figure 1.2 shows the definition of two OSS reducers which operate on OSS series
|
||
of bit sets. The first function computes the union of a series of bit sets,
|
||
while the second computes their intersection.
|
||
|
||
(defunS Rlogior (bsets)
|
||
(declare (type oss bsets))
|
||
(ReduceF 0 #'logior bsets))
|
||
|
||
(defunS Rlogand (bsets)
|
||
(declare (type oss bsets))
|
||
(ReduceF -1 #'logand bsets))
|
||
|
||
(Figure 1.2: Operations on OSS series of bit sets.)
|
||
|
||
Live variable analysis.
|
||
As an illustration of the way bit sets might be used, consider the following.
|
||
Suppose that in a compiler, program code is being represented as blocks of
|
||
straight-line code connected by possibly cyclic control flow. The top part of
|
||
Figure 1.3 shows the data structure which represents a block of code. Each
|
||
block has several pieces of information associated with it. Two of these
|
||
pieces of information are the blocks that can branch to the block in question
|
||
and the blocks it can branch to. A program is represented as a list of blocks
|
||
that point to each other through these fields.
|
||
|
||
In addition to control flow information, each structure contains information
|
||
about the way variables are accessed. In particular, it records the variables
|
||
that are written by the block and the variables that are used by the block
|
||
(i.e., either read without being written or read before they are written). An
|
||
additional field (computed by the function determine-live discussed below)
|
||
records the variables which are live at the end of the block. (A variable is
|
||
live if it has to be saved, because it can potentially be used by a following
|
||
block.) Finally, there is a temporary data field which is used by functions
|
||
(such as determine-live) which perform computations involved with the blocks.
|
||
|
||
The remainder of Figure 1.3 shows the function determine-live which, given a
|
||
program represented as a list of blocks, determines the variables which are
|
||
live in each block. To perform this computation efficiently, the function uses
|
||
bit sets. The function operates in three steps. The first step
|
||
(convert-to-bsets) looks at each block and sets up an auxiliary data structure
|
||
containing bit set representations for the written variables, the used
|
||
variables, and an initial guess that there are no live variables. This
|
||
auxiliary structure is defined by the third form in Figure 1.3 and is stored in
|
||
the temp field of the block. The integer 0 represents an empty bit set.
|
||
|
||
The second step (perform-relaxation) determines which variables are live. This
|
||
is done by relaxation. The initial guess that there are no live variables in
|
||
any block is successively improved until the correct answer is obtained.
|
||
|
||
The third step (convert-from-bsets) operates in the reverse of the first step.
|
||
Each block is inspected and the bit set representation of the live variables is
|
||
converted into a list which is stored in the live field of the block.
|
||
|
||
(defstruct (block (:conc-name nil))
|
||
predecessors ;Blocks that can branch to this one.
|
||
successors ;Blocks this one can branch to.
|
||
written ;Variables written in the block.
|
||
used ;Variables read before written in the block.
|
||
live ;Variables that must be available at exit.
|
||
temp) ;Temporary storage location.
|
||
|
||
(defun determine-live (program-graph)
|
||
(let ((universe (list nil)))
|
||
(convert-to-bsets program-graph universe)
|
||
(perform-relaxation program-graph)
|
||
(convert-from-bsets program-graph universe))
|
||
program-graph)
|
||
|
||
(defstruct (temp-bsets (:conc-name bset-))
|
||
used written live)
|
||
|
||
(defun convert-to-bsets (program-graph universe)
|
||
(letS ((block (Elist program-graph)))
|
||
(setf (temp block)
|
||
(make-temp-bsets
|
||
:used (list->bset (used block) universe)
|
||
:written (list->bset (written block) universe)
|
||
:live 0))))
|
||
|
||
(defun perform-relaxation (program-graph)
|
||
(let ((to-do program-graph))
|
||
(loop
|
||
(when (null to-do) (return (values)))
|
||
(let* ((block (pop to-do))
|
||
(estimate (live-estimate block)))
|
||
(when (not (= estimate (bset-live (temp block))))
|
||
(setf (bset-live (temp block)) estimate)
|
||
(letS ((prev (Elist (predecessors block))))
|
||
(pushnew prev to-do)))))))
|
||
|
||
(defun live-estimate (block)
|
||
(letS ((next (temp (Elist (successors block)))))
|
||
(Rlogior (logior (bset-used next)
|
||
(logandc2 (bset-live next)
|
||
(bset-written next))))))
|
||
|
||
(defun convert-from-bsets (program-graph universe)
|
||
(letS ((block (Elist program-graph)))
|
||
(setf (live block)
|
||
(bset->list (bset-live (temp block)) universe))
|
||
(setf (temp block) nil)))
|
||
|
||
(Figure 1.3: Live variable analysis.)
|
||
|
||
On each cycle of the loop in perform-relaxation, a block is
|
||
examined to determine whether its live set has to be changed. To do
|
||
this (see the function live-estimate), the successors of the
|
||
block are inspected. Each successor needs to have available to it the
|
||
variables it uses, plus the variables that are supposed to be live
|
||
after it, minus the variables it writes. (The function logandc2
|
||
takes the difference of two bit sets.) A new estimate of the total
|
||
set of variables needed by the successors as a group is computed by
|
||
using Rlogior.
|
||
|
||
If this new estimate is different from the current estimate of what
|
||
variables are live, then the estimate is changed. In addition, if the
|
||
estimate is changed, perform-relaxation has to make sure that
|
||
all of the predecessors of the current block will be examined to see
|
||
if the new estimate for the current block requires their live
|
||
estimates to be changed. This is done by adding each predecessor onto
|
||
the list to-do unless it is already there. As soon as the
|
||
estimates of liveness stop changing, the computation can stop.
|
||
|
||
Summary.
|
||
The function determine-live is a particularly good example of the way OSS
|
||
expressions are intended to be used in two ways. First, OSS expressions are
|
||
used in a number of places to express computations which would otherwise be
|
||
expressed less clearly as loops or less efficiently as sequence function
|
||
expressions. Second, the main relaxation algorithm is expressed as a loop.
|
||
This is done, because neither OSS expressions (nor Common Lisp sequence
|
||
function expressions) lend themselves to expressing the relaxation algorithm.
|
||
This highlights the fact that OSS expressions are not intended to render loops
|
||
entirely obsolete, but rather to provide a greatly improved method for
|
||
expressing the vast majority of loops.
|
||
|
||
2. Reference Manual
|
||
|
||
This section is organized around descriptions of the various functions and
|
||
forms supported by the OSS macro package. Each description begins with a
|
||
header which shows the arguments and results of the function or form being
|
||
described. For ease of reference, the headers are duplicated in Section 5. In
|
||
Section 5, the headers are in alphabetical order and show the page where the
|
||
function or form is described.
|
||
|
||
In a reference manual like this one, it is advantageous to describe each
|
||
construct separately and completely. However, this inevitably leads to
|
||
presentation problems, because everything is related to everything
|
||
else. Therefore, one cannot avoid referring to things which have not
|
||
been discussed. The reader is encouraged to skip around in the
|
||
document and to realize that more than one reading will probably be
|
||
necessary in order to gain a complete understanding of the OSS macro
|
||
package.
|
||
|
||
Although the following list of OSS functions is large, it should not be
|
||
taken as complete. Every effort has been made to provide a wide range of
|
||
useful predefined functions. However, except for a few primitive forms,
|
||
all of these functions could have been defined by the user. It is hoped
|
||
that users will write many more such functions. A key reason for
|
||
presenting a wide array of predefined functions is to inspire users with
|
||
thoughts of the wide variety of functions they can write for themselves.
|
||
|
||
Restrictions and Definitions of Terms.
|
||
|
||
As alluded to in Section 1, there are a number of restrictions which OSS
|
||
expressions have to obey. The OSS macro package is designed so that all but
|
||
three of these restrictions are impossible to violate with the facilities
|
||
provided. As a result, the programmer need not think about these restrictions
|
||
at all.
|
||
|
||
The OSS macro package checks to see that the remaining three
|
||
restrictions are obeyed on an expression by expression basis and
|
||
automatically fixes any violations which are detected. However, the
|
||
automatic fixes are often not very efficient. As a result, it is
|
||
advisable for the user to fix such violations explicitly.
|
||
|
||
Given that simple OSS expressions are very unlikely to violate any of
|
||
the restrictions, and any violations which do occur are automatically
|
||
fixed, it is reasonable for the reader to skip this section when first
|
||
reading this manual. However, it is useful to read this section
|
||
before trying to write complex OSS expressions.
|
||
|
||
The discussion below starts by defining two key terms (on-line
|
||
functions and early termination) which are used to categorize the OSS
|
||
functions described in the rest of this manual. The discussion then
|
||
continues by briefly describing the three restrictions which can be
|
||
violated. (See [6] for a complete discussion of all the
|
||
restrictions.)
|
||
|
||
On-line and off-line.
|
||
Suppose that f is an OSS function which reads one or more series inputs and
|
||
writes one or more series outputs. The function f is on-line [1] if it
|
||
operates in the following fashion. First, f reads in the first element of each
|
||
input series, then it writes out the first element of each output series, then
|
||
it reads in the second element of each input series, then it writes out the
|
||
second element of each output series, and so on. In addition, f must
|
||
immediately terminate as soon as any input runs out of elements. If an f is
|
||
not on-line, then it is off-line.
|
||
|
||
In the context of OSS expressions, the term on-line is generalized so
|
||
that it applies to individual OSS input and output ports in addition to
|
||
whole functions. An OSS port is on-line iff the processing at that
|
||
port always follows the rigidly synchronized pattern described above.
|
||
Otherwise, it is off-line. From this point of view, a function is
|
||
on-line iff all of its OSS ports are on-line.
|
||
|
||
The prototypical example of an on-line OSS function is TmapF
|
||
(which maps a function over a series). Each time it reads an input
|
||
element it applies the mapped function to it and writes an output
|
||
element. In contrast, the function Tremove-duplicates (which
|
||
removes the duplicate elements from a series) is not on-line. Since
|
||
some of the input elements do not become output elements, it is not
|
||
possible for Tremove-duplicates to write an output element every
|
||
time it reads an input element.
|
||
|
||
For every OSS function, the documentation below specifies which ports
|
||
are on-line and which are off-line. In this regard, it is interesting
|
||
to note that every function which has only one OSS port (e.g.,
|
||
enumerators with only one output and reducers with only one input) are
|
||
trivially on-line. The only OSS functions which have off-line ports
|
||
are transducers.
|
||
|
||
Early termination.
|
||
An important feature of OSS functions is the situations under which
|
||
they terminate. The definition of on-line above requires that on-line
|
||
functions must terminate as soon as any series input runs out of
|
||
elements. If an OSS function can terminate before any of its inputs
|
||
are exhausted, then it is an early terminator. The degenerate
|
||
case of functions which do not have any series inputs (i.e.,
|
||
enumerators) is categorized by saying that enumerators are early
|
||
terminators iff they can terminate.
|
||
|
||
As an example of an early terminator, consider the function TuntilF (which
|
||
reads a series and returns all of the elements of that series up to, but not
|
||
including, the first element which satisfies a given predicate). This function
|
||
is an early terminator, because it can terminate before the input runs out of
|
||
elements.
|
||
|
||
The documentation below specifies which functions are early terminators.
|
||
Besides enumerators, their are only 7 OSS functions which are early
|
||
terminators.
|
||
|
||
Isolation. A data flow arc delta in an OSS expression X is isolated iff it is
|
||
possible to partition the functions in X into two parts Y and Y' in such a way
|
||
that: delta goes from Y to Y', there is no OSS data flow from Y to Y', and
|
||
there is no data flow from Y' to Y. For example, consider the OSS expression
|
||
(letS ((x (f y))) (i (h x (g x)))) which corresponds to the graph in Figure
|
||
2.1. [Not shown in this textual version of the memo. See the printed
|
||
version.]
|
||
|
||
The data flow arc delta4 is isolated. To show this, one merely has
|
||
to partition the expression so that f, g, and h are
|
||
on one side and i is on the other. The question of whether or not
|
||
the other data flow arcs are isolated is more complicated to answer.
|
||
If delta3 crosses a partition, then delta1 must cross this
|
||
partition as well. As a result, delta3 is isolated iff delta1
|
||
carries a non-OSS value. (This is true no matter what kind of value
|
||
passes over delta3 itself.) In a related situation, delta2 is
|
||
isolated iff (it and therefore delta1) carries a non-OSS value.
|
||
Finally, consider the arc delta1. Here there are two potential
|
||
partitions to consider: one which cuts delta2 and one which cuts
|
||
delta3. The data flow arc delta1 is isolated iff either it (and
|
||
therefore delta2) or delta3 carries a non-OSS value.
|
||
|
||
The concept of isolation is extended to inputs and outputs as follows.
|
||
An output p in an expression X is isolated iff X can be
|
||
partitioned into two parts Y and Y' such that: every data flow
|
||
originating on p goes from Y to Y', every other data flow
|
||
from Y to Y' is a non-OSS data flow, and there is no data
|
||
flow from Y' to Yf. An input q in an expression X is
|
||
isolated iff X can be partitioned into two parts Y and Y'
|
||
such that: the data flow terminating on q goes from Y to Y',
|
||
every other data flow from Y to Y' is a non-OSS data flow,
|
||
and there is no data flow from Y' to Y.
|
||
|
||
For example, in Figure 2.1, the outputs of f and h are isolated as is the input
|
||
of i. The input and output of g are isolated iff f computes a non-OSS value.
|
||
The inputs of h are isolated iff the data flow arcs terminating on them are
|
||
isolated.
|
||
|
||
Non-OSS data flows must be isolated.
|
||
In order for an OSS expression to be reliably converted into a highly
|
||
efficient loop, every non-OSS data flow in it must be isolated. As an
|
||
example of an expression where this is not true, consider the
|
||
following. In this expression, the data flow implemented by the
|
||
variable total is not isolated.
|
||
|
||
(letS* ((nums (Evector '#(3 2 8))) ;Signals warning 16
|
||
(total (ReduceF 0 #'+ nums)))
|
||
(Rvector (/ nums total))) => #(3/13 2/13 8/13)
|
||
|
||
(The basic problem here is that while the elements created by Evector are being
|
||
used to compute total, they all have to be saved so that they can be used again
|
||
later in order to perform the indicated divisions. Eliminating the need for
|
||
such storage is the key source of efficiency underlying OSS expressions.)
|
||
|
||
Off-line OSS ports must be isolated.
|
||
In order for an OSS expression to be reliably converted into a highly
|
||
efficient loop, every off-line port must be isolated. As an example
|
||
of an expression which has an off-line output which is not isolated,
|
||
consider the following. In this expression, the data flow implemented
|
||
by the variable positions is not isolated.
|
||
|
||
(letS* ((keys (Elist list)) ;Signals warning 17.1
|
||
(positions (Tpositions keys)))
|
||
(Rlist (list positions keys)))
|
||
|
||
(The basic problem here is that since Tpositions skips null
|
||
elements of the input, Tpositions sometimes has to read several
|
||
input elements before it can produce the next output element. This
|
||
forces an unpredictable number of elements of keys to be saved
|
||
so that they can be used later when creating lists. As above,
|
||
eliminating the need for such storage is the main goal of OSS
|
||
expressions.)
|
||
|
||
Code copying.
|
||
If an OSS expression violates either of the above restrictions, the OSS
|
||
macro packaged automatically fixes the problem by copying code until
|
||
the data flow or port in question becomes isolated. For instance, the
|
||
example above of an OSS expression in which a non-OSS data flow is not
|
||
isolated is fixed as follows.
|
||
|
||
(letS* ((nums (Evector '#(3 2 8)))
|
||
(total (ReduceF 0 #'+ (Evector '#(3 2 8)))))
|
||
(Rvector (/ nums total))) => #(3/13 2/13 8/13)
|
||
|
||
Even though the problem has been automatically fixed, the OSS macro
|
||
package issues a warning message. This is done for two reasons.
|
||
First, if side-effects (e.g., input or output) are involved, the code
|
||
copying that was performed may not be correctness preserving. Second,
|
||
large amounts of code sometimes have to be copied and that can
|
||
introduce large amounts of extra computation.
|
||
|
||
A major goal of OSS expressions is ensuring that expressions which
|
||
look simple to compute actually are simple to compute. Automatically
|
||
introducing large amounts of additional computation without the
|
||
programmer's knowledge would violate this goal. At the very least,
|
||
issuing warning messages makes programmers aware of what is expensive
|
||
to compute and what is not. Looked at from a more positive
|
||
perspective, it encourages them to think of ways to compute what they
|
||
want without code copying being required.
|
||
|
||
For instance, consider the example above of an OSS expression in
|
||
which an off-line port is not isolated. It might be the case that
|
||
the programmer knows that list does not contain any null
|
||
elements and that Tpositions is therefore merely being used to
|
||
enumerate what the positions of the elements are. In this situation,
|
||
the expression can be fixed as follows, which does not require any
|
||
code copying. (The key insight here is that the positions do not
|
||
actually depend on the values in the list.)
|
||
|
||
(let ((list '(a b c)))
|
||
(letS* ((keys (Elist list))
|
||
(positions (Eup 0)))
|
||
(Rlist (list positions keys)))) => ((0 a) (1 b) (2 c))
|
||
|
||
(It is interesting to note that if an expression is a tree (as opposed
|
||
to a graph as in Figure 2.1), then every data flow arc and every
|
||
port is isolated. This is why OSS expressions which do not contain
|
||
variables bound by letS, lambdaS, or defunS cannot
|
||
violated either of the isolation restrictions. This is also why code
|
||
copying can always fix any violation---code copying can convert any
|
||
graph into a tree.)
|
||
|
||
On-line subexpressions.
|
||
The two isolation restrictions above permit a divide and conquer
|
||
approach to the processing of OSS expressions. If an OSS expression
|
||
obeys the isolation restrictions, then it can be repeatedly
|
||
partitioned until all of the data flow in each subexpression goes from
|
||
an on-line output to an on-line input. The subexpressions which
|
||
remain after partitioning are referred to as on-line
|
||
subexpressions.
|
||
|
||
Termination points.
|
||
The functions in each on-line subexpression can be divided into two
|
||
classes: those which are termination points and those which are not. A
|
||
function is a termination point if it can terminate before any other
|
||
function in the subexpression terminates. There are two reasons for
|
||
functions being termination points. Functions which are early
|
||
terminators are always termination points. In addition, any function
|
||
which reads an OSS series which comes from a different on-line
|
||
subexpression is a termination point.
|
||
|
||
Data flow paths between termination points and outputs.
|
||
In order for an OSS expression to be reliably converted into a highly
|
||
efficient loop, it must be the case that within each on-line
|
||
subexpression, there is a data flow path from each termination point
|
||
to each output. As an example of an OSS expression for which this
|
||
property does not hold, consider the following. Partitioning divides
|
||
this expression into two on-line subexpressions, one containing
|
||
list and one containing everything else. In the large on-line
|
||
subexpression, the two instances of Evector are termination
|
||
points. The program violates the property above, because there is no
|
||
data flow path from the termination point
|
||
(Evector weight-vector) to the output of (Rvector squares).
|
||
|
||
(defun weighted-squares (value-vector weight-vector)
|
||
(letS* ((values (Evector value-vector)) ;Signals warning 18
|
||
(weights (Evector weight-vector))
|
||
(squares (* values values))
|
||
(weighted-squares (* squares weights)))
|
||
(list (Rvector squares) (Rvector weighted-squares))))
|
||
|
||
(weighted-squares #(1 2 3) #(2 3 4)) => (#(1 4 9) #(2 12 36))
|
||
(weighted-squares #(1 2) #(2 3 4)) => (#(1 4) #(2 12))
|
||
(weighted-squares #(1 2 3) #(2 3)) => (#(1 4 9) #(2 12))
|
||
|
||
(The basic problem here is that if the number of elements in
|
||
value-vector is greater than the number of elements in
|
||
weight-vector, the computation of squares has to continue even
|
||
after the computation of weighted-squares has been completed.
|
||
This kind of partial continuing evaluation in a single on-line
|
||
subexpression is not supported by the OSS macro package, because it was
|
||
judged that it requires too much overhead in order to control what
|
||
gets evaluated when.)
|
||
|
||
When an OSS expression violates the restriction above, the violation is
|
||
automatically fixed by applying code copying. It is impossible for an
|
||
on-line subexpression to violate the restriction unless it computes
|
||
two different outputs. Code copying can always be used to break the
|
||
subexpression in question into two parts each of which computes one of
|
||
the outputs. Unfortunately, this can require a great deal of code to
|
||
be copied. There are two basic approaches which can be used to fix a
|
||
violation much more efficiently: reducing the number of termination
|
||
points and increasing the connectivity between termination points and
|
||
outputs.
|
||
|
||
The easiest way to decrease the number of termination points is to replace
|
||
early terminators by equivalent operations which are not early terminators. If
|
||
an early terminator is not an enumerator, then this can always be done without
|
||
difficultly. (The documentation below describes a non-early variant for each
|
||
early terminating transducer and reducer.) If multiple enumerators are the
|
||
problem (as in the example above) decreasing the number of termination points
|
||
is usually not practical. However, sometimes an enumerator which terminates
|
||
can be replaced by an enumerator which never terminates.
|
||
|
||
The connectivity between termination points and outputs can be increased by
|
||
using the function Tcotruncate. This is the preferred way to fix the problem
|
||
in the example above.
|
||
|
||
General Information
|
||
|
||
Before discussing the individual OSS functions in detail, a few general
|
||
comments are in order. First, all of the OSS functions and forms are defined
|
||
in the package OSS. To make these names easily accessible, use the package OSS
|
||
(i.e., evaluate (use-package "OSS")). If this is not done, the names will have
|
||
to be prefixed with "oss:" when they are used.
|
||
|
||
Naming conventions.
|
||
The names of the various OSS functions and forms follow a strict naming
|
||
convention. The first letter of an OSS function name indicates
|
||
the type of function as shown below. The letter codes are written in upper
|
||
case in this document (case does not matter to Common Lisp) and each letter
|
||
is intended to be pronounced as a separate syllable.
|
||
|
||
E Enumerator.
|
||
T Transducer.
|
||
R Reducer.
|
||
|
||
The last letter of each OSS special form is "S". In general,
|
||
this indicates that the form is primitive in the sense that it could
|
||
not be defined by the user. Some OSS functions end in the letter
|
||
"F". This is used to indicate that the function is a
|
||
higher-order function which takes functions as arguments.
|
||
|
||
The naming convention has two advantages: one trivial but vital and
|
||
the other more fundamentally useful. First, many of the OSS functions
|
||
are very similar to standard Common Lisp sequence functions. As a
|
||
result, it makes sense to give them similar names. However, it is not
|
||
possible to give them exactly the same names without redefining the
|
||
standard functions. The naming convention allows the names to be
|
||
closely related in a predictable way without making the names
|
||
unreasonably long.
|
||
|
||
Second, the naming convention highlights several properties of OSS
|
||
functions which make it easier to read and understand OSS expressions.
|
||
In particular, the prefixes highlight the places where series are
|
||
created and consumed.
|
||
|
||
The names of arguments and results of OSS functions are also chosen following
|
||
naming conventions. First, all of the names are chosen in an attempt to
|
||
indicate type restrictions (e.g., number indicates that an argument must be a
|
||
number; item indicates that there is no type restriction). Plural names are
|
||
used iff the value in question is an OSS series (e.g., numbers indicates an OSS
|
||
series of numbers; items indicates an OSS series of unrestricted values). The
|
||
name of a series input or output begins with "O" iff it is off-line.
|
||
|
||
OSS series. Two general points about OSS series are
|
||
worthy of note. First, like Common Lisp sequences, OSS series use
|
||
zero-based indexing (i.e., the first element is the 0th element).
|
||
Second, unlike Common Lisp sequences, OSS series can be unbounded in
|
||
length.
|
||
|
||
Tutorial mode.
|
||
A prominent feature of the various descriptions is that they contain
|
||
many examples. These examples contain large numbers of OSS series as
|
||
inputs and outputs. In the interest of brevity, the notation
|
||
[...] is used to indicate a literal OSS series. If the last entry
|
||
in a literal OSS series is an ellipsis, this indicates that the OSS
|
||
series is unbounded in length.
|
||
|
||
[1 2 3]
|
||
[a b (c d)]
|
||
[T nil T nil ...]
|
||
|
||
The notation [...] is not supported by the OSS macro package.
|
||
It would be straightforward to do so by using
|
||
set-macro-character. Perhaps even better, one could use
|
||
set-dispatch-macro-character to support a notation #[...]
|
||
analogous to #(...). However, although literal series are
|
||
very useful in the examples below, experience suggests that literal
|
||
series are seldom useful when writing actual programs. Inasmuch as
|
||
this is the case, it was decided that it was unwise to use up one of
|
||
the small set of characters which are available for user-defined
|
||
reader macros or user-defined # dispatch characters.
|
||
|
||
Many of the examples show OSS expressions returning OSS series as their
|
||
values. However, one should not take this literally. If these examples
|
||
are typed to Common Lisp as isolated expressions, they will not return any
|
||
values. This is so, because the OSS macro package does not allow complete
|
||
OSS expressions to return OSS series. The examples are intended to show what
|
||
would be returned if the example expressions were nested in larger
|
||
expressions.
|
||
|
||
oss-tutorial-mode &optional (T-or-nil T) => state-of-tutorial-mode
|
||
|
||
The above not withstanding, the OSS macro package provides a special
|
||
tutorial mode in which the notation [...] is supported and
|
||
OSS expressions can return (potentially unbounded) OSS values. However,
|
||
these values still cannot be stored in ordinary variables. This mode is
|
||
entered by calling the function oss-tutorial-mode with an argument of
|
||
T. Calling the function with an argument of nil turns tutorial
|
||
mode off.
|
||
|
||
Using tutorial mode, it is possible to directly duplicate the examples
|
||
shown below. However, tutorial mode is very inefficient. What is
|
||
worse, tutorial mode introduces non-correctness-preserving
|
||
changes in OSS expressions. (For example, in order to correctly
|
||
duplicate the examples that illustrate error messages about
|
||
non-terminating expressions and the fact that OSS series are not actually
|
||
returned by complete OSS expressions, tutorial mode must be turned
|
||
off.) All in all, it is important that tutorial mode not be used as
|
||
anything other than an educational aid.
|
||
|
||
OSS functions are actually macros.
|
||
Every OSS function is actually a macro. As a result, OSS functions cannot be
|
||
funcall'ed, or apply'ed. When the user defines new OSS
|
||
functions, they must be defined before the first time they are used. Also,
|
||
when an OSS function takes keyword arguments, the keywords must be literals.
|
||
They cannot be expressions which evaluate to keywords at run time.
|
||
|
||
Finally, the macro expansion processing associated with OSS expressions
|
||
is relatively time consuming. In order to avoid this overhead during
|
||
the running of a user program, it is important that programs
|
||
containing OSS expressions be compiled rather than run interpretively.
|
||
|
||
A minor advantage of the fact that everything in the OSS macro package
|
||
is a macro is that once a program which uses the macro package is
|
||
compiled, the compiled program can subsequently be run without having
|
||
to load the OSS macro package.
|
||
|
||
A more important advantage of the fact that everything in the OSS macro
|
||
package is a macro is that quoted macro names can be used as
|
||
functional arguments to higher-order OSS functions. (In contrast,
|
||
quoted macro names cannot be used as functional arguments to
|
||
higher-order Common Lisp functions such as reduce.) Although
|
||
this may appear to be a minor benefit, it is actually quite useful.
|
||
|
||
Enumerators
|
||
|
||
Enumerators create OSS outputs based on non-OSS inputs. There are two
|
||
basic kinds of enumerators: ones that create an OSS series based on some
|
||
formula (e.g., enumerating a sequence of integers) and ones that create
|
||
an OSS series containing the elements of an aggregate data structure
|
||
(e.g., enumerating the elements of a list). All the predefined
|
||
enumerators are on-line. In general, they are all early terminators.
|
||
However, as noted below, in some situations, some enumerators produce
|
||
unbounded outputs and are not early terminators.
|
||
|
||
Eoss &rest expr-list => items
|
||
|
||
The expr-list consists of zero or more expressions. The function
|
||
Eoss creates an OSS series containing the values of these
|
||
expressions. Every expression in expr-list is evaluated
|
||
before the first output element is returned.
|
||
|
||
(Eoss 1 'a 'b) => [1 a b]
|
||
(Eoss) => []
|
||
|
||
To get the effect of delaying the evaluation of individual elements
|
||
until they are needed, it is necessary to define a special purpose
|
||
enumerator which computes the individual items as needed. However,
|
||
due to the control overhead required, this is seldom worthwhile.
|
||
|
||
It is possible for the expr-list to contain an instance of
|
||
:R. (This must be a literal instance of :R, not an expression
|
||
which evaluates to :R.) If this is the case, then Eoss
|
||
produces an unbounded OSS series analogous to a repeating decimal number.
|
||
The output consists of the values of the expressions preceding the
|
||
:R followed by an unbounded number of repetitions of the values
|
||
following the :R, if there are any such values. (In this
|
||
situation, Eoss is not an early terminator.)
|
||
|
||
(Eoss 1 'a :R 'b 'c) => [1 a b c b c b c ...]
|
||
(Eoss T :R nil) => [T nil nil nil ...]
|
||
(Eoss 1 :R) => [1]
|
||
(Eoss :R 1) => [1 1 1 ...]
|
||
|
||
Eup &optional (start 0) &key (:by 1) :to :below :length => numbers
|
||
|
||
This function is analogous to the Loop macro [2] numeric
|
||
iteration clause. It creates an OSS series of numbers starting with
|
||
start and counting up by :by. The argument start is
|
||
optional and defaults to integer 0. The keyword argument
|
||
:by must always be a positive number and defaults to integer 1.
|
||
|
||
There are four kinds of end tests. If :to is specified, stepping
|
||
stops at this number. The number :to will be included in the OSS
|
||
series iff (- :to start) is a multiple of :by. If
|
||
:below is specified, things operate exactly as if :to were
|
||
specified except that the number :below is never included in the
|
||
OSS series. If :length is specified, the OSS series has length
|
||
:length. It must be the case that :length is a non-negative
|
||
integer. If :length is positive, the last element of the OSS
|
||
series will be (+ start (* :by (1- :length))). If none of the
|
||
termination arguments are specified, the output has unbounded length.
|
||
(In this situation, Eup is not an early terminator.) If more than
|
||
one termination argument is specified, it is an error.
|
||
|
||
(Eup :to 4) => [0 1 2 3 4]
|
||
(Eup :to 4 :by 3) => [0 3]
|
||
(Eup 1 :below 4) => [1 2 3]
|
||
(Eup 4 :length 3) => [4 5 6]
|
||
(Eup) => [0 1 2 3 4 ...]
|
||
|
||
As shown in the following example, Eup does not assume that the
|
||
numbers being enumerated are integers.
|
||
|
||
(Eup 1.5 :by .1 :length 3) => [1.5 1.6 1.7]
|
||
|
||
Edown &optional (start 0) &key (:by 1) :to :above :length => numbers
|
||
|
||
The function Edown is analogous to Eup, except that
|
||
it counts down instead of up and uses the keyword :above instead
|
||
of :below.
|
||
|
||
(Edown :to -4) => [0 -1 -2 -3 -4]
|
||
(Edown :to -4 :by 3) => [0 -3]
|
||
(Edown 1 :above -4) => [1 0 -1 -2 -3]
|
||
(Edown 4 :length 3) => [4 3 2]
|
||
(Edown) => [0 -1 -2 -3 -4 ...]
|
||
(Edown -1.5 :by .1 :length 3) => [-1.5 -1.6 -1.7]
|
||
|
||
Esublists list &optional (end-test #'endp) => sublists
|
||
|
||
This function creates an OSS series containing the successive sublists
|
||
of list. The end-test must be a function from objects to
|
||
boolean values (i.e., to null/non-null). It is used to determine when
|
||
to stop the enumeration. Successive cdrs are returned up to, but not
|
||
including, the first one for which end-test returns non-null.
|
||
|
||
(Esublists '(a b c)) => [(a b c) (b c) (c)]
|
||
(Esublists '(a b . c) #'atom) => [(a b . c) (b . c)]
|
||
|
||
The default end-test (#'endp) will cause Esublists
|
||
to blow up if list contains a non-list cdr. More robust
|
||
enumeration can be obtained by using the end-test #'atom as in
|
||
the second example above. The assumption that list will end
|
||
with nil is used as the default case, because the assumption
|
||
sometimes allows programming errors to be detected closer to their
|
||
sources.
|
||
|
||
Elist list &optional (end-test #'endp) => elements
|
||
|
||
This function creates an OSS series containing the successive elements
|
||
of list. It is closely analogous to Esublists as shown below.
|
||
In particular, end-test has the same meaning and the same caveats apply.
|
||
|
||
(Elist '(a b c)) => [a b c]
|
||
(Elist '()) => []
|
||
(Elist '(a b . c) #'atom) => [a b]
|
||
(Elist list) == (car (Esublists list))
|
||
|
||
The value returned by Elist can be used as a destination for alterS.
|
||
|
||
(let ((list '(a b c)))
|
||
(alterS (Elist (cdr list)) (Eup))
|
||
list) => (a 0 1)
|
||
|
||
Ealist alist &optional (test #'eql) => keys values
|
||
|
||
This function returns two OSS series containing keys and their
|
||
associated values. The first element of keys is the key in the
|
||
first entry in alist, the first element of values is the
|
||
value in the first entry, and so on. The alist must be a proper
|
||
list ending in nil and each entry in alist must be a cons
|
||
cell or nil. Like assoc, Ealist skips entries which
|
||
are nil and entries which have the same key as an earlier entry.
|
||
The test argument is used to determine when two keys are the
|
||
same.
|
||
|
||
(Ealist '((a . 1) () (a . 3) (b . 2))) => [a b] [1 2]
|
||
(Ealist nil) => [] []
|
||
|
||
Both of the series returned by Ealist can be used as
|
||
destinations for alterS. (In analogy with
|
||
multiple-value-bind, letS can be used to bind both values
|
||
returned by Ealist.)
|
||
|
||
(let ((alist '((a . 1) (b . 2))))
|
||
(letS (((key val) (Ealist alist)))
|
||
(alterS key (list key))
|
||
(alterS val (1+ val)))
|
||
alist) => '(((a) . 2) ((b) . 3))
|
||
|
||
The OSS function Ealist is forced to perform a significant amount
|
||
of computation in order to check that no duplicate keys or null
|
||
entries are being enumerated. In a situation where it is known that
|
||
no duplicate keys or null entries exist, it is much more efficient to
|
||
use Elist as shown below.
|
||
|
||
(letS* ((e (Elist '((a . 1) (b . 2))))
|
||
(keys (car e))
|
||
(values (cdr e)))
|
||
(Rlist (list keys values))) => ((a 1) (b 2))
|
||
|
||
Eplist plist => indicators values
|
||
|
||
This function returns two OSS series containing indicators and their
|
||
associated values. The first element of indicators is the first
|
||
indicator in the plist, the first element of values is the
|
||
associated value, and so on. The plist argument must be a
|
||
proper list of even length ending in nil. In analogy with the
|
||
way get works, if an indicator appears more than once in
|
||
plist, it (and its value) will only be enumerated the first time it
|
||
appears. (Both of the OSS series returned by Eplist can be used
|
||
as destinations for alterS.)
|
||
|
||
(Eplist '(a 1 a 3 b 2)) => [a b] [1 2]
|
||
(Eplist nil) => [] []
|
||
|
||
The OSS function Eplist has to perform a significant amount
|
||
of computation in order to check that no duplicate indicators
|
||
are being enumerated. In a situation where it is known that
|
||
no duplicate indicators exist, it is much more efficient to
|
||
use EnumerateF as shown below.
|
||
|
||
(letS* ((e (EnumerateF '(a 1 b 2) #'cddr #'null))
|
||
(indicators (car e))
|
||
(values (cadr e)))
|
||
(Rlist (list indicators values))) => ((a b) (1 2))
|
||
|
||
Etree tree &optional (leaf-test #'atom) => nodes
|
||
|
||
This function creates an OSS series containing all of the nodes in
|
||
tree. The function assumes that tree is a tree built of lists,
|
||
where each node is a list and the elements in the list are the
|
||
children of the node. The function Etree does not assume that
|
||
the node lists end in nil; however, it ignores any non-list
|
||
cdrs. (This behavior increases the utility of Etree when it is
|
||
used to scan Lisp code.) The nodes in the tree are enumerated in
|
||
preorder (i.e., first the root is output, then the nodes in the tree
|
||
which is the first child of the root is enumerated in full, then the
|
||
nodes in the tree which is the second child of the root is enumerated
|
||
in full, etc.).
|
||
|
||
The leaf-test is used to decide which elements of the tree are
|
||
leaves as opposed to internal nodes. Failure of the test should
|
||
guarantee that the element is a list. By default, leaf-test is
|
||
#'atom. This choice of test categorizes nil as a leaf
|
||
rather than as a node with no children.
|
||
|
||
The function Etree assumes that tree is a tree as opposed
|
||
to a graph. If tree is a graph instead of a tree (i.e. some
|
||
node has more than one parent), then this node (and its descendants)
|
||
will be enumerated more than once. If the tree is a cyclic graph,
|
||
then the output series will be unbounded in length.
|
||
|
||
(Etree 'd) => [d]
|
||
(Etree '((c) d)) => [((c) d) (c) c d]
|
||
(Etree '((c) d)
|
||
#'(lambda (e)
|
||
(or (atom e) (atom (car e))))) => [((c) d) (c) d]
|
||
|
||
Efringe tree &optional (leaf-test #'atom) => leaves
|
||
|
||
This enumerator is the same as Etree except that it only
|
||
enumerates the leaves of the tree, skipping all internal nodes.
|
||
The logical relationship between Efringe and Etree is
|
||
shown in the first example below. However, Efringe is
|
||
implemented more efficiently than this example would indicate.
|
||
|
||
(Efringe tree) == (TselectF #'atom (Etree tree))
|
||
(Efringe 'd) => [d]
|
||
(Efringe '((c) d)) => [c d]
|
||
(Efringe '((c) d)
|
||
#'(lambda (e)
|
||
(or (atom e) (atom (car e))))) => [(c) d]
|
||
|
||
The value returned by Efringe can be used as a destination for
|
||
alterS. However, if the entire tree is a leaf and gets altered,
|
||
this will have no side-effect on the tree as a whole. In
|
||
addition, altering a leaf will have no effect on the leaves
|
||
enumerated. In particular, if a leaf is altered into a subtree, the
|
||
leaves of this subtree will not get enumerated.
|
||
|
||
(let ((tree '((3) 4)))
|
||
(letS ((leaf (Efringe tree)))
|
||
(if (evenp leaf) (alterS leaf (- leaf))))
|
||
tree) => ((3) -4)
|
||
|
||
Evector vector &optional (indices (Eup)) => elements
|
||
|
||
This function creates an OSS series of the elements of a one-dimensional
|
||
array. If indices assumes its default value, Evector
|
||
enumerates all of the elements of vector in order.
|
||
|
||
(Evector "BAR") => [#\B #\A #\R]
|
||
(Evector "") => []
|
||
|
||
Looked at in greater detail, Evector enumerates the elements of
|
||
vector which are indicated by the elements of the OSS series
|
||
indices. The indices must be non-negative integers, however,
|
||
they do not have to be in order. Enumeration stops when indices
|
||
runs out, or an index greater than or equal to the length of
|
||
vector is encountered. One can use Eup to create an index
|
||
series which picks out a section of vector. (Since
|
||
Evector takes in an OSS series it is technically a transducer,
|
||
however, it is on-line and is an enumerator in spirit.)
|
||
|
||
(Evector '#(b a r) (Eup 1 :to 2)) => [a r]
|
||
(Evector "BAR" [0 2 1 1 4 1]) => [#\B #\R #\A #\A]
|
||
|
||
The value returned by Evector can be used as a destination for
|
||
alterS.
|
||
|
||
(let ((v "FOOBAR"))
|
||
(alterS (Evector v (Eup 2 :to 4)) #\-) v) => "FO---R"
|
||
|
||
Esequence sequence &optional (indices (Eup)) => elements
|
||
|
||
The function Esequence is the same as Evector except that
|
||
it will work on any Common Lisp sequence. However, since it has to
|
||
determine the type of sequence at run-time, it is much less
|
||
efficient than either Elist or Evector. (The value
|
||
returned by Esequence can be used as a destination for alterS.)
|
||
|
||
(Esequence '(b a r)) => [b a r]
|
||
(Esequence '#(b a r)) => [b a r]
|
||
|
||
Ehash table => keys values
|
||
|
||
This function returns two OSS series containing keys and their
|
||
associated values. The first element of keys is the key of the
|
||
first entry, the first element of values is the value in the
|
||
first entry, and so on. (There are no guarantees as to the order
|
||
in which entries will be enumerated.)
|
||
|
||
(Ehash (let ((h (make-hash-table)))
|
||
(setf (gethash 'color h) 'brown)
|
||
(setf (gethash 'name h) 'fred)
|
||
h)) => [color name] [brown fred] ;in some order
|
||
|
||
In the pure Common Lisp version of the OSS macro package, Ehash
|
||
is rather inefficient, because Common Lisp does not provide incremental
|
||
support for scanning the elements of a hash table. However, in
|
||
the Symbolics Common Lisp version of the OSS macro package,
|
||
Ehash is quite efficient.
|
||
|
||
Esymbols &optional (package *package*) => symbols
|
||
|
||
This function creates an OSS series of the symbols in package
|
||
(which defaults to the current package). (There are no guarantees as
|
||
to the order in which symbols will be enumerated.)
|
||
|
||
(Esymbols) => [foo bar baz ... zot] ;in some order
|
||
|
||
In the pure Common Lisp version of the OSS macro package,
|
||
Esymbols is rather inefficient, because Common Lisp does not provide
|
||
incremental support for scanning the symbols in a package. However,
|
||
in the Symbolics Common Lisp version of the OSS macro package,
|
||
Esymbols is quite efficient.
|
||
|
||
Efile name => items
|
||
|
||
This function creates an OSS series of the items written in the file
|
||
named name. The function combines the functionality of
|
||
with-open-file with the action of reading from the file (using
|
||
read). It is guaranteed that the file will be closed
|
||
correctly, even if an error occurs. As an example of using
|
||
Efile, assume that the forms (a), (1 2), and T have
|
||
been written into the file "test.lisp".
|
||
|
||
(Efile "test.lisp") => [(a) (1 2) T]
|
||
|
||
EnumerateF init step &optional test => items
|
||
|
||
The higher-order function EnumerateF is used to create new kinds
|
||
of enumerators. The init must be a value of some type T1.
|
||
The step argument must be a non-OSS function from T1 to
|
||
T1. The test argument (if present) must be a non-OSS function
|
||
from T1 to boolean.
|
||
|
||
Suppose that the series returned by EnumerateF is S. The
|
||
first output element S_0 has the value S_0=init. For
|
||
subsequent elements, S_i=step(S_i-1).
|
||
|
||
If the test is present, the output consists of elements up to, but
|
||
not including, the first element for which test(S_i) is
|
||
true. In addition, it is guaranteed that step will not be applied
|
||
to the element for which test is true. If there is no test,
|
||
then the output series will be of unbounded length. (In this situation,
|
||
EnumerateF is not an early terminator.)
|
||
|
||
(EnumerateF '(a b c d) #'cddr #'null) => [(a b c d) (c d)]
|
||
(EnumerateF '(a b c d) #'cddr) => [(a b c d) (c d) nil nil ...]
|
||
(EnumerateF list #'cdr #'null) == (Esublists list)
|
||
|
||
If there is no test, then each time an element is output, the
|
||
function step is applied to it. Therefore, it is important that
|
||
other factors in an expression cause termination before EnumerateF
|
||
computes an element which step cannot be applied to. In this
|
||
regard, it is interesting that the following equivalence is almost, but
|
||
not quite true. The difference is that including the test
|
||
argument in the call on EnumerateF guarantees that step
|
||
will not be applied to the element which fails test, while the
|
||
expression using TuntilF guarantees that it will.
|
||
|
||
(TuntilF test (EnumerateF init step)) not= (EnumerateF init step test)
|
||
|
||
Enumerate-inclusiveF init step test => items
|
||
|
||
The higher-order function Enumerate-inclusiveF is the same as
|
||
EnumerateF except that the first element for which test is
|
||
true is included in the output. As with EnumerateF, it is
|
||
guaranteed that step will not be applied to the element for
|
||
which test is true.
|
||
|
||
(Enumerate-inclusiveF '(a b) #'cddr #'null) => [(a b) ()]
|
||
|
||
On-Line Transducers
|
||
|
||
Transducers compute OSS series from OSS series and form the heart of
|
||
most OSS expressions. This section and the next one present the
|
||
predefined transducers that are on-line (i.e., all of their inputs and
|
||
outputs are on-line). These transducers are singled out because they
|
||
can be used more flexibly than the transducers which are off-line.
|
||
In particular, it is impossible to violate the off-line port isolation
|
||
restriction without using an off-line transducer.
|
||
|
||
Tprevious items &optional (default nil) (amount 1) => shifted-items
|
||
|
||
This function creates a series which is shifted right amount
|
||
elements. The input amount must be a positive integer. The
|
||
shifting is done by inserting amount copies of default
|
||
before items and discarding amount elements from the end
|
||
of items. The output is always the same length as the input.
|
||
|
||
(Tprevious [a b c]) => [nil a b]
|
||
(Tprevious [a b c] 'z) => [z a b]
|
||
(Tprevious [a b c] 'z 2) => [z z a]
|
||
(Tprevious []) => []
|
||
|
||
The word previous is used as the root for the name of this
|
||
function, because the function is typically used to access previous
|
||
values of a series. An example of Tprevious used in this way is
|
||
shown in conjunction with Tuntil below.
|
||
|
||
To insert some amount of stuff in front of a series without losing any
|
||
of the elements off the end, use Tconcatenate as shown below.
|
||
|
||
(Tconcatenate [z z] [a b c]) => [z z a b c]
|
||
|
||
Tlatch items &key :after :before :pre :post => masked-items
|
||
|
||
This function acts like a latch electronic circuit component.
|
||
Each input element causes the creation of a corresponding output
|
||
element. After a specified number of non-null input elements have
|
||
been encountered, the latch is triggered and the output mode is
|
||
permanently changed.
|
||
|
||
The :after and :before arguments specify the latch point.
|
||
The latch point is just after the :after-th non-null element in
|
||
items or just before the :before-th non-null element. If
|
||
neither :after nor :before is specified, an :after
|
||
of 1 is assumed. If both are specified, it is an error.
|
||
|
||
If a :pre is specified, every element prior to the latch point
|
||
is replaced by this value. If a :post is specified, this value
|
||
is used to replace every element after the latch point. If neither is
|
||
specified, a :post of nil is assumed.
|
||
|
||
(Tlatch [nil c nil d e]) => [nil c nil nil nil]
|
||
(Tlatch [nil c nil d e] :before 2 :post T) => [nil c nil T T]
|
||
(Tlatch [nil c nil d e] :before 2 :pre 'z) => [z z z d e]
|
||
|
||
As a more realistic example of using Tlatch, suppose that a
|
||
programmer wants to write a program get-codes which takes in a
|
||
list and returns a list of all of the numbers which appear in the list after
|
||
the second number in the list.
|
||
|
||
(defun get-codes (list)
|
||
(letS ((elements (Elist list)))
|
||
(Rlist (Tselect (Tlatch (numberp elements) :after 2 :pre nil)
|
||
elements))))
|
||
|
||
(get-codes '(a b 3 4 c d 5 e 6 f)) => (5 6)
|
||
|
||
Tuntil bools items => initial-items
|
||
|
||
This function truncates an OSS series of elements based on an OSS series
|
||
of boolean values. The output consists of all of the elements of
|
||
items up to, but not including, the first element which corresponds to
|
||
a non-null element of bools. That is to say, if the first
|
||
non-null value in bools is the mth, the output will consist of
|
||
all of the elements of items up to, but not including, the mth.
|
||
(The effect of including the mth element in the output can be
|
||
obtained by using Tprevious as shown in the last example below.)
|
||
In addition, the output terminates as soon as either input runs out of elements
|
||
even if a non-null element of bools has not been encountered.
|
||
|
||
(Tuntil [nil nil T nil T] [1 2 -3 4 -5]) => [1 2]
|
||
(Tuntil [nil nil T nil T] [1]) => [1]
|
||
(Tuntil (Eoss :R nil) (Eup)) => [0 1 2 ...]
|
||
(Tuntil [nil nil T nil T] (Eup)) => [0 1]
|
||
(letS ((x [1 2 -3 4 -5]))
|
||
(Tuntil (minusp x) x)) => [1 2]
|
||
(letS ((x [1 2 -3 4 -5]))
|
||
(Tuntil (Tprevious (minusp x)) x)) => [1 2 -3]
|
||
|
||
If the items input of Tuntil is such that it can be
|
||
used as a destination for alterS, then the output of
|
||
Tuntil can be used as a destination for alterS.
|
||
|
||
(letS* ((list '(a b 10 c))
|
||
(x (Elist list))
|
||
(y (Tuntil (numberp x) x)))
|
||
(alterS y (list y))
|
||
list) => ((a) (b) 10 c)
|
||
|
||
TuntilF pred items => initial-items
|
||
|
||
This function is the same as Tuntil except that it takes a
|
||
functional argument instead of an OSS series of boolean values. The non-OSS
|
||
function pred is mapped over items in order to obtain a
|
||
series of boolean values. (Like Tuntil, TuntilF is
|
||
can be used as a destination
|
||
of alterS if items can.) The basic relationship between
|
||
TuntilF and Tuntil is shown in the last example below.
|
||
|
||
(TuntilF #'minusp [1 2 -3 4 -5]) => [1 2]
|
||
(TuntilF #'minusp [1]) => [1]
|
||
(TuntilF #'minusp (Eup)) => [0 1 2 ...]
|
||
(TuntilF pred items)
|
||
== (letS ((var items)) (Tuntil (TmapF pred var) var))
|
||
|
||
The functions Tuntil and TuntilF are both early
|
||
terminators. This can sometimes lead to conflicts with the
|
||
restriction that within each on-line subexpression, there must be a
|
||
data flow path from each termination point to each
|
||
output. To get the same effect without using an early terminator use
|
||
Tselect of Tlatch as shown below.
|
||
|
||
(Tuntil bools items)
|
||
== (Tselect (not (Tlatch bools :post T)) items)
|
||
|
||
(TuntilF #'pred items)
|
||
== (Tselect (not (Tlatch (pred items) :post T)) items)
|
||
|
||
TmapF function &rest items-list => items
|
||
|
||
The higher-order function TmapF is used to create simple
|
||
kinds of on-line transducers. Its arguments are a single function and
|
||
zero or more OSS series. The function argument must be a non-OSS
|
||
function which is compatible with the number of input series and the
|
||
types of their elements.
|
||
|
||
A single OSS series is returned. Each element of this series is the
|
||
result of applying function to the corresponding elements of the
|
||
input series. (That is to say, if TmapF receives a single input
|
||
series R it will return a single output S such that
|
||
S_i=function(R_i).) The length of the output is the
|
||
same as the length of the shortest input. If there are no bounded
|
||
series inputs (e.g., if there are no series inputs), then TmapF
|
||
will generate an unbounded OSS series.
|
||
|
||
(TmapF #'+ [1 2 3] [4 5]) => [5 7]
|
||
(TmapF #'sqrt []) => []
|
||
(TmapF #'gensym) => [#:G003 #:G004 #:G005 ...]
|
||
|
||
TscanF {init} function items => results
|
||
|
||
The higher-order function TscanF is used to create complex kinds
|
||
of on-line transducers. (The name is borrowed from APL.) The
|
||
init argument (if present) must be a non-OSS value of some type
|
||
T1. The function argument must be a binary non-OSS
|
||
function from T1 and some type T2 to T1. The
|
||
items argument must be an OSS series whose elements are of type
|
||
T2. If the init argument is not present than T1 must
|
||
equal T2.
|
||
|
||
The function argument is used to compute a series of accumulator
|
||
values of type T1 which is returned as the output of
|
||
TscanF. The output is the same length as the series input and
|
||
consists of the successive accumulator values.
|
||
|
||
Suppose that the series input to TscanF is R and the output is S. The basic
|
||
relationship between the output and the input is that S_i=function(S_i-1,R_i).
|
||
If the init argument is specified, it is used as an initial value of the
|
||
accumulator and the first output element S_0 has the value
|
||
S_0=function(init,R_0). Typically, but not necessarily, init is chosen so that
|
||
it is a left identity of function. If that is the case, then S_0=R_0. It is
|
||
important to remember that the elements of items are used as the second
|
||
argument of function. The order of arguments is chosen to highlight this fact.
|
||
|
||
(TscanF 0 #'+ [1 2 3]) => [1 3 6]
|
||
(TscanF 10 #'+ [1 2 3]) => [11 13 16]
|
||
(TscanF nil #'cons [a b]) => [(nil . a) ((nil . a) . b)]
|
||
(TscanF nil #'(lambda (state x) (cons x state)) [a b]) => [(a) (b a)]
|
||
|
||
If the init argument is not specified, then the first element of
|
||
the output is computed differently from the succeeding elements and
|
||
S_0=R_0. (If function is cheap to evaluate, TscanF runs
|
||
more efficiently if it is provided with an init argument.) One
|
||
situation where one typically has to leave out the init argument
|
||
is when function does not have a left identity element as in the
|
||
last example below.
|
||
|
||
(TscanF #'+ [1 2 3]) => [1 3 6]
|
||
(TscanF #'max [1 3 2]) => [1 3 3]
|
||
|
||
An interesting example of a scanning process is the operation of
|
||
proration. In this process, a total is divided up and allocated
|
||
between a number of categories. The allocation is done based on
|
||
percentages which are associated with the categories. (For example,
|
||
some number of packages might be divided up between a number of
|
||
people.) One might think that this could be done straightforwardly by
|
||
multiplying the total by each of the percentages. Unfortunately, this
|
||
mapping approach does not work.
|
||
|
||
The proration problem is more complex than it first appears.
|
||
Typically, there is a limit to the divisibility of the total (e.g.,
|
||
when a group of packages is divided up, the individual packages cannot
|
||
be subdivided). This means that rounding must be performed each time
|
||
the total is multiplied by a percentage. In addition, it is usually
|
||
important that the total be allocated exactly---i.e., that the
|
||
sum of the allocations be exactly equal to the total, rather than
|
||
being one more or one less. Scanning is required in order to make sure
|
||
that things come out exactly right.
|
||
|
||
As a concrete example of proration, suppose that 99 packages need to
|
||
be allocated among three people based on the percentages 35%, 45%,
|
||
and 20%. Assuming that the percentages and the number of packages are
|
||
all represented as integers, simple mapping would lead to the
|
||
incorrect result below in which the allocations add up to 100 instead
|
||
of 99.
|
||
|
||
(prognS (round (/ (* 99 [35 45 20]) 100))) => [35 45 20]
|
||
|
||
The transducer Tprorate below solves the proration problem by
|
||
using TscanF. It takes in a total and an OSS series of
|
||
percentages and returns an OSS series of allocations. The basic action
|
||
of the program is to multiply each percentage by the total. However,
|
||
it also keeps track of how much of the total has been allocated. When
|
||
the last percentage is encountered, the allocation is set to be
|
||
everything which remains to be allocated. (This can cause a
|
||
significant distortion in the final allocation, but it guarantees that
|
||
the allocations will always add up to the total no matter what has
|
||
happened with rounding along the way.) In order to determine when the
|
||
last percentage is being encountered, the program keeps track of how
|
||
much percentage has been accounted for and assumes that the
|
||
percentages always add up to 100.
|
||
|
||
(defun prorate-step (state percent)
|
||
(let* ((total (second state))
|
||
(unallocated (third state))
|
||
(unused-percent (fourth state))
|
||
(allocation (if (= percent unused-percent) unallocated
|
||
(round (/ (* total percent) 100)))))
|
||
(setf (first state) allocation)
|
||
(setf (third state) (- unallocated allocation))
|
||
(setf (fourth state) (- unused-percent percent))
|
||
state))
|
||
|
||
(defunS Tprorate (total percents)
|
||
(declare (type oss percents))
|
||
(car (TscanF (list 0 total total 100) #'prorate-step percents)))
|
||
|
||
(Tprorate 99 [35 45 20]) => [35 45 19]
|
||
|
||
An interesting aspect of the function Tprorate is that the
|
||
state manipulated by the scanned function prorate-step has four
|
||
parts: an allocation, the total, the unallocated portion of the total,
|
||
and the remaining percentage not yet allocated. This illustrates the
|
||
fact that TscanF can be used with complex state objects. (The
|
||
same is true of EnumerateF and ReduceF.) However, it also
|
||
illustrates that accessing the various parts of a complex state is
|
||
awkward and inefficient.
|
||
|
||
Fortunately, it is often possible to get around the need for a complex
|
||
state object by using a compound OSS expression. For the example of
|
||
proration, this can be done as shown below. Simple mapping is
|
||
combined with two scans which keep track of cumulative values. An
|
||
implicitly mapped test is used to make sure that things come out right
|
||
on the last step. (The function Tprevious is used to access the
|
||
previous value of the series unallocated.)
|
||
|
||
(defunS Tprorate-multi-state (total percents)
|
||
(declare (type oss percents))
|
||
(letS* ((allocation (round (/ (* percents total) 100)))
|
||
(unallocated (TscanF total #'- allocation))
|
||
(unused-percent (TscanF 100 #'- percents)))
|
||
(if (zerop unused-percent)
|
||
(Tprevious unallocated total)
|
||
allocation)))
|
||
|
||
Cotruncation
|
||
|
||
A key feature of every on-line transducer is that it terminates as
|
||
soon as any input runs out of elements. Put another way, the output
|
||
is never longer than the shortest input. (If the transducer is also an
|
||
early terminator, then the output can be shorter than the shortest input,
|
||
otherwise it must be the same length as the shortest input.)
|
||
This effect is referred to as cotruncation, because it acts as if each
|
||
input had been truncated to the length of the shortest input. If
|
||
several enumerators and on-line transducers are combined
|
||
together into an OSS expression, cotruncation will typically cause all
|
||
of the series produced by the enumerators to be
|
||
truncated to the same length. For example, in the expression below,
|
||
all of the series (including the unbounded series produced by
|
||
Eup) are truncated to a length of two.
|
||
|
||
(Rlist (* (+ (Eup) [4 5]) [1 2 3])) => (4 12)
|
||
|
||
Tcotruncate items &rest more-items => initial-items &rest more-initial-items
|
||
|
||
It is occasionally important to specify cotruncation explicitly. This
|
||
can be done with the function Tcotruncate whose only action is
|
||
to force all of the outputs to be of the same length. (If any of the
|
||
inputs of Tcotruncate are such that they can be used as
|
||
destinations of alterS, then the corresponding outputs of
|
||
Tcotruncate can be used as destinations of alterS.)
|
||
|
||
(Tcotruncate [1 2 -3 4 -5] [10]) => [1] [10]
|
||
(Tcotruncate (Eup) [a b]) => [0 1] [a b]
|
||
(Tcotruncate [a b] []) => [] []
|
||
|
||
An important feature of Tcotruncate is that it has a powerful
|
||
interaction with the requirement that within each on-line
|
||
subexpression, there must be a data flow path from each termination
|
||
point to each output. Consider the function weighted-squares
|
||
below. This program is intended to take a vector of values and a
|
||
vector of weights and return a list of two vectors: the squares of the
|
||
values and the squares multiplied by the weights. The program
|
||
violates the requirement above, because there is no data flow path
|
||
from (Evector weight-vector) to (Rvector squares).
|
||
|
||
(defun weighted-squares (value-vector weight-vector)
|
||
(letS* ((values (Evector value-vector)) ;Signals warning 18
|
||
(weights (Evector weight-vector))
|
||
(squares (* values values))
|
||
(weighted-squares (* squares weights)))
|
||
(list (Rvector squares) (Rvector weighted-squares))))
|
||
|
||
(weighted-squares #(1 2 3) #(2 3 4)) => (#(1 4 9) #(2 12 36))
|
||
(weighted-squares #(1 2) #(2 3 4)) => (#(1 4) #(2 12))
|
||
(weighted-squares #(1 2 3) #(2 3)) => (#(1 4 9) #(2 12))
|
||
|
||
It might be the case that the programmer knows that value-vector
|
||
and weight-vector always have the same length. (Or it might be
|
||
the case that he wants both output values to be no longer than the
|
||
shortest input.) In either case, the function can be written as shown
|
||
below which is much more efficient than the program above since there is
|
||
no longer a restriction violation which triggers code copying. The
|
||
key difference is that the use of Tcotruncate makes both outputs
|
||
depend on both inputs. If the inputs are known to be the same length,
|
||
the use of Tcotruncate can be thought of as a declaration.
|
||
|
||
(defun weighted-squares* (value-vector weight-vector)
|
||
(letS* (((values weights)
|
||
(Tcotruncate (Evector value-vector)
|
||
(Evector weight-vector)))
|
||
(squares (* values values))
|
||
(weighted-squares (* squares weights)))
|
||
(list (Rvector squares) (Rvector weighted-squares))))
|
||
|
||
(weighted-squares* #(1 2 3) #(2 3 4)) => (#(1 4 9) #(2 12 36))
|
||
(weighted-squares* #(1 2) #(2 3 4)) => (#(1 4) #(2 12))
|
||
(weighted-squares* #(1 2 3) #(2 3)) => (#(1 4) #(2 12))
|
||
|
||
Off-Line Transducers
|
||
|
||
This section and the next two describe transducers that are not
|
||
on-line. Most of these functions have some inputs or outputs which
|
||
are on-line. The ports which are on-line can be used freely.
|
||
However, the off-line ports have to be isolated when they are used.
|
||
(For ease of reference, the off-line ports all begin with the letter
|
||
code "O".)
|
||
|
||
Tremove-duplicates Oitems &optional (comparator #'eql) => items
|
||
|
||
This function is analogous to remove-duplicates. It creates an
|
||
OSS series that has the same elements as the off-line input Oitems
|
||
with all duplicates removed. The comparator is used to
|
||
determine whether or not two items are duplicates. If two items are
|
||
the same, then the item which is later in the series is discarded.
|
||
(As in remove-duplicates the algorithm employed is not
|
||
particularly efficient, being O(n^2).) (If the Oitems input
|
||
of Tremove-duplicates is such that it can be used as a
|
||
destination for alterS, then the output of
|
||
Tremove-duplicates can be used as a destination for alterS.)
|
||
|
||
(Tremove-duplicates [1 2 1 (a) (a)]) => [1 2 (a) (a)]
|
||
(Tremove-duplicates [1 2 1 (a) (a)] #'equal) => [1 2 (a)]
|
||
|
||
Tchunk amount Oitems => lists
|
||
|
||
This function creates an OSS series of lists of length amount of
|
||
successive subseries of the off-line input Oitems. If the length
|
||
of Oitems is not a multiple of amount, then the last
|
||
(mod (Rlength Oitems) amount) elements of Oitems
|
||
will not appear in any output chunk.
|
||
|
||
(Tchunk 2 [a b c d e]) => [(a b) (c d)]
|
||
(Tchunk 6 [a b c d]) => []
|
||
|
||
Twindow amount Oitems => lists
|
||
|
||
This function creates an OSS series of lists of length amount of
|
||
subseries of the off-line input Oitems starting at each element
|
||
position. If the length of Oitems is less than amount,
|
||
the output will not contain any windows. The last example below shows
|
||
Twindow being used to compute a moving average.
|
||
|
||
(Twindow 2 [a b c d]) => [(a b) (b c) (c d)]
|
||
(Twindow 4 [a b c d]) => [(a b c d)]
|
||
(Twindow 6 [a b c d]) => []
|
||
(prognS (/ (apply #'+ (Twindow 2 [2 4 6 8])) 2)) => [3 5 7]
|
||
|
||
Tconcatenate Oitems1 Oitems2 &rest more-Oitems => items
|
||
|
||
This function creates an OSS series by concatenating together two or
|
||
more off-line input OSS series. The length of the output is the sum of the
|
||
lengths of the inputs. (The elements of the individual input series
|
||
are not computed until they need to be.)
|
||
|
||
(Tconcatenate [b c] [] [d]) => [b c d]
|
||
(Tconcatenate [] []) => []
|
||
|
||
TconcatenateF Enumerator Oitems => items
|
||
|
||
The Enumerator must be a quoted OSS function that is an
|
||
enumerator. The function TconcatenateF applies Enumerator
|
||
to each element of the off-line input Oitems and returns the
|
||
series obtained by concatenating all of the results together. If
|
||
Enumerator returns multiple values, then TconcatenateF will as
|
||
well.
|
||
|
||
(TconcatenateF #'Elist [(a b) () (c d)]) => [a b c d]
|
||
(TconcatenateF #'Elist [() ()]) => []
|
||
(TconcatenateF #'Eplist [(a 1) (b 2 c 3)]) => [a b c] [1 2 3]
|
||
|
||
Tsubseries Oitems start &optional below => items
|
||
|
||
This function creates an OSS series containing a subseries of the
|
||
elements of the off-line input Oitems from start up to, but
|
||
not including, below. If below is greater than the length
|
||
of Oitems, output nevertheless stops as soon as the input runs
|
||
out of elements. If below is not specified, the output
|
||
continues all the way to the end of Oitems. Both of the
|
||
arguments start and below must be non-negative integers.
|
||
|
||
(Tsubseries [a b c d] 1) => [b c d]
|
||
(Tsubseries [a b c d] 1 3) => [b c]
|
||
(Rlist (Tsubseries (Elist list) 1 2)) == (subseq list 1 2)
|
||
|
||
If the Oitems input of Tsubseries is such that it can be
|
||
used as a destination for alterS, then the output of
|
||
Tsubseries can be used as a destination for alterS.
|
||
|
||
(let ((list '(a b c d e)))
|
||
(alterS (Tsubseries (Elist list) 1 3) (Eup))
|
||
list) => (a 0 1 d e)
|
||
|
||
The function Tsubseries terminates as soon as it has written the
|
||
last output element. As a result, it is an early terminator. This
|
||
can sometimes lead to conflicts with the restriction that within each
|
||
on-line subexpression, there must be a data flow path from each
|
||
termination point to each output. To select a subseries without using
|
||
an early terminator, use Tselect, Tmask, and Eup as
|
||
shown below.
|
||
|
||
(Tsubseries Oitems from below)
|
||
== (Tselect (Tmask (Eup from :below below)) Oitems)
|
||
|
||
Tpositions Obools => indices
|
||
|
||
This function takes in an OSS series and returns an OSS series of the
|
||
indexes of the non-null elements in the off-line input series.
|
||
|
||
(Tpositions [T nil T 44]) => [0 2 3]
|
||
(Tpositions [nil nil nil]) => []
|
||
|
||
Tmask Omonotonic-indices => bools
|
||
|
||
This function is a quasi-inverse of Tpositions. The input
|
||
Omonotonic-indices must be a strictly increasing OSS series of
|
||
non-negative integers. The output, which is always unbounded, contains
|
||
T in the positions specified by Omonotonic-indices and
|
||
nil everywhere else.
|
||
|
||
(Tmask [0 2 3]) => [T nil T T nil nil ...]
|
||
(Tmask []) => [nil nil ...]
|
||
(Tmask (Tpositions x)) == (Tconcatenate (not (null x)) (Eoss :R nil))
|
||
|
||
Tmerge Oitems1 Oitems2 comparator => items
|
||
|
||
This function is analogous to merge. The output series contains
|
||
the elements of the two off-line input series. The elements of
|
||
Oitems1 appear in the same order that they are read in. Similarly,
|
||
the elements of Oitems2 appear in the same order that they are
|
||
read in. However the elements from the two inputs are intermixed
|
||
under the control of the comparator. At each step, the
|
||
comparator is used to compare the current elements in the two series.
|
||
If the comparator returns non-null, the current element is
|
||
removed from Oitems1 and transferred to the output. Otherwise,
|
||
the next output comes from Oitems2. (If, as in the first
|
||
example below, the elements of the individual input series are ordered
|
||
with respect to comparator, then the result will also be ordered
|
||
with respect to comparator. If, as in the second example below,
|
||
either input is not ordered, the result will not be ordered.)
|
||
|
||
(Tmerge [1 3 7 9] [4 5 8] #'<) => [1 3 4 5 7 8 9]
|
||
(Tmerge [1 7 3 9] [4 5 8] #'<) => [1 4 5 7 3 8 9]
|
||
(Tmerge x y #'(lambda (x y) T)) == (Tconcatenate x y)
|
||
|
||
Tlastp Oitems => bools items
|
||
|
||
This function takes in a series and returns a series of boolean values
|
||
having the same length such that the last value is T and all of
|
||
the other values are nil. If the input series is unbounded, then
|
||
the output series will also be unbounded and every element of the
|
||
output will be nil.
|
||
|
||
It turns out that this output cannot be computed by an on-line OSS
|
||
function. Therefore, if Tlastp returned only the boolean values
|
||
described above, the isolation restrictions
|
||
would make it impossible to use the input series and the output values
|
||
together in the same computation. In order to get around this
|
||
problem, Tlastp returns a second output which is identical to
|
||
the input. This output can be used in lieu of the input in
|
||
combination with the boolean values.
|
||
|
||
(Tlastp [a b c d]) => [nil nil nil T] [a b c d]
|
||
(Tlastp [a]) => [T] [a]
|
||
(Tlastp []) => [] []
|
||
(Tlastp (Eup)) => [nil nil nil ...] [0 1 2 ...]
|
||
|
||
As an example of using Tlastp, it is interesting to return to
|
||
the example of proration discussed in conjunction with the function
|
||
TscanF. Both of the proration functions presented earlier
|
||
assume that the percentages always add up to 100. If this turns out
|
||
not to be the case, then an exact allocation of the total is not
|
||
guaranteed. The following program ensures that exact allocation will
|
||
occur no matter what the percentages add up to. It does this by using
|
||
Tlastp to detect which percentage is the last one.
|
||
|
||
(defunS Tprorate-robust (total Opercents)
|
||
(declare (type oss Opercents))
|
||
(letS* (((is-last percents) (Tlastp Opercents))
|
||
(allocation (round (/ (* percents total) 100)))
|
||
(unallocated (TscanF total #'- allocation)))
|
||
(if is-last (Tprevious unallocated total) allocation)))
|
||
|
||
(Tprorate-robust 99 [35 45 20]) => [35 45 19]
|
||
(Tprorate-robust 99 [35 45 21]) => [35 45 19]
|
||
(Tprorate 99 [35 45 21]) => [35 45 21]
|
||
|
||
Selection and Expansion
|
||
|
||
Selection and its inverse
|
||
are particularly important kinds of off-line transducers.
|
||
|
||
Tselect bools &optional items => Oitems
|
||
|
||
This function selects elements from a series based on a boolean
|
||
series. The off-line output consists of the elements of items
|
||
which correspond to non-null elements of bools. That is to say,
|
||
the nth element of items is in the output iff the nth
|
||
element of bools is non-null. The order of the elements in
|
||
Oitems} is the same as the order of the elements in items. The
|
||
output terminates as soon as either input runs out of elements. If no
|
||
items input is specified, then the non-null elements of
|
||
bools are themselves returned as the output of Tselect. (If
|
||
the items input of Tselect is such that it can be used as
|
||
a destination for alterS, then the output of Tselect can
|
||
be used as a destination for alterS.)
|
||
|
||
(Tselect [T nil T nil] [a b c d]) => [a c]
|
||
(Tselect [a nil b nil]) => [a b]
|
||
(Tselect [nil nil] [a b]) => []
|
||
|
||
An interesting aspect of Tselect is that the output series is
|
||
off-line rather than having the two input series be off-line. This is
|
||
done in recognition of the fact that the two input series are always
|
||
in synchrony with each other. Having only one port which is off-line
|
||
allows more flexibility then having two ports which are off-line.
|
||
|
||
One might want to select elements out of a series based on their
|
||
positions in the series rather than on boolean values. This can be
|
||
done straightforwardly using Tmask as shown below.
|
||
|
||
(Tselect (Tmask [0 2]) [a b c d]) => [a c]
|
||
(Tselect (not (Tmask [0 2])) (Eup 10)) => [11 13 14 15 ...]
|
||
|
||
A final feature of Tselect in particular, and off-line ports in
|
||
general, is illustrated by the program below. In this program, the
|
||
Tselect causes the first Elist to get out of phase with
|
||
the second Elist. As a result, it is important to think of OSS
|
||
expressions as passing around series objects rather than as merely
|
||
being abbreviations for loops where things are always happening
|
||
in lock step. The latter point of view might lead to the idea that
|
||
the output of the program below would be ((a 1) (c 2) (d 4)).
|
||
|
||
(letS ((tag (Elist '(a b c d e)))
|
||
(x (Elist '(1 -2 2 4 -5))))
|
||
(Rlist (list tag (Tselect (plusp x) x)))) => ((a 1) (b 2) (c 4))
|
||
|
||
TselectF pred Oitems => items
|
||
|
||
This function is the same as Tselect, except that it maps the
|
||
non-OSS function pred over Oitems to obtain a series of
|
||
boolean values with which to control the selection. In addition,
|
||
TselectF has an off-line input rather than an off-line output (this
|
||
is fractionally more efficient). The logical relationship between
|
||
Tselect and TselectF is shown in the last example below.
|
||
|
||
(TselectF #'identity [a nil nil b nil]) => [a b]
|
||
(TselectF #'plusp [-1 2 -3 4]) => [2 4]
|
||
(TselectF pred items)
|
||
== (letS ((var items)) (Tselect (TmapF pred var) var))
|
||
|
||
Texpand bools Oitems &optional (default nil) => items
|
||
|
||
This function is a quasi-inverse of Tselect. (The name is
|
||
borrowed from APL.) The output contains the elements of
|
||
Oitems spread out into the positions specified by the non-null
|
||
elements in bools---i.e., the nth element of Oitems is
|
||
in the position occupied by the nth non-null element in bools.
|
||
The other positions in the output are occupied by default.
|
||
The output stops as soon as bools runs out of elements, or a
|
||
non-null element in bools is encountered for which there is no
|
||
corresponding element in Oitems.
|
||
|
||
(Texpand [nil T nil T T] [a b c]) => [nil a nil b c]
|
||
(Texpand [nil T nil T T] [a]) => [nil a nil]
|
||
(Texpand [nil T] [a b c] 'z) => [z a]
|
||
(Texpand [nil T nil T T] []) => [nil]
|
||
|
||
Splitting
|
||
|
||
An operation which is closely related to selection, is splitting. In
|
||
selection, specified elements are selected out of a series. It is not
|
||
possible to apply further operations to the elements which are not
|
||
selected, because they have been discarded. In contrast, splitting
|
||
divides up a series into two or more parts which can be individually
|
||
used. Both Tsplit and TsplitF have on-line inputs and
|
||
off-line outputs. The outputs have to be off-line, because they are
|
||
inherently non-synchronized with each other.
|
||
|
||
Tsplit items bools &rest more-bools => Oitems1 Oitems2 &rest more-Oitems
|
||
|
||
This function takes in a series of elements and partitions them
|
||
between two or more outputs. If there are n boolean inputs then
|
||
there are n+1 outputs. Each input element is placed in exactly one
|
||
output series. Suppose that the nth element of bools is
|
||
non-null. In this case, the nth element of items will be
|
||
placed in Oitems1. On the other hand, if the nth element of
|
||
bools is nil, the second boolean input (if any) is
|
||
consulted in order to see whether the input element should be placed
|
||
in the second output or in a later output. (As in a cond, each
|
||
time a boolean element is nil, the next boolean series is
|
||
consulted.) If the nth element of every boolean series is
|
||
nil, then the nth element of items is placed in the last
|
||
output.
|
||
|
||
(Tsplit [-1 -2 3 4] [T T nil nil]) => [-1 -2] [3 4]
|
||
(Tsplit [-1 -2 3 4] [T T nil nil] [nil T nil T]) => [-1 -2] [4] [3]
|
||
(Tsplit [-1 -2 3 4] [T T T T]) => [-1 -2 3 4] []
|
||
|
||
If the items input of Tsplit is such that it can be used
|
||
as a destination for alterS, then all of the outputs of
|
||
Tsplit can be used as destinations for alterS.
|
||
|
||
(letS* ((list '(-1 2 -3))
|
||
(x (Elist list))
|
||
((x+ x-) (Tsplit x (plusp x))))
|
||
(alterS x+ (+ x+ 10))
|
||
(alterS x- (- x- 10))
|
||
list) => (-11 12 -13)
|
||
|
||
TsplitF items pred &rest more-pred => Oitems1 Oitems2 &rest more-Oitems
|
||
|
||
This function is the same as Tsplit, except that it takes predicates
|
||
as arguments rather than boolean series. The predicates must be
|
||
non-OSS functions and are applied to items in order to create
|
||
boolean values. The relationship between TsplitF and
|
||
Tsplit is almost but not exactly as shown below.
|
||
|
||
(TsplitF items pred1 pred2)
|
||
not= (letS ((var items))
|
||
(Tsplit var (TmapF pred1 var) (TmapF pred2 var)))
|
||
|
||
The reason that the equivalence above does not quite hold is that, as
|
||
in a cond, the predicates are not applied to individual elements
|
||
of items unless the resulting value is needed in order to
|
||
determine which output series the element should be placed in (e.g.,
|
||
if the first predicate returns non-null when given the nth element
|
||
of items, the second predicate will not be called). This promotes
|
||
efficiency and allows earlier predicates to act as guards for later
|
||
predicates.
|
||
|
||
(TsplitF [-1 -2 3 4] #'minusp) => [-1 -2] [3 4]
|
||
(TsplitF [-1 -2 3 4] #'minusp #'evenp) => [-1 -2] [4] [3]
|
||
|
||
Reducers
|
||
|
||
Reducers produce non-OSS outputs based on OSS inputs. There are two
|
||
basic kinds of reducers: ones that combine the elements of OSS series
|
||
together into aggregate data structures (e.g., into a list) and ones
|
||
that compute some summary value from these elements (e.g., the sum).
|
||
All the predefined reducers are on-line.
|
||
A few reducers are also early terminators.
|
||
These reducers are described in the next section.
|
||
|
||
Rlist items => list
|
||
|
||
This function creates a list of the elements in items in order.
|
||
|
||
(Rlist [a b c]) => (a b c)
|
||
(Rlist []) => ()
|
||
(Rlist (fn (Elist x) (Elist y))) == (mapcar #'fn x y)
|
||
(Rlist (fn (Esublists x) (Esublists y))) == (maplist #'fn x y)
|
||
|
||
Rbag items => list
|
||
|
||
This function creates a list of the elements in items with no
|
||
guarantees as to the order of the elements. The function Rbag
|
||
is more efficient than Rlist.
|
||
|
||
(Rbag [a b c]) => (c a b) ;in some order
|
||
(Rbag []) => ()
|
||
|
||
Rappend lists => list
|
||
|
||
This function creates a list by appending the elements of lists
|
||
together in order.
|
||
|
||
(Rappend [(a b) nil (c d)]) => (a b c d)
|
||
(Rappend []) => ()
|
||
|
||
Rnconc lists => list
|
||
|
||
This function creates a list by nconcing the elements of
|
||
lists together in order. The function Rnconc is faster than
|
||
Rappend, but modifies the lists in the OSS series lists.
|
||
|
||
(Rnconc [(a b) nil (c d)]) => (a b c d)
|
||
(Rnconc []) => ()
|
||
(let ((x '(a b))) (Rnconc (Eoss x x))) => (a b a b a b ...)
|
||
(Rnconc (fn (Elist x) (Elist y))) == (mapcan #'fn x y)
|
||
(Rnconc (fn (Esublists x) (Esublists y))) == (mapcon #'fn x y)
|
||
|
||
Ralist keys values => alist
|
||
|
||
This function creates an alist containing keys and values.
|
||
It terminates as soon as either of the inputs runs out of elements.
|
||
If there are duplicate keys, they will be put on the alist, but order
|
||
is preserved.
|
||
|
||
(Ralist [a b] [1 2]) => ((a . 1) (b . 2))
|
||
(Ralist [a b] []) => ()
|
||
(Ralist keys values) == (Rlist (cons keys values))
|
||
|
||
Rplist indicators values => plist
|
||
|
||
This function creates a plist containing keys and values.
|
||
It terminates as soon as either of the inputs runs out of elements.
|
||
If there are duplicate indicators, they will be put on the plist, but
|
||
order is preserved.
|
||
|
||
(Rplist [a b a] [1 2 3]) => (a 1 b 2 a 3)
|
||
(Rplist [a b] []) => ()
|
||
(Rplist keys values) == (Rnconc (list keys values))
|
||
|
||
Rhash keys values &rest option-plist => table
|
||
|
||
This function creates a hash table containing keys and
|
||
values. It terminates as soon as either of the inputs runs out of
|
||
elements. The option-plist can contain any options acceptable
|
||
to make-hash-table. The option-plist cannot refer to
|
||
variables bound by letS.
|
||
|
||
(Rhash [color name] [brown fred]) => #<hash-table 23764432>
|
||
;;hash table containing color->brown, name->fred
|
||
|
||
(Rhash [color name] []) => #<hash-table 23764464>
|
||
;;empty hash table
|
||
|
||
Rvector items &key :size &rest option-plist => vector
|
||
|
||
This function creates a vector containing the elements of items
|
||
in order. The option-plist can contain any options acceptable
|
||
to make-array. The option-plist cannot refer to variables
|
||
bound by letS.
|
||
|
||
The function Rvector operates in one of two ways. If the
|
||
:size argument is supplied, then Rvector assumes that
|
||
items will contain exactly :size elements. A vector is created
|
||
of length :size with the options specified in option-plist
|
||
and the elements of items are stored in it. (If items has
|
||
fewer than :size elements, some of the slots in the vector will
|
||
be left in their initial state. If items has more than
|
||
:size elements, an error will ensue.) In this mode, Rvector is
|
||
very efficient, but rather inflexible.
|
||
|
||
(Rvector [1 2 3] :size 3) => #(1 2 3)
|
||
(Rvector [#\B #\A #\R] :size 3 :element-type 'string-char) => "BAR"
|
||
(Rvector [1] :size 4 :initial-element 0) => #(1 0 0 0)
|
||
|
||
If the :size argument is not supplied, then Rvector allows
|
||
for the creation of an arbitrarily large vector. It does this by
|
||
using vector-push-extend. In order for this to work, it forces
|
||
:adjustable to be T and :fill-pointer to be 0 no
|
||
matter what is specified in the options-list. In this mode, an
|
||
arbitrary number of input elements can be handled, however, things are
|
||
much less efficient, since the vector created is not a simple vector.
|
||
|
||
(Rvector [1 2 3]) => #(1 2 3)
|
||
(Rvector []) => #()
|
||
(Rvector [#\B #\A #\R] :element-type 'string-char) => "BAR"
|
||
|
||
To store a series in a preexisting vector, use alterS of
|
||
Evector.
|
||
|
||
(let ((v '#(a b c)))
|
||
(alterS (Evector v) (Eoss 1 2))
|
||
v) => #(1 2 c)
|
||
|
||
Rfile name items &rest option-plist => T
|
||
|
||
This function creates a file named name and writes the elements
|
||
of items into it using print. The option-plist can
|
||
contain any of the options accepted by open except
|
||
:direction which is forced to be :output. All of the ordinary
|
||
printer control variables are obeyed during the printout. The value
|
||
T is always returned. The option-plist cannot refer to
|
||
variables bound by letS.
|
||
|
||
(Rfile "test.lisp" ['(a) '(1 2) T] :if-exists :append) => T
|
||
;;The output "
|
||
;;(a)
|
||
;;(1 2)
|
||
;;T " is printed into the file "test.lisp".
|
||
|
||
Rlast items &optional (default nil) => item
|
||
|
||
This function returns the last element of items. If items
|
||
is of zero length, default is returned.
|
||
|
||
(Rlast [a b c]) => c
|
||
(Rlast [] 'z) => z
|
||
|
||
Rlength items => number
|
||
|
||
This function returns the number of elements in items.
|
||
|
||
(Rlength [a b c]) => 3
|
||
(Rlength []) => 0
|
||
|
||
Rsum numbers => number
|
||
|
||
This function computes the sum of the elements in numbers.
|
||
These elements must be numbers, but they need not be integers.
|
||
|
||
(Rsum [1 2 3]) => 6
|
||
(Rsum []) => 0
|
||
(Rsum [1.1 1.2 1.3]) => 3.6
|
||
|
||
Rmax numbers => number
|
||
|
||
This function computes the maximum of the elements in numbers.
|
||
These elements must be non-complex numbers, but they need not be integers.
|
||
The value nil is returned if numbers has length zero.
|
||
|
||
(Rmax [2 1 4 3]) => 4
|
||
(Rmax []) => nil
|
||
(Rmax [1.2 1.1 1.4 1.3]) => 1.4
|
||
|
||
Rmin numbers => number
|
||
|
||
This function computes the minimum of the elements in numbers.
|
||
These elements must be non-complex numbers, but they need not be integers.
|
||
The value nil is returned if numbers has length zero.
|
||
|
||
(Rmin [2 1 4 3]) => 1
|
||
(Rmin []) => nil
|
||
(Rmin [1.2 1.1 1.4 1.3]) => 1.1
|
||
|
||
ReduceF init function items => result
|
||
|
||
This function is analogous to reduce. In addition, it is
|
||
similar to TscanF except that init is not optional and the
|
||
final value of the accumulator is the only value returned as shown in
|
||
the last example below. If items is of length zero, init
|
||
is returned. As with TscanF, function must be a non-OSS
|
||
function and the value of init is typically chosen to be a left
|
||
identity of function. It is important to remember that the
|
||
elements of items are used as the second argument of
|
||
function. The order of arguments is chosen to highlight this fact.
|
||
|
||
(ReduceF 0 #'+ [1 2 3]) => 6
|
||
(ReduceF 0 #'+ []) => 0
|
||
(ReduceF 0 #'+ x) == (Rsum x)
|
||
(ReduceF init function items)
|
||
== (letS ((var init))
|
||
(Rlast (TscanF var function items) var))
|
||
|
||
In order to do reduction without an initial seed value, use
|
||
Rlast of TscanF. Note that although a seed value does not have
|
||
to be specified, a value to be returned if there are no elements in
|
||
items still has to be specified.
|
||
|
||
(Rlast (TscanF #'max x) nil) == (Rmax x)
|
||
|
||
Early Reducers
|
||
|
||
The following four reducers are early terminators. Each of these functions has
|
||
a non-early variant denoted by the suffix "-late". The early variants are more
|
||
efficient, because they terminate as soon as they have determined a result.
|
||
This may be long before any of the input series run out of elements. However,
|
||
as discussed at the end of this section, one has to be somewhat careful when
|
||
using an early reducer in an OSS expression.
|
||
|
||
Rfirst items &optional (default nil) => item
|
||
|
||
Rfirst-late items &optional (default nil) => item
|
||
|
||
Both of these functions return the first element of items. If
|
||
items is of zero length, default is returned. The only
|
||
difference between the functions is that Rfirst stops immediately after
|
||
reading the first element of items, while Rfirst-late
|
||
does not terminate until items runs out of elements.
|
||
|
||
(Rfirst [a b c]) => a
|
||
(Rfirst [] 'z) => z
|
||
|
||
Rnth n items &optional (default nil) => item
|
||
|
||
Rnth-late n items &optional (default nil) => item
|
||
|
||
Both of these functions return the nth element of items.
|
||
If n is greater than or equal to the length of items,
|
||
default is returned. The only difference between the functions is
|
||
that Rnth stops immediately after reading the nth element
|
||
of items, while Rnth-late does not terminate until
|
||
items runs out of elements.
|
||
|
||
(Rnth 1 [a b c]) => b
|
||
(Rnth 1 [] 'z) => z
|
||
|
||
Rand bools => bool
|
||
|
||
Rand-late bools => bool
|
||
|
||
Both of these functions compute the and of the elements in
|
||
bools. As with the function and, nil is returned if any
|
||
element of bools is nil. Otherwise the last element of
|
||
bools is returned. The value T is returned if bools
|
||
has length zero. The only difference between the functions is that
|
||
Rand terminates as soon as a nil is encountered in the
|
||
input, while Rand-late does not terminate until
|
||
bools runs out of elements.
|
||
|
||
(Rand [a b c]) => c
|
||
(Rand [a nil c]) => nil
|
||
(Rand []) => T
|
||
(Rand (pred (Esequence x) (Esequence y))) == (every #'pred x y)
|
||
|
||
Ror bools => bool
|
||
|
||
Ror-late bools => bool
|
||
|
||
Both of these functions compute the or of the elements in
|
||
bools. As with the function or, nil is returned if every
|
||
element of bools is nil. Otherwise the first non-null
|
||
element of bools is returned. The value nil is returned
|
||
if bools has length zero. The only difference between the
|
||
functions is that Ror terminates as soon as a non-null value is
|
||
encountered in the input, while Ror-late does not terminate
|
||
until bools runs out of elements.
|
||
|
||
(Ror [a b c]) => a
|
||
(Ror [a nil c]) => a
|
||
(Ror []) => nil
|
||
(Ror (pred (Esequence x) (Esequence y))) == (some #'pred x y)
|
||
|
||
Care must be taken when using early reducers. As discussed in the section on
|
||
restrictions, OSS expressions are required to obey the restriction that within
|
||
each on-line subexpression, there must be a data flow path from each
|
||
termination point to each output. Early reducers interact with this
|
||
restriction since early reducers are termination points. As a result, there
|
||
must be a data flow path from each early reducer to each output of the
|
||
containing on-line subexpression.
|
||
|
||
Since reducers compute non-OSS values, they directly compute outputs of
|
||
on-line subexpressions. As a result, it is impossible for there to be
|
||
a data flow path from a reducer to any output other than the output
|
||
the reducer itself computes. Therefore, the use of an early reducer
|
||
will trigger code copying unless that reducer computes the
|
||
only output of the on-line subexpression.
|
||
|
||
For example, consider the following four expressions. The first two
|
||
expressions return the same result. However, the first is more
|
||
efficient. This is a prototypical example of a situation where it is
|
||
better to use an early reducer. In contrast, although the last two
|
||
expressions also return the same results, the second of the
|
||
expressions is more efficient. The problem is that in the first of
|
||
these expressions, there is no data flow path from the use of
|
||
Rfirst to the second output. In order to fix this problem the OSS
|
||
macro package duplicates the list enumeration. It is more efficient
|
||
to use a non-early reducer as in the last example.
|
||
|
||
(letS ((x (Elist '(1 2 -3 4 5 -6 -7 8))))
|
||
(Rfirst (TselectF #'minusp x))) => -3
|
||
|
||
(letS ((x (Elist '(1 2 -3 4 5 -6 -7 8))))
|
||
(Rfirst-late (TselectF #'minusp x))) => -3
|
||
|
||
(letS ((x (Elist '(1 2 -3 4 5 -6 -7 8)))) ;Signals warning 18
|
||
(valS (Rfirst (TselectF #'minusp x))
|
||
(Rsum x))) => -3 4
|
||
|
||
(letS ((x (Elist '(1 2 -3 4 5 -6 -7 8))))
|
||
(valS (Rfirst-late (TselectF #'minusp x))
|
||
(Rsum x))) => -3 4
|
||
|
||
Series Variables
|
||
|
||
The principal way to create OSS variables is to use the form letS.
|
||
(These variables are also created by the forms lambdaS and
|
||
defunS.)
|
||
|
||
letS var-value-pair-list {decl}* &body expr-list => result
|
||
|
||
The form letS is syntactically analogous to let. Just as
|
||
in a let, the first subform is a list of variable-value pairs.
|
||
The letS form defines the scope of these variables and gives
|
||
them the indicated values. As in a let, one or more
|
||
declarations can follow the variable-value pairs. These can be used
|
||
to specify the types of the variables.
|
||
|
||
The variables created by letS can be OSS variables or non-OSS
|
||
variables. Which are which is determined by the type of the value
|
||
that is bound to the variable. As in let, the variables are
|
||
bound in parallel. In the example below, y is an OSS variable
|
||
while x and z are non-OSS variables.
|
||
|
||
(letS ((x '(1 2 3))
|
||
(y (Elist '(1 2 3)))
|
||
(z (Rsum (Elist '(1 2 3)))))
|
||
(list x (Rmax y) z)) => ((1 2 3) 3 6)
|
||
|
||
Unlike let, letS does not support degenerate
|
||
variable-value pairs which consist solely of a variable. (Since
|
||
letS variables cannot be assigned to, see below, degenerate pairs
|
||
would be of little value.)
|
||
|
||
(letS (x) ...) ;Signals error 9
|
||
|
||
The following example illustrates the use of a declaration in a
|
||
letS. Declarations are handled in the same way that they are handled
|
||
in a let.
|
||
|
||
(letS ((x (Elist '(1 2 3))))
|
||
(declare (type integer x))
|
||
(Rsum x)) => 6
|
||
|
||
The form letS goes beyond let to include the functionality of
|
||
multiple-value-bind. A variable in a variable-value pair can be a list of
|
||
variables instead of a single variable. When this is the case, the variables
|
||
pick up the first, second, etc. results returned by the value expression. (If
|
||
there is only one variable, it gets the first value. If nil is used in lieu of
|
||
a variable, the corresponding value is ignored.) If there are fewer variables
|
||
than values, the extra values are ignored. Unlike multiple-value-bind, letS
|
||
signals an error if there are more variables than values. (Note that there is
|
||
no form multiple-value-bindS and that the form multiple-value-bind cannot be
|
||
used inside of an OSS expression to bind the results of an OSS function.)
|
||
|
||
(letS (((key value) (Ealist '((a . 1) (b . 2)))))
|
||
(Rlist (list key value))) => ((a 1) (b 2))
|
||
|
||
(letS ((key (Ealist '((a . 1) (b . 2)))))
|
||
(Rlist key)) => (a b)
|
||
|
||
(letS (((nil value) (Ealist '((a . 1) (b . 2)))))
|
||
(Rlist value)) => (1 2)
|
||
|
||
(letS (((key value x) (Ealist '((a . 1) (b . 2)))))
|
||
(Rlist (list key value x))) ;Signals error 8
|
||
|
||
The expr-list of a letS has the effect of grouping several OSS
|
||
expressions together. The value of the last form in the expr-list is
|
||
returned as the value of the letS. This value may be an OSS value or
|
||
a non-OSS value.
|
||
|
||
In addition to placing all of the expressions in the same letS
|
||
binding scope, the grouping imposed by the expr-list causes the
|
||
entire body to become an OSS expression. This can alter the way
|
||
implicit mapping is applied by including non-OSS functions in the
|
||
OSS expression.
|
||
|
||
The restricted nature of OSS variables.
|
||
There are a number of ways in which the variables bound by letS
|
||
(or lambdaS and defunS) are more restricted than the ones
|
||
bound by let. For the most part, these restrictions stem from
|
||
the fact that when the OSS macro package transforms an OSS expression
|
||
into a loop, it rearranges the expressions extensively. This forces
|
||
letS variable scopes to be supported by variable renaming rather
|
||
than binding. One result of this is that it is not possible to
|
||
declare (or proclaim) a letS variable to be special.
|
||
(Standard Common Lisp does not provide any method for determining
|
||
whether or not a variable has been proclaimed special. As a result,
|
||
the OSS macro package is unable to issue an error message when a
|
||
special letS variable is encountered. The Symbolics Common Lisp
|
||
version of the OSS macro package does issue an error message.)
|
||
|
||
(proclaim '(special z))
|
||
(letS ((z (Elist '(1 2 3)))) (Rsum z)) ;erroneous expression
|
||
|
||
Another limitation is that programmers are not allowed to assign
|
||
values to letS variables in the body of a letS. (This
|
||
restriction applies whether or not the variables contain OSS values.)
|
||
The only time letS variables can be given a value is the moment
|
||
they are bound. (Although assignment could be supported easily
|
||
enough, the rearrangements introduced by the OSS macro package would
|
||
make it very confusing for a programmer to figure out exactly what
|
||
would happen in a given situation. In particular, naively applying
|
||
implicit mapping to setq would lead to peculiar results. In
|
||
addition, outlawing assignments enhances the functional nature of the
|
||
OSS macro package.) An error message is issued whenever such an
|
||
assignment is attempted.
|
||
|
||
(lets ((x (Elist '(1 2 3))))
|
||
(setq x (1+ x)) ;Signals error 12
|
||
(Rlist x))
|
||
|
||
Another aspect of letS variables is that their scope is somewhat
|
||
limited. In particular, letS variables can be referenced in a
|
||
letS or mapS which is inside the letS which binds
|
||
them. However, they cannot be referenced in lambda or
|
||
lambdaS. (As above, this limitation is imposed in order to avoid
|
||
confusions due to rearrangements. Further, it is not obvious what it
|
||
would mean to refer to an OSS variable in a lambda. Should some
|
||
sort of implicit mapping be applied?) No attempt is made to issue
|
||
error messages in this situation. Rather, the variable reference in
|
||
question is merely treated as a free variable.
|
||
|
||
(let ((x 4))
|
||
(letS ((x (Elist '(1 2 3))))
|
||
(Rlist (TmapF #'(lambda (y) (+ x y)) x)))) => (5 6 7)
|
||
|
||
letS* var-value-pair-list {decl}* &body expr-list => result
|
||
|
||
The form letS* is exactly the same as letS except that the
|
||
variables are bound sequentially instead of in parallel.
|
||
|
||
(letS* ((x '(1 2 3))
|
||
(y (Elist x))
|
||
(z (Rsum y)))
|
||
(list x (Rmax y) z)) => ((1 2 3) 3 6)
|
||
|
||
prognS &body expr-list => result
|
||
|
||
As shown below, prognS is identical to letS except that it
|
||
cannot contain any variable-value pairs or declarations. It is a
|
||
degenerate form whose only function is to delineate an OSS expression.
|
||
This can alter the way implicit mapping is applied by including
|
||
non-OSS functions in the OSS expression.
|
||
|
||
(prognS . expr-list) == (letS () . expr-list)
|
||
|
||
Complete OSS expressions do not return OSS values.
|
||
A key point relevant to the discussion above is that syntactically
|
||
complete OSS expressions are not allowed to return OSS values. This is
|
||
relevant, because letS and prognS are often used in such a
|
||
way that an OSS series gratuitously ends up as the return value. For
|
||
example, the main intent of the expression below is to print out the
|
||
elements of the list. However, as written, the expression appears to
|
||
return an OSS series of the values produced by prin1. Because
|
||
expressions like the one below are relatively common, it was decided
|
||
not to issue an error message in this situation. Rather, the OSS value
|
||
is simply discarded and no value is returned.
|
||
|
||
(prognS (prin1 (Elist '(1 2)))) =>
|
||
;;The output "12" is printed.
|
||
|
||
It might be the case that the programmer actually desires to have a
|
||
physical series returned in the example above. This can be done by
|
||
using a reducer such as Rlist or Rvector as shown below.
|
||
|
||
(prognS (Rlist (prin1 (Elist '(1 2))))) => (1 2)
|
||
;;The output "12" is printed.
|
||
|
||
Preventing complete OSS expressions from returning OSS values does not
|
||
limit what can be written, because programmers can always return a
|
||
non-OSS series. This can be a bit cumbersome at times, but it is
|
||
highly preferable to the large inefficiencies which would be
|
||
introduced by automatically constructing physical representations for
|
||
OSS series in situations where the returned values are not used in
|
||
further computation.
|
||
|
||
Coercion of Non-Series to Series
|
||
|
||
If an OSS input of an OSS function is applied to a non-series value, the
|
||
type conflict is resolved by converting the non-OSS value into a series
|
||
by inserting Eoss. That is to say, a non-OSS value acts the same
|
||
as an unbounded OSS series of the value.
|
||
|
||
(Ralist (Elist '(a b)) (* 2 3))
|
||
== (Ralist (Elist '(a b)) (Eoss :R (* 2 3))) => ((a . 6) (b . 6))
|
||
|
||
Using Eoss to coerce a non-OSS value to an OSS series has the
|
||
effect of only evaluating the expression which computes the value
|
||
once. This has many advantages with regard to efficiency, but may not
|
||
always be what is desired. Multiple evaluation can be specified by
|
||
using TmapF or mapS.
|
||
|
||
(Ralist (Elist '(a b)) (gensym)) => ((a . #:G004) (b . #:G004))
|
||
(Ralist (Elist '(a b)) (TmapF #'gensym)) => ((a . #:G004) (b . #:G005))
|
||
|
||
Implicit Mapping
|
||
|
||
Mapping operations can be created by using TmapF. However, in the
|
||
interest of convenience, two other ways of creating mapping operations
|
||
are supported. The most prominent of these is implicit mapping. If a
|
||
non-OSS function appears in an OSS expression and is applied to one or
|
||
more arguments which are OSS series, the type conflict is resolved by
|
||
automatically mapping the function over these series.
|
||
|
||
(Rsum (car (Elist '((1) (2)))))
|
||
== (Rsum (TmapF #'car (Elist '((1) (2))))) => 3
|
||
|
||
(Rsum (* 2 (Elist '(1 2))))
|
||
== (Rsum (TmapF #'(lambda (x) (* 2 x)) (Elist '(1 2)))) => 6
|
||
|
||
As shown in the second example, implicit mapping actually applies to
|
||
entire non-OSS subexpressions rather than merely to individual
|
||
functions. This promotes efficiency and makes sure that related
|
||
groups of functions are mapped together. However, it is not always
|
||
what is desired. For instance, in the first example below, the call
|
||
on gensym gets mapped in conjunction with the call on
|
||
list. This causes each list to contain a separate gensym
|
||
variable. It might be the case that the programmer wants to have the
|
||
same gensym variable in each list. This can be achieved by
|
||
inserting an Eoss as shown in the second example. (Inserting a
|
||
Eoss here and there can promote efficiency by avoiding
|
||
unnecessary recomputation.)
|
||
|
||
(Rlist (list (Elist '(a b)) (gensym)))
|
||
== (Rlist (TmapF #'(lambda (x) (list x (gensym)))
|
||
(Elist '(a b)))) => ((a #:G002) (b #:G003))
|
||
|
||
(Rlist (list (Elist '(a b)) (Eoss :R (gensym))))
|
||
== (Rlist (TmapF #'list
|
||
(Elist '(a b))
|
||
(Eoss :R (gensym)))) => ((a #:G002) (b #:G002))
|
||
|
||
In order to be implicitly mapped, a non-OSS function must appear inside
|
||
of an OSS expression. For example, the instance of prin1 in the
|
||
first example below does not get implicitly mapped, because it is not
|
||
in an OSS expression. Implicit mapping of the prin1 can be
|
||
forced by using prognS as shown in the second example above.
|
||
|
||
(prin1 (Elist '(1 2))) => nil
|
||
;;The output "NIL" is printed.
|
||
|
||
(prognS (prin1 (Elist '(1 2)))) =>
|
||
;;The output "12" is printed.
|
||
|
||
(The result of the first example above is that NIL gets
|
||
printed. This happens because (Elist '(1 2 3)) is a
|
||
syntactically complete OSS expression and is therefore not allowed to
|
||
return a series. It returns no values instead. The function
|
||
prin1 demands a value anyway, and gets nil.)
|
||
|
||
Another aspect of implicit mapping is that a non-OSS function will not
|
||
be mapped unless it is applied to a series. This is usually, but not
|
||
always, what is desired. Consider the first expression below. The
|
||
instance of prin1 is mapped over x. However, the instance
|
||
of princ is not applied to a series and is therefore not mapped.
|
||
If the programmer intends to print a dash after each number, he has to
|
||
do something in order to get the princ to be mapped. This could
|
||
be done using TmapF or mapS. However, the best thing to
|
||
do is to group the two printing statements into a single subexpression
|
||
as shown in either of the last two examples below. This grouping
|
||
shows the relationship between the printing operations and causes them
|
||
to be mapped together.
|
||
|
||
(letS ((x (Elist '(1 2 3))))
|
||
(prin1 x)
|
||
(princ "-")) => "-"
|
||
;;The output "123-" is printed.
|
||
|
||
(letS ((x (Elist '(1 2 3))))
|
||
(progn (prin1 x) (princ "-"))) =>
|
||
;;The output "1-2-3-" is printed.
|
||
|
||
(letS ((x (Elist '(1 2 3))))
|
||
(format T "~A-" x)) =>
|
||
;;The output "1-2-3-" is printed
|
||
|
||
Ugly details.
|
||
Implicit mapping is easy to understand when applied in simple
|
||
situations such as the ones above. However, it can be applied to any
|
||
Lisp form. Things become somewhat more complicated when control
|
||
constructs (e.g., if) and binding constructs (e.g., let)
|
||
are encountered. The example below shows the implicit mapping of an
|
||
if. This creates a lambda expression containing a
|
||
conditional which is mapped over a series. A key thing to notice in
|
||
this example is that implicit mapping of if is very different
|
||
from a use of Tselect. In particular, the mapped if
|
||
returns a value corresponding to every input, while the Tselect
|
||
does not.
|
||
|
||
(Rlist (if (plusp (Elist '(10 -11 12))) (Eup)))
|
||
== (Rlist (TmapF #'(lambda (x y) (if (plusp x) y))
|
||
(Elist '(10 -11 12)) (Eup))) => (0 nil 2)
|
||
|
||
(Rlist (Tselect (plusp (Elist '(10 -11 12))) (Eup))) => (0 2)
|
||
|
||
Another aspect of the way conditionals are handled inside of an OSS
|
||
expression is illustrated below. When an OSS expression is being
|
||
processed in order to determine what should be implicitly mapped, the
|
||
expression is broken up into OSS pieces and non-OSS pieces. If the argument
|
||
of a conditional is an OSS expression, this argument will end up in a
|
||
separate piece from the conditional itself. One result of this is
|
||
that the argument will always be evaluated and the conditional will
|
||
therefore lose its power to control when the argument should be
|
||
evaluated. This effect will happen even if, as in the example below,
|
||
the conditional does not have to be mapped. The three examples below
|
||
all produce the same value, but the first two always evaluate
|
||
(Rlist (abs (Elist x))) while the last may not.
|
||
|
||
(prognS (if (Ror (minusp (Elist x)))
|
||
(Rlist (abs (Elist x)))
|
||
x))
|
||
== (prognS (funcall #'(lambda (y z) (if y z x))
|
||
(Ror (minusp (Elist x)))
|
||
(Rlist (abs (Elist x)))))
|
||
not= (if (Ror (minusp (Elist x)))
|
||
(Rlist (abs (Elist x)))
|
||
x)
|
||
|
||
The following example shows the implicit mapping of a let.
|
||
(Among other things, this illustrates that such expressions are far
|
||
from clear. In general it is better to use letS as in the
|
||
second example.)
|
||
|
||
(Rlist (let ((double (* 2 (Elist '(1 2))))) (* double double)))
|
||
== (Rlist (TmapF #'(lambda (x)
|
||
(let ((double (* 2 x))) (* double double)))
|
||
(Elist '(1 2)))) => (4 16)
|
||
|
||
(letS ((double (* 2 (Elist '(1 2)))))
|
||
(Rlist (* double double))) => (4 16)
|
||
|
||
A problem with the implicit mapping of a let (or other binding
|
||
forms) is that the implicit mapping transformation potentially moves
|
||
subexpressions out of the scope of the binding form in question. This
|
||
can change the meaning of the expression if any of these
|
||
subexpressions contain an instance of a variable bound by the binding
|
||
form. For instance, in the example above, the transformation moves
|
||
the subexpression (Elist '(1 2)) out of the scope of the
|
||
let. This would cause a problem if this subexpression referred to
|
||
the variable double.
|
||
|
||
In recognition of this problem, a warning message is issued whenever
|
||
implicit mapping of a binding form causes a variable reference to move
|
||
out of a form that binds it. Whenever it occurs, this problem can be
|
||
alleviated by using letS as shown above.
|
||
|
||
A final complexity involves forms like return,
|
||
return-from, throw, etc. These forms are implicitly mapped
|
||
like any other non-OSS form. When they get evaluated, they will cause
|
||
an exit. However, the loop produced by the OSS macro does not contain
|
||
a boundary which is recognized by any of these forms (e.g., it does
|
||
not create a prog or catch). As a result, such a boundary
|
||
must be defined which will serve as the reference point. Needless to
|
||
say, the final results of the OSS expression will not be computed if
|
||
the expression is exited in this way.
|
||
|
||
Nested loops.
|
||
Implicit mapping is applied when non-OSS functions receive OSS values.
|
||
However, implicit mapping is not applied when OSS functions receive OSS
|
||
values, even if these values are passed to non-OSS inputs. As
|
||
illustrated below, whenever this situation occurs, an error message is
|
||
issued.
|
||
|
||
(Elist (Elist '((1 2) (3 4)))) ;Signals error 14
|
||
|
||
There are situations corresponding to nested loops where it would be
|
||
reasonable to implicitly map subexpressions containing OSS functions.
|
||
For example, one might write the following expression in order to copy
|
||
a list of lists.
|
||
|
||
(Rlist (Rlist (Elist (Elist '((1 2) (3 4)))))) ;Signals error 14
|
||
(Rlist (TmapF #'(lambda (x) (Rlist (Elist x)))
|
||
(Elist '((1 2) (3 4))))) => ((1 2) (3 4))
|
||
|
||
Nevertheless, expressions like the first one above are forbidden.
|
||
This is done for two reasons. First, in more complex situations
|
||
OSS expressions corresponding to nested loops become so confusing that
|
||
such expressions are very hard to understand. As a
|
||
result, they are not very useful. Second, experience suggests that a
|
||
large proportion of situations where mapping of OSS functions might
|
||
be done arise from programming errors rather than an intention to have
|
||
a nested loop. Outlawing these expressions makes it possible to find
|
||
these errors more quickly.
|
||
|
||
(The following example shows that there is no problem with having
|
||
one loop computation following another. There are no type conflicts
|
||
in this situation and no implicit mapping is required.)
|
||
|
||
(Rsum (Evector (Rvector (Elist '(1 2))))) => 3
|
||
|
||
Needless to say, it would be unreasonable if there were no way to write
|
||
OSS expressions corresponding to nested loops. First of all,
|
||
this can always be done using TmapF as shown above. However,
|
||
this can be rather cumbersome. To alleviate this difficulty, an
|
||
additional form (mapS) is introduced which facilitates the
|
||
expression of nested computations.
|
||
|
||
mapS &body expr-list => items
|
||
|
||
The expr-list consists of one or more expressions. These
|
||
expressions are treated as the body of a function and mapped over any
|
||
free OSS variables which appear in them. That is to say, the first element
|
||
of the output is computed by evaluating the expressions in an environment
|
||
where each OSS variable is bound to the first element of the corresponding
|
||
series. The second element of the output is computed by evaluating the
|
||
expressions in an environment where each OSS variable is bound to the
|
||
second element of the corresponding series, etc. The way mapS
|
||
could be used to copy a list-of-lists is shown below. A letS
|
||
has to be used, because mapS requires that the series being
|
||
mapped over must be held in a variable.
|
||
|
||
(letS ((z (Elist '((1 2) (3 4)))))
|
||
(Rlist (mapS (Rlist (Elist z)))))
|
||
== (letS ((z (Elist '((1 2) (3 4)))))
|
||
(Rlist (TmapF #'(lambda (x)
|
||
(Rlist (Elist x))) z))) => ((1 2) (3 4))
|
||
|
||
(Rlist
|
||
(mapS
|
||
(Rlist (Elist (Elist '((1 2) (3 4))))))) ;Signals error 14
|
||
|
||
Implicit mapping is very valuable.
|
||
From the above, it can be seen that although implicit mapping is
|
||
simple in simple situations, there are a number of situations where it
|
||
becomes quite complex. There is no question that these complexities
|
||
dilute the value of implicit mapping. Nevertheless, experience
|
||
suggests that implicit mapping is so valuable that, warts and all, it
|
||
is perhaps the most useful single feature of OSS expressions.
|
||
|
||
Literal Series Functions
|
||
|
||
Just as it is very convenient to be able to specify a literal non-OSS
|
||
function using lambda, it is sometimes convenient to be able to
|
||
specify a literal OSS function.
|
||
|
||
lambdaS var-list {decl}* &body expr-list
|
||
|
||
The form lambdaS is analogous to lambda except that some
|
||
of the arguments can have OSS series passed to them and the return
|
||
value can be an OSS series. The var-list is simpler than the
|
||
lambda lists which are supported by lambda. In
|
||
particular, the var-list must consist solely of variable names. It
|
||
cannot contain any of the lambda list keywords such as
|
||
&optional and &rest. As in a letS, the variables in the
|
||
var-list cannot be assigned to in the expr-list or
|
||
referenced inside of a nested lambda or lambdaS.
|
||
|
||
As in a lambda, the body can begin with one or more
|
||
declarations. All of the arguments which are to receive OSS values
|
||
have to be declared inside the lambdaS using the declaration
|
||
type oss (see below). All of the other arguments are assumed to
|
||
correspond to non-OSS values. Just as in a letS, the
|
||
declarations may contain other kinds of declarations besides
|
||
type oss declarations. However, the variables in the var-list
|
||
cannot be declared (or proclaimed) to be special.
|
||
|
||
The expr-list is a list of expressions which are grouped
|
||
together into an OSS expression as in a letS or
|
||
prognS. The value of the function specified by a lambdaS is
|
||
the value of the last form in the expr-list. This value may or
|
||
may not be an OSS series.
|
||
|
||
In many ways, lambdaS bears the same relationship to letS
|
||
that lambda bears to let. However, there is one key
|
||
difference. The expr-list in a lambdaS cannot refer to
|
||
any free variables which are bound by a letS, defunS, or
|
||
another lambdaS. Each lambdaS is processed in complete
|
||
isolation from the OSS expression which surrounds it. The only values
|
||
which can enter or leave a lambdaS are specified by the
|
||
var-list and non-OSS variables which are bound outside of the entire
|
||
containing OSS expression.
|
||
|
||
Another key feature of lambdaS is that the only place where it
|
||
can validly appear is as the quoted first argument of funcallS
|
||
(see below), or as an argument to a macro which will eventually expand
|
||
in such a way that the lambdaS will end up as the quoted first
|
||
argument of a funcallS.
|
||
|
||
The following example illustrates the use of lambdaS. It shows
|
||
an anonymous OSS function identical to Rsum.
|
||
|
||
(funcallS #'(lambdaS (x)
|
||
(declare (type oss x))
|
||
(ReduceF 0 #'+ x))
|
||
(Elist '(1 2 3))) => 6
|
||
|
||
type oss &rest variable-list
|
||
|
||
This type declaration can only be used inside of a declare
|
||
inside of a lambdaS or a defunS. It specifies that the
|
||
variables carry OSS values.
|
||
|
||
funcallS function &rest expr-list => result
|
||
|
||
This is analogous to funcall except that function can be
|
||
an OSS function. In particular, it can be the quoted name of a series
|
||
function, a quoted lambdaS, or a macro call which expands into
|
||
either of the above. It is also possible for function to be a
|
||
non-OSS function, in which case funcallS is identical to
|
||
TmapF. If function is an expression which evaluates to a
|
||
function (as opposed to a literal function), then it is assumed to be
|
||
a non-OSS function.
|
||
|
||
(funcallS #'Elist '(1 2)) == (Elist '(1 2)) => [1 2]
|
||
(funcallS #'(lambdaS (y) (declare (type oss y)) (* 2 y))
|
||
(Elist '(1 2))) => [2 4]
|
||
(funcallS #'car [(1) (2)]) => [1 2]
|
||
(funcallS #'car '(1 2)) => [1 1 1 1 ...]
|
||
|
||
The number of expressions in expr-list must be exactly the same
|
||
as the number of arguments expected by function. If not, an
|
||
error message is issued. In addition, the types of values (either OSS
|
||
series or not) returned by the expressions should be the same as the
|
||
types which are expected by function. If not, coercion of
|
||
non-series to series will be applied if possible in order to resolve
|
||
the conflict.
|
||
|
||
Defining Series Functions
|
||
|
||
An important aspect of the OSS macro package is that it makes it
|
||
easy for programmers to define new OSS functions. Straightforward OSS
|
||
functions can be defined using the facilities outlined below. More
|
||
complex OSS functions can be defined using the subprimitive facilities
|
||
described in [6].
|
||
|
||
defunS name lambda-list {doc} {decl}* &body expr-list
|
||
|
||
This is analogous to defun, but for OSS functions. At a simple
|
||
level, defunS is just syntactic sugar which defines a
|
||
macro that creates a funcallS of a lambdaS. The
|
||
lambda-list, declarations, and expression list are restricted in
|
||
exactly the same way as in a lambdaS except that the standard
|
||
lambda list keywords &optional and &key are allowed
|
||
in the lambda-list.
|
||
|
||
(defunS Rlast (items &optional (default nil))
|
||
"Returns the last element of an OSS series"
|
||
(declare (type oss items))
|
||
(ReduceF default #'(lambda (state x) x) items))
|
||
== (defmacro Rlast (items &optional (default 'nil))
|
||
"Returns the last element of an OSS series"
|
||
`(funcallS #'(lambdaS (items default)
|
||
(declare (type oss items))
|
||
(ReduceF default #'(lambda (state x) x) items))
|
||
,items ,default))
|
||
|
||
However, at a deeper level, there is a key additional aspect to
|
||
defunS. Preprocessing and checking of the resulting lambdaS is
|
||
performed when the defunS is evaluated (or compiled), rather
|
||
than when the resulting OSS function is used. This saves time when the
|
||
function is used. More importantly, it leads to better error messages
|
||
because error messages can be issued when the defunS is
|
||
initially encountered, rather than when the OSS function defined is
|
||
used.
|
||
|
||
Although the lambda list keywords &optional and &key
|
||
are supported by defunS, it should be realized
|
||
that they are supported in the way they are supported by macros, not
|
||
the way they are supported by functions. In particular, when keywords
|
||
are used in a call on the OSS function being defined, they have to be
|
||
literal keywords rather than computed by an expression. In addition,
|
||
initialization forms cannot refer to the run-time values of other
|
||
arguments, because these are not available at macro-expansion-time.
|
||
They are also not allowed to refer to the macro-expansion-time values
|
||
of the other arguments. They must stand by themselves when computing
|
||
a value. A quote is inserted so that this value will
|
||
be computed at run-time rather than at macro-expansion-time. (In the
|
||
example above, (default nil) becomes (default 'nil).)
|
||
|
||
It may seem unduly restrictive that defunS does not support all
|
||
of the standard keywords in lambda-list. However, this is not
|
||
that much of a problem because defmacro can be used directly in
|
||
situations where these capabilities are desired. For example,
|
||
Tconcatenate is defined in terms of a more primitive OSS function
|
||
Tconcatenate2 as follows.
|
||
|
||
(defmacro Tconcatenate (Oitems1 Oitems2 &rest more-Oitems)
|
||
(if (null more-Oitems)
|
||
`(Tconcatenate2 ,Oitems1 ,Oitems2)
|
||
`(Tconcatenate2 ,Oitems1 (Tconcatenate ,Oitems2 .,more-Oitems))))
|
||
|
||
Using defmacro directly also makes it possible to define new
|
||
higher-order OSS functions. For example, an OSS function analogous
|
||
to substitute-if could be defined as follows. (The Eoss ensures
|
||
that newitem will only be evaluated once.)
|
||
|
||
(defmacro Osubstitute-if (newitem test items)
|
||
(let ((var (gensym)))
|
||
`(letS ((,var ,items))
|
||
(if (funcall ,test ,var) (Eoss :R ,newitem) ,var))))
|
||
|
||
(Osubstitute-if 3 #'minusp [1 -1 2 -3]) => [1 3 2 3]
|
||
|
||
Multiple Values
|
||
|
||
The OSS macro package supports multiple values in a number of contexts.
|
||
As discussed above, letS can be used to bind
|
||
variables to multiple values returned by an OSS function. Faculties are
|
||
also provided for defining OSS functions which return multiple values.
|
||
The support for multiple values is complicated by the fact that the OSS
|
||
macro package implements all communication of values by using
|
||
variables. As a result, it is not possible to support the standard
|
||
Common Lisp feature that multiple values can coexist with single
|
||
values without the programmer having to pay much attention to what is
|
||
going on. When using OSS expressions, the programmer has to be
|
||
explicit about how many values are being passed around.
|
||
|
||
valS &rest expr-list => &rest multiple-value-result
|
||
|
||
This is analogous to values except that it can operate on OSS
|
||
values. It takes in the values returned by n different expressions
|
||
and returns them as n multiple values. It enforces the restriction
|
||
that the values must either all be OSS values or all be non-OSS values.
|
||
The following example shows how a simple version of Eplist could be defined.
|
||
|
||
(defunS simple-Eplist (place)
|
||
(letS ((plist (EnumerateF place #'cddr #'null)))
|
||
(valS (car plist) (cadr plist))))
|
||
|
||
It is possible to use values in an OSS expression. However, the
|
||
results will be very different from the results obtained from using
|
||
valS. The values will be implicitly mapped like any other
|
||
non-OSS form. The value ultimately returned will be the single value
|
||
returned by TmapF.
|
||
|
||
(prognS (valS (Elist '(1 2)) (Elist '(3 4)))) => [1 2] [3 4]
|
||
|
||
(prognS (values (Elist '(1 2)) (Elist '(3 4))))
|
||
== (prognS (TmapF #'(lambda (x y) (values x y))
|
||
(Elist '(1 2)) (Elist '(3 4)))) => [1 2]
|
||
|
||
pass-valS n expr => &rest multiple-value-result
|
||
|
||
This function is used essentially as a declaration. It tells the OSS
|
||
macro package that the form expr returns n multiple values
|
||
which the programmer wishes to have preserved in the context of the OSS
|
||
expression. (This is needed, because Common Lisp does not provide any
|
||
compile-time way to determine the number of arguments that a function
|
||
will return.) The first example below enumerates a list of symbols
|
||
and returns a list of the internal symbols, if any, which correspond
|
||
to them. The second example defines a two valued OSS function which
|
||
locates symbols.
|
||
|
||
(letS* ((names (Elist '(zots Elist zorch)))
|
||
((symbols statuses) (pass-valS 2 (find-symbol (string names))))
|
||
(internal-symbols (Tselect (eq statuses :internal) symbols)))
|
||
(Rlist internal-symbols)) => (zots zorch)
|
||
|
||
(defunS find-symbols (names)
|
||
(declare (type oss names))
|
||
(pass-valS 2 (find-symbol (string names))))
|
||
|
||
(find-symbols [zots Elist zorch])
|
||
=> [zots Elist zorch] [:internal :inherited :internal]
|
||
|
||
The form pass-valS never has to be used in conjunction with
|
||
an OSS function, because the OSS macro package knows how many values
|
||
every OSS function returns. Similarly, pass-valS never has to be
|
||
used when multiple values are being bound by letS, because the
|
||
syntax of the letS indicates how many values are returned. (As a
|
||
result, the pass-valS in the first example above is not necessary.)
|
||
However, in situations such as the second example above,
|
||
pass-valS must be used.
|
||
|
||
Alteration of Values
|
||
|
||
The transformations introduced by the OSS macro package are inherently
|
||
antagonistic to the transformations introduced by the macro
|
||
setf. In particular, OSS function calls cannot be used as the
|
||
destination of a setf. In order to get around this problem, the
|
||
OSS macro package supports a separate construct which is in fact
|
||
more powerful than setf.
|
||
|
||
alterS destinations items => items
|
||
|
||
This form takes in a series of destinations and a series of items and stores
|
||
the items in the destinations. It returns the series of items. Like setf,
|
||
alterS cannot be applied to a destination unless there is an associated
|
||
definition for what should be done (see the discussion of alterableS in [6]).
|
||
The outputs of the predefined functions Elist, Ealist, Eplist, Efringe,
|
||
Evector, and Esequence are alterable. The effects of this alteration are
|
||
illustrated in conjunction with the descriptions of these functions. For
|
||
example, the following sets all of the elements in a list to nil.
|
||
|
||
(let ((list '((a . 1) (b . 2) (c . 3))))
|
||
(alterS (Elist list) nil)
|
||
list) => (nil nil nil)
|
||
|
||
As a related example, consider the following. Although setf
|
||
cannot be applied to an OSS function, it can be applied to a non-OSS
|
||
function in an OSS expression. In the example below, setf is
|
||
used to set the cdr of each element of a list to nil.
|
||
|
||
(let ((list '((a . 1) (b . 2) (c . 3))))
|
||
(prognS (setf (cdr (Elist list)) nil))
|
||
list) => ((a) (b) (c))
|
||
|
||
A key feature of alterS is that (in contrast to
|
||
setf) a structure can be altered by applying alterS to a
|
||
variable which contains enumerated elements of the structure. This is
|
||
useful because the old value in a structure can be used to decide what
|
||
new value should be put in the structure. (When alterS
|
||
is applied to such a variable it modifies the structure being
|
||
enumerated but does not change the value of the variable.)
|
||
|
||
(letS* ((v '#(1 2 3))
|
||
(x (Evector v)))
|
||
(alterS x (* x x))
|
||
(valS (Rlist x) v)) => (1 2 3) #(1 4 9)
|
||
|
||
Another interesting aspect of alterS is that it can be applied
|
||
to the outputs of a number of transducers. This is possible whenever
|
||
a transducer passes through unchanged a series of values taken from an
|
||
input which is itself alterable. This can happen with the transducers
|
||
Tuntil, TuntilF, Tcotruncate,
|
||
Tremove-duplicates, Tsubseries, Tselect, TselectF,
|
||
Tsplit, and TsplitF. For example, the following takes the
|
||
absolute value of the elements of a vector.
|
||
|
||
(letS* ((v '#(1 -2 3))
|
||
(x (TselectF #'minusp (Evector v))))
|
||
(alterS x (- x))
|
||
v) => #(1 2 3)
|
||
|
||
Debugging
|
||
|
||
The OSS macro package supports a number of features which are intended
|
||
to facilitate debugging. One example of this is the fact that the
|
||
macro package tries to use the variable names which are bound by a
|
||
letS in the code produced. Since the macro package is forced to
|
||
use variable renaming in order to implement variable scoping, it
|
||
cannot guarantee that these variable names will be used. However,
|
||
there is a high probability that they will. If a break occurs in the
|
||
middle of an OSS expression, these variables can be inspected in order
|
||
to determine what is going on. If a letS variable holds an OSS series,
|
||
then the variable will contain the current element of the series.
|
||
For example, the OSS expression below is transformed
|
||
into the loop shown. (For a discussion of how this transformation is
|
||
performed see [6].)
|
||
|
||
(letS* ((v (get-vector user))
|
||
(x (Evector v)))
|
||
(Rsum x))
|
||
|
||
(let (#:index-9 #:last-8 #:sum-2 x v)
|
||
(setq v (get-vector user))
|
||
(tagbody (setq #:index-9 -1)
|
||
(setq #:last-8 (length v))
|
||
(setq #:sum-2 0)
|
||
#:L-1 (incf #:index-9)
|
||
(if (not (< #:index-9 #:last-8)) (go oss:END))
|
||
(setq x (aref v #:index-9))
|
||
(setq #:sum-2 (+ #:sum-2 x))
|
||
(go #:L-1)
|
||
oss:END)
|
||
#:sum-2)
|
||
|
||
showS thing &optional (format "~%~S") (stream *standard-output*) => thing
|
||
|
||
This function is convenient for printing out debugging information
|
||
while an OSS expression is being evaluated. It can be wrapped around
|
||
any expression no matter whether it produces an OSS value or a non-OSS
|
||
value without disturbing the containing expression. The function
|
||
prints out the value and then returns it. If the value is a non-OSS
|
||
thing, it will be printed out once at the time it is created. If it
|
||
is an OSS series thing, it will be printed out an element at a time.
|
||
The format can be used to print a tag in order to identify the
|
||
value being shown.
|
||
|
||
(showS format stream)
|
||
== (let ((x thing)) (format stream format x) x)
|
||
|
||
(letS ((x (Elist '(1 2 3))))
|
||
(Rsum (showS x "Item: ~A, "))) => 6
|
||
;;The output "Item: 1, Item: 2, Item: 3, " is printed.
|
||
|
||
*permit-non-terminating-oss-expressions*
|
||
|
||
On the theory that non-terminating loops are seldom desired, the
|
||
OSS macro package checks each loop constructed to see if it can
|
||
terminate. If this control variable is nil (which is the
|
||
default), then a warning message is issued for each loop which the OSS
|
||
macro package thinks has no possibility of terminating. This is
|
||
useful in the first example below, but not in the second. The form
|
||
compiler-let can be used to bind this control variable to
|
||
T around such an expression.
|
||
|
||
(Rlist 4) ;Signals warning 15
|
||
|
||
(block bar ;Signals warning 15
|
||
(letS ((x (Eup :by 10)))
|
||
(if (> x 15) (return-from bar x)))) => 20
|
||
|
||
(compiler-let ((*permit-non-terminating-oss-expressions* T))
|
||
(block bar
|
||
(letS ((x (Eup :by 10)))
|
||
(if (> x 15) (return-from bar x))))) => 20
|
||
|
||
*last-oss-loop*
|
||
|
||
This variable contains the loop most recently produced by the OSS macro
|
||
package. After evaluating (or macro-expanding) an OSS expression,
|
||
this variable can be inspected in order to see the code which was produced.
|
||
|
||
*last-oss-error*
|
||
|
||
This variable contains the most recently printed warning or error message
|
||
produced by the OSS macro package. The information in this variable
|
||
can be useful for tracking down errors.
|
||
|
||
Side-Effects
|
||
|
||
The OSS macro package works by converting each OSS expression into a
|
||
loop. This allows the expressions to be evaluated very efficiently,
|
||
but radically changes the order in which computations are performed.
|
||
In addition, off-line ports are supported by code motion. Given all
|
||
of these changes, it is not surprising that OSS expressions are
|
||
primarily intended to be used in situations where there are no
|
||
side-effects. Due to the change in computation order, it can be
|
||
hard to figure out what the result of a side-effect will be.
|
||
|
||
Nevertheless, since side-effects (particularly in the form of input
|
||
and output) are an inevitable part of programming, several steps are
|
||
taken in order to make the behavior of OSS expressions containing
|
||
side-effect operations as easy to understand as possible. First, when
|
||
implicit mapping is applied, it is applied to as large a subexpression
|
||
as possible. This makes it straightforward to understand the
|
||
interaction of the side-effects within a single mapped subexpression.
|
||
Several examples of this are given in the section above which discusses
|
||
implicit mapping.
|
||
|
||
Second, wherever possible, the OSS macro package leaves the order of
|
||
evaluation of the OSS functions in an expression unchanged. Each
|
||
function is evaluated incrementally an element at a time, but on each
|
||
cycle, the processing follows the syntactic ordering of the functions
|
||
in the expression.
|
||
|
||
The one place where order changes are required is when handling
|
||
off-line ports. However, things are simplified here by ensuring that
|
||
the evaluation order implied by the order of the inputs of an off-line
|
||
function is preserved.
|
||
|
||
Third, when determining whether or not each termination point is
|
||
connected to every output in each on-line subexpression, functions
|
||
whose outputs are not used for anything are considered to be outputs
|
||
of the subexpression. The reasoning behind this is that if the
|
||
outputs are not used for anything, then the function must be being
|
||
used for side-effect and probably matters that the function get
|
||
evaluated the full number of times it should be. For example,
|
||
consider the expressions below. The first expression prints out the
|
||
numbers in a list and returns the first negative number. The second
|
||
expression signals a warning and the enumeration of the list is
|
||
duplicated so that the princ will be applied to all of the
|
||
elements of the list.
|
||
|
||
(letS* ((x (Elist '(1 2 3 -4 5))))
|
||
(princ x)
|
||
(Rfirst-passive (TselectF #'minusp x))) => -4
|
||
;;The output "123-45" printed.
|
||
|
||
(letS* ((x (Elist '(1 2 3 -4 5)))) ;Signals warning 18
|
||
(princ x)
|
||
(Rfirst (TselectF #'minusp x))) => -4
|
||
;;The output "123-45" printed.
|
||
|
||
3. Bibliography
|
||
|
||
[1] A. Aho, J. Hopcraft, and J. Ullman, The Design and
|
||
Analysis of Computer Algorithms, Addison-Wesley, Reading MA, 1974.
|
||
|
||
[2] G. Burke and D. Moon, Loop Iteration Macro, MIT/LCS/TM-169,
|
||
July 1980.
|
||
|
||
[3] R. Polivka and S. Pakin, APL: The Language and Its Usage,
|
||
Prentice-Hall, Englewood Cliffs NJ, 1975.
|
||
|
||
[4] G. Steele Jr., Common Lisp: the Language, Digital Press,
|
||
Maynard MA, 1984.
|
||
|
||
[5] R. Waters, "A Method for Analyzing Loop Programs",
|
||
IEEE Trans. on Software Engineering, 5(3):237--247, May 1979.
|
||
|
||
[6] R. Waters, Synchronizable Series Expressions: Part II: Overview of
|
||
the Theory and Implementation, MIT/AIM-959, November 1987
|
||
|
||
[7] Lisp Machine Documentation for Genera 7.0, Symbolics, Cambridge MA, 1986.
|
||
|
||
4. Warning and Error Messages
|
||
|
||
In order to facilitate the debugging of OSS expressions, this section
|
||
discusses the various warning and error messages which can be issued
|
||
by the OSS macro package while processing the functions described in
|
||
this document. Error messages describe problems in OSS expressions
|
||
which make it impossible to process the expression correctly. Warning
|
||
messages identify less serious situations which are worthy of
|
||
programmer scrutiny, but which do not prevent the expression from
|
||
being processed in a way which is, at least probably, correct.
|
||
|
||
Warning and error messages are both printed out in the following
|
||
format. Error messages (as opposed to warnings) can be identified by
|
||
the fact that the word "Error" precedes the message number.
|
||
(The format is shown as it appears on the Symbolics Lisp machine and
|
||
may differ in minor ways in other systems.)
|
||
|
||
Warning: {Error} message-number in OSS expression:
|
||
containing OSS expression
|
||
detailed message
|
||
|
||
For example, the following error message might be printed.
|
||
|
||
Warning: Error 1.1 in OSS expression:
|
||
(LETS ((X (ELIST NUMBER-LIST))
|
||
(Y (EUP (CAR HEADER) :TO 4 :LENGTH 5)))
|
||
(RLIST (LIST Y X)))
|
||
Too many keywords specified in a call on Eup:
|
||
(EUP (CAR HEADER) :TO 4 :LENGTH 5)
|
||
|
||
The first line of each message specifies the number of the warning or
|
||
error. This number is useful for looking up further information in
|
||
the documentation below. The next part of the message shows the
|
||
complete OSS expression which contains the problem. This makes it easier
|
||
to locate the problem in a program. The remainder of the message
|
||
describes the particular problem in detail. (The variable
|
||
*last-oss-error* contains a list of the information which was used to
|
||
print out the most recent warning or error message.)
|
||
|
||
The OSS macro package reports problems using warn so that
|
||
processing of other parts of a program can continue, potentially
|
||
finding other problems. However, each time an OSS error (as opposed to
|
||
a warning) is detected, the OSS macro package skips over the rest of
|
||
the OSS expression without performing any additional checks. Therefore,
|
||
even if there are several OSS errors in an OSS expression, only one OSS
|
||
error will be reported. When an OSS error is found, a dummy value is
|
||
inserted in place of the erroneous OSS expression. As a result, it is
|
||
virtually impossible for the containing program to run correctly.
|
||
|
||
The documentation below describes each of the messages which the
|
||
OSS macro package can produce. Each description begins with a header
|
||
line containing a schematic rendition of the message. Italics is used
|
||
to indicate pieces of specific information which are inserted in the
|
||
message. The number of the warning or error is shown in the left
|
||
margin at the beginning of the header. For ease of reference, the
|
||
messages are described in numerical order.
|
||
|
||
Local errors concerning single OSS functions.
|
||
The following error messages report errors which are local in that
|
||
they stem purely from the improper use of a single OSS function. These
|
||
errors cover only a few special situations. Many (if not most) local
|
||
errors are reported directly by the standard Common Lisp processor
|
||
rather than by the OSS macro package. For example, if an OSS function
|
||
is used with the wrong number of arguments, an error message is issued
|
||
by the standard macro expander.
|
||
|
||
1.1 Error: Too many keywords specified in call on Eup: call
|
||
|
||
1.2 Error: Too many keywords specified in call on Edown: call
|
||
|
||
1.3 Error: Too many keywords specified in call on Tlatch: call
|
||
|
||
Each of these errors specifies that incompatible keywords have been
|
||
provided for the indicated function. The entire function call is
|
||
printed out as shown above.
|
||
|
||
2 Error: Invalid enumerator arg to TconcatenateF: enumerator
|
||
|
||
This error is issued if the enumerator argument to TconcatenateF
|
||
fails to be an enumerator---i.e., fails to be an OSS function that has
|
||
no OSS inputs, at least one OSS output, and which can terminate.
|
||
|
||
3 Error: Unsupported &-keyword keyword in defunS arglist.
|
||
|
||
This error is issued if an &-keyword other than &optional or &key appears in
|
||
the argument list of defunS. Other keywords have to be supported by using
|
||
defmacro directly. (See the discussion of defunS.)
|
||
|
||
4 Error: AlterS applied to an unalterable form: call
|
||
|
||
This error is issued if alterS is applied to a value which is not alterable.
|
||
Values are alterable only if they come directly from an enumerator which has an
|
||
alterable value, or come indirectly from such an enumerator via one or more
|
||
transducers which allow alterability to pass through.
|
||
|
||
5 Error: Malformed lambdaS argument arg.
|
||
|
||
This error message is issued if an argument of a lambdaS fails to be a valid
|
||
variable. In particular, it is issued if the argument, is not a symbol, is T
|
||
or nil, is a symbol in the keyword package, or is an &-keyword. (It is also
|
||
erroneous for such a variable to be declared special. However, this error is
|
||
only reported on the Symbolics Lisp Machine.)
|
||
|
||
6 Error: LambdaS used in inappropriate context: call
|
||
|
||
This error message is issued if a lambdaS ends up (after macro expansion of the
|
||
surrounding code) being used in any context other than as the quoted first
|
||
argument of a funcallS.
|
||
|
||
7 Error: Wrong number of args to funcallS: call
|
||
|
||
This error message is issued if a use of funcallS does not contain a number of
|
||
arguments which is compatible with the number of arguments expected by the OSS
|
||
functional argument.
|
||
|
||
8 Error: Only n return values present where m expected: call
|
||
|
||
This error message is issued if an OSS function is used in a situation where it
|
||
is expected to return more values than it actually does---for example, if a
|
||
letS tries to bind two values from an OSS function which only returns one, or
|
||
pass-valS tries to obtain two values from an OSS function which only returns
|
||
one. (Non-OSS functions return extra values of nil if they are requested to
|
||
produce more values than they actually do.)
|
||
|
||
Warnings and errors concerning OSS variables.
|
||
The following warnings and errors concern the creation and use of letS and
|
||
lambdaS variables. Like the errors above, they are quite local in nature and
|
||
relatively easy to fix.
|
||
|
||
9 Error: Malformed letS{*} binding pair pair.
|
||
|
||
This error message is issued if a letS or letS* binding pair fails to be either
|
||
a list of a valid variable and a value, or a list of a list of valid variables
|
||
and a value. The criterion for what makes a variable valid is the same as the
|
||
one used in Error 5, except that a binding pair can contain nil instead of a
|
||
variable.
|
||
|
||
10 Warning: The variable(s) vars declared TYPE OSS in a letS{*}.
|
||
|
||
This warning message is issued if one or more variables in a letS are
|
||
explicitly declared to be of type oss. The explicit declarations are ignored.
|
||
|
||
11 Warning: The letS{*} variable variable is unused in: call
|
||
|
||
This warning message is issued if a variable in a letS is never referenced in
|
||
the body of the letS. Note that these variables cannot be referenced inside a
|
||
nested lambda or lambdaS.
|
||
|
||
12 Error: The letS{*} variable var setqed.
|
||
|
||
This error message is issued if a letS variable (either OSS or non-OSS) is
|
||
assigned to in the body of a letS. It is also issued if any of the variables
|
||
bound by a lambdaS or defunS are assigned to.
|
||
|
||
Non-local warnings and errors concerning complete OSS expressions. The
|
||
following warnings and errors concern non-local problems in OSS expressions.
|
||
The first two are discussed in further detail in the section on implicit
|
||
mapping.
|
||
|
||
13 Warning: Decomposition moves: code out of a binding scope: surround
|
||
|
||
This warning is issued if the processing preparatory to implicit mapping causes
|
||
a subexpression to be moved out of the binding scope for one of the variables
|
||
in it. The problem can be fixed by using letS to create the binding scope, or
|
||
by moving the binding form so that it surrounds the entire OSS expression.
|
||
(The testing for this problem is somewhat approximate in nature. It can miss
|
||
some erroneous situations and can complain in some situations where there is no
|
||
problem. Due to this latter difficulty, the OSS macro package merely issues a
|
||
warning message rather than issuing an error message.)
|
||
|
||
14 Error: OSS value carried to non-OSS input by data flow from: call to: call
|
||
|
||
As illustrated below, this error is issued whenever data flow connects an OSS
|
||
output to a non-OSS input of an OSS function as in the example below. (If the
|
||
expression in question is intended to contain a nested loop, the error can be
|
||
fixed by wrapping the nested portion in a mapS.)
|
||
|
||
Warning: Error 14 in OSS expression:
|
||
(Rlist (Rlist (Elist (Elist '((1 2) (3 4))))))
|
||
OSS value carried to non-OSS input by data flow from:
|
||
(Elist '((1 2) (3 4)))
|
||
to:
|
||
(Elist (Elist '((1 2) (3 4))))
|
||
|
||
The error message prints out two pieces of code in order to indicate the source
|
||
and destination of the data flow in question. The outermost part of the first
|
||
piece of code shows the function which creates the value in question. The
|
||
outermost function in the second piece of code shows the function which
|
||
receives the value. (Entire subexpressions are printed in order to make it
|
||
easier to locate the functions in question within the OSS expression as a
|
||
whole.) If nesting of expressions is used to implement the data flow, then the
|
||
first piece of code will be nested in the second one.
|
||
|
||
15 Warning: Non-terminating OSS expression: expr
|
||
|
||
This warning message is issued whenever a complete OSS expression appears
|
||
incapable of terminating. The expression in question is printed. It may well
|
||
be only a subexpression of the OSS expression being processed. A warning
|
||
message is issued instead of an error message, because the expression may in
|
||
fact be capable of terminating or the expression might not be intended to
|
||
terminate. (This warning message can be turned off by using the variable
|
||
*permit-non-terminating-oss-expressions*.)
|
||
|
||
Warnings concerning the violation of restrictions.
|
||
The following warnings are issued when an OSS expression violates one of the
|
||
isolation restrictions or the requirement that within each on-line
|
||
subexpression, there must be a data flow path from each termination point to
|
||
each output. In each case, the violation is automatically fixed by the macro
|
||
package. However, in order to achieve high efficiency, the user should fix the
|
||
violation explicitly rather than relying on the automatic fix.
|
||
|
||
16 Warning: Non-isolated non-oss data flow from: call to: call
|
||
|
||
This warning is issued if an OSS expression violates the non-OSS data flow
|
||
isolation restriction. As shown below, the message prints out two pieces of
|
||
code which indicate the data flow in question.
|
||
|
||
Warning: 16 in OSS expression:
|
||
(LETS* ((NUMS (EVECTOR '#(3 2 8)))
|
||
(TOTAL (REDUCEF 0 #'+ NUMS)))
|
||
(RVECTOR (/ NUMS TOTAL)))
|
||
Non-isolated non-OSS data flow from:
|
||
(REDUCEF 0 #'+ NUMS)
|
||
to:
|
||
(/ NUMS TOTAL)
|
||
|
||
The OSS macro package automatically fixes the isolation restriction violation
|
||
by duplicating subexpressions until the data flow in question becomes isolated.
|
||
(In the example above, the vector enumeration gets copied.) However, the macro
|
||
package is not guaranteed to minimize the amount of code copied. In addition,
|
||
it is sometimes possible for a programmer to fix an expression much more
|
||
efficiently without using any code copying. As a result, it is advisable for
|
||
programmers to fix these violations explicitly, rather than relying on the
|
||
automatic fixes provided by the OSS macro package.
|
||
|
||
17.1 Warning: Non-isolated oss input at the end of
|
||
the data flow from: call to: call
|
||
|
||
17.2 Warning: Non-isolated oss output at the start of
|
||
the data flow from: call to: call
|
||
|
||
One of these warnings is issued if an OSS expression violates the off-line port
|
||
isolation restriction. The warning message prints out two pieces of code which
|
||
indicate a data flow which ends (or starts) on the port in question. Code
|
||
copying is automatically applied in order to fix the violation. It is
|
||
worthwhile to try and think of a more efficient way to fix the violation. As
|
||
with Warning 16, even if code copying is the only thing which can be done, it
|
||
is better for the programmer to do this explicitly.
|
||
|
||
18 Warning: No data flow path from the termination point: call
|
||
to the output: call
|
||
|
||
This warning is issued if a termination point in an on-line subexpression of an
|
||
OSS expression is not connected by data flow to one of the outputs. Code
|
||
copying is automatically applied in order to fix the violation. (However, the
|
||
OSS macro package has a tendency to copy a good deal more code than necessary.)
|
||
The violation can often be fixed much more efficiently by using
|
||
non-early-terminating OSS functions instead of early-terminating functions or
|
||
by using Tcotruncate to indicate relationships between inputs.
|
||
|
||
Errors concerning implementation limitations.
|
||
These errors reflect limitations of the way the OSS macro package is
|
||
implemented rather than anything fundamental about OSS expressions.
|
||
|
||
19 Error: LambdaS body too complex to merge into a single unit: forms
|
||
|
||
In general, the OSS macro package is capable of combining together any kind of
|
||
permissible OSS expression. In particular, there is never a problem as long as
|
||
the expression as a whole does not have any OSS inputs or OSS outputs.
|
||
However, in the body of a lambdaS, it is possible to write OSS expressions
|
||
which have both OSS inputs and OSS outputs. If such an expression has a data
|
||
flow path from an OSS input to an OSS output which contains a non-OSS data flow
|
||
arc, then this error message is issued. For example, the error would be issued
|
||
in the situation below.
|
||
|
||
(funcallS #'(lambdaS (items) ;Signals error 19
|
||
(declare (type oss items))
|
||
(Elist (Rlist items)))
|
||
...)
|
||
|
||
An error message is issued in the situation above, because the situation is
|
||
unlikely to occur and there is no way to support the situation without
|
||
resorting to very peculiar code. In particular, the input items in the example
|
||
above would have to be converted into an off-line input.
|
||
|
||
20 Error: The form function not allowed in OSS expressions.
|
||
|
||
In general, the OSS macro package has a sufficient understanding of special
|
||
forms to handle them correctly when they appear in an OSS expression. However,
|
||
it does not handle the forms compiler-let, flet, labels, or macrolet. The
|
||
forms compiler-let and macrolet would not be that hard to handle, however it
|
||
does not seem worth the effort. The forms flet and labels would be hard to
|
||
handle, because the OSS macro package does not preserve binding scopes and
|
||
therefore does not have any obvious place to put them in the code it produces.
|
||
All four forms can be used by simply wrapping them around entire OSS
|
||
expressions rather than putting them in the expressions.
|
||
|
||
21--27 Documentation for these errors appears in [6].
|
||
|
||
5. Index of Functions
|
||
|
||
This section is an index and concise summary of the functions, variables, and
|
||
special forms described in this document. Each entry shows the inputs and
|
||
outputs of the function, the page where documentation can be found, and a one
|
||
line description.
|
||
|
||
The names of OSS functions often start with one of the following prefix
|
||
letters.
|
||
|
||
E Enumerator.
|
||
T Transducer.
|
||
R Reducer.
|
||
|
||
Occasionally, a name will end with one of the following suffix letters.
|
||
|
||
S Special form.
|
||
F Function that takes functional arguments.
|
||
|
||
In addition, the argument and result names indicate data type restrictions
|
||
(e.g., number indicates that an argument must be a number, item indicates that
|
||
there is no type restriction). Plural names are used iff the value in question
|
||
is an OSS series (e.g., numbers indicates an OSS series of numbers; items
|
||
indicates an OSS series of unrestricted values). The name of a series input or
|
||
output begins with "O" iff it is off-line.
|
||
|
||
alterS destinations items => items
|
||
Alters the values in destinations to be items.
|
||
|
||
defunS name lambda-list {doc} {decl}* &body expr-list
|
||
Defines an OSS function, see lambdaS.
|
||
|
||
Ealist alist &optional (test #'eql) => keys values
|
||
Creates two series containing the keys and values in an alist.
|
||
|
||
Edown &optional (start 0) &key (:by 1) :to :above :length => numbers
|
||
Creates a series of numbers by counting down from start by :by.
|
||
|
||
Efile name => items
|
||
Creates a series of the forms in the file named name.
|
||
|
||
Efringe tree &optional (leaf-test #'atom) => leaves
|
||
Creates a series of the leaves of a tree.
|
||
|
||
Ehash table => keys values
|
||
Creates two series containing the keys and values in a hash table.
|
||
|
||
Elist list &optional (end-test #'endp) => elements
|
||
Creates a series of the elements in a list.
|
||
|
||
EnumerateF init step &optional test => items
|
||
Creates a series by applying step to init until test returns non-null.
|
||
|
||
Enumerate-inclusiveF init step test => items
|
||
Creates a series containing one more element than EnumerateF.
|
||
|
||
Eoss &rest expr-list => items
|
||
Creates a series of the results of the expressions.
|
||
|
||
Eplist plist => indicators values
|
||
Creates two series containing the indicators and values in a plist.
|
||
|
||
Esequence sequence &optional (indices (Eup)) => elements
|
||
Creates a series of the elements in a sequence.
|
||
|
||
Esublists list &optional (end-test #'endp) => sublists
|
||
Creates a series of the sublists in a list.
|
||
|
||
Esymbols &optional (package *package*) => symbols
|
||
Creates a series of the symbols in package.
|
||
|
||
Etree tree &optional (leaf-test #'atom) => nodes
|
||
Creates a series of the nodes in a tree.
|
||
|
||
Eup &optional (start 0) &key (:by 1) :to :below :length => numbers
|
||
Creates a series of numbers by counting up from start by :by.
|
||
|
||
Evector vector &optional (indices (Eup)) => elements
|
||
Creates a series of the elements in a vector.
|
||
|
||
funcallS function &rest expr-list => result
|
||
Applies an OSS function to the results of the expressions.
|
||
|
||
lambdaS var-list {decl}* &body expr-list
|
||
Form for specifying literal OSS functions.
|
||
|
||
*last-oss-error*
|
||
Variable containing a description of the last OSS warning or error.
|
||
|
||
*last-oss-loop*
|
||
Variable containing the loop the last OSS expression was converted into.
|
||
|
||
letS var-value-pair-list {decl}* &body expr-list => result
|
||
Binds OSS variables in parallel.
|
||
|
||
letS* var-value-pair-list {decl}* &body expr-list => result
|
||
Binds OSS variables sequentially.
|
||
|
||
mapS &body expr-list => items
|
||
Causes expr-list to be mapped over the OSS variables in it.
|
||
|
||
oss-tutorial-mode &optional (T-or-nil T) => state-of-tutorial-mode
|
||
If called with an argument of T, turns tutorial mode on.
|
||
|
||
pass-valS n expr => &rest multiple-value-result
|
||
Used to pass multiple values from a non-OSS function into an OSS expression.
|
||
|
||
*permit-non-terminating-oss-expressions*
|
||
When non-null, inhibits error messages about non-terminating OSS expressions.
|
||
|
||
prognS &body expr-list => result
|
||
Delineates an OSS expression.
|
||
|
||
Ralist keys values => alist
|
||
Combines a series of keys and a series of values together into an alist.
|
||
|
||
Rand bools => bool
|
||
Computes the and of the elements of bools, terminating early.
|
||
|
||
Rand-late bools => bool
|
||
Computes the and of the elements of bools.
|
||
|
||
Rappend lists => list
|
||
Appends the elements of lists together into a single list.
|
||
|
||
Rbag items => list
|
||
Combines the elements of items together into an unordered list.
|
||
|
||
ReduceF init function items => result
|
||
Computes a cumulative value by applying function to the elements of items.
|
||
|
||
Rfile name items &rest option-plist => T
|
||
Prints the elements of items into a file.
|
||
|
||
Rfirst items &optional (default nil) => item
|
||
Returns the first element of items, terminating early.
|
||
|
||
Rfirst-late items &optional (default nil) => item
|
||
Returns the first element of items.
|
||
|
||
Rhash keys values &rest option-plist => table
|
||
Combines a series of keys and a series of values together into a hash table.
|
||
|
||
Rlast items &optional (default nil) => item
|
||
Returns the last element of items.
|
||
|
||
Rlength items => number
|
||
Returns the number of elements in items.
|
||
|
||
Rlist items => list
|
||
Combines the elements of items together into a list.
|
||
|
||
Rmax numbers => number
|
||
Returns the maximum element of numbers.
|
||
|
||
Rmin numbers => number
|
||
Returns the minimum element of numbers.
|
||
|
||
Rnconc lists => list
|
||
Destructively appends the elements of lists together into a single list.
|
||
|
||
Rnth n items &optional (default nil) => item
|
||
Returns the nth element of items, terminating early.
|
||
|
||
Rnth-late n items &optional (default nil) => item
|
||
Returns the nth element of items.
|
||
|
||
Ror bools => bool
|
||
Computes the or of the elements of bools, terminating early.
|
||
|
||
Ror-late bools => bool
|
||
Computes the or of the elements of bools.
|
||
|
||
Rplist indicators values => plist
|
||
Combines a series of indicators and a series of values together into a plist.}
|
||
|
||
Rsum numbers => number
|
||
Computes the sum of the elements in numbers.
|
||
|
||
Rvector items &key (:size 32) &rest option-plist => vector
|
||
Combines the elements of items together into a vector.
|
||
|
||
showS thing &optional (format "~%~S") (stream *standard-output*) => thing
|
||
Displays thing for debugging purposes.
|
||
|
||
Tchunk amount Oitems => lists
|
||
Creates a series of lists of length amount of non-overlapping subseries of Oitems.
|
||
|
||
Tconcatenate Oitems1 Oitems2 &rest more-Oitems => items
|
||
Concatenates two or more series end to end.
|
||
|
||
TconcatenateF Enumerator Oitems => items
|
||
Concatenates the results of applying Enumerator to the elements of Oitems.
|
||
|
||
Tcotruncate items &rest more-items => initial-items &rest more-initial-items
|
||
Truncates all the inputs to the length of the shortest input.
|
||
|
||
Texpand bools Oitems &optional (default nil) => items
|
||
Spreads the elements of items out into the indicated positions.
|
||
|
||
Tlastp Oitems => bools items
|
||
Determines which element of the input is the last.
|
||
|
||
Tlatch items &key :after :before :pre :post => masked-items
|
||
Modifies a series before or after a latch point.
|
||
|
||
TmapF function &rest items-list => items
|
||
Maps function over the input series.
|
||
|
||
Tmask Omonotonic-indices => bools
|
||
Creates a series continuing T in the indicated positions.
|
||
|
||
Tmerge Oitems1 Oitems2 comparator => items
|
||
Merges two series into one.
|
||
|
||
Tpositions Obools => indices
|
||
Returns a series of the positions of non-null elements in Obools.
|
||
|
||
Tprevious items &optional (default nil) (amount 1) => shifted-items
|
||
Shifts items to the right by amount inserting default.
|
||
|
||
Tremove-duplicates Oitems &optional (comparator #'eql) => items
|
||
Removes the duplicate elements from a series.
|
||
|
||
TscanF {init} function items => results
|
||
Computes cumulative values by applying function to the elements of items.
|
||
|
||
Tselect bools &optional items => Oitems
|
||
Selects the elements of items corresponding to non-null elements of bools.
|
||
|
||
TselectF pred Oitems => items
|
||
Selects the elements of Oitems for which pred is non-null.
|
||
|
||
Tsplit items bools &rest more-bools => Oitems1 Oitems2 &rest more-Oitems
|
||
Divides a series into multiple outputs based on bools.
|
||
|
||
TsplitF items pred &rest more-pred => Oitems1 Oitems2 &rest more-Oitems
|
||
Divides a series into multiple outputs based on pred.
|
||
|
||
Tsubseries Oitems start &optional below => items
|
||
Returns the elements of Oitems from start up to, but not including, below.
|
||
|
||
Tuntil bools items => initial-items
|
||
Returns items up to, but not including, the first non-null element of bools.
|
||
|
||
TuntilF pred items => initial-items
|
||
Returns items up to, but not including, the first element which satisfies pred.
|
||
|
||
Twindow amount Oitems => lists
|
||
Creates a series of lists of length amount of successive overlapping subseries.
|
||
|
||
type oss &rest variable-list
|
||
Declaration used to specify that variables are OSS variables.
|
||
|
||
valS &rest expr-list => &rest multiple-value-result
|
||
Returns multiple series values.
|