Common Lisp the Language, 2nd Edition
A key feature of Lisp is that variable declarations are strictly optional. Nevertheless, it is often the case that they are necessary in situations where efficiency matters. Therefore, it is important that it be possible for programmers to provide declarations for every variable in a program. The transformation of series expressions into loops presents certain problems in this regard, because the loops created contain variables not evident in the original code. However, if the information described below is supplied by the user, appropriate declarations can be generated for all of the loop variables created.
All the explicit variables that are bound in a series expression (for example, by a let that is part of the expression) should be given informative declarations making use of the type specifier (series element-type) where appropriate.
Informative types should be supplied to series functions (such as scan and map-fn) that have type arguments. When using scan it is important to specify the type of element in the sequence as well as the sequence itself (for example, by using (vector * integer) as opposed to merely vector). The form (list element-type) can be used to specify the type of elements in a list.
If it is appropriate to have a type more specific than (series t) associated with the output of #M, #Z, scan-alist, scan-file, scan-hash, scan-lists-of-lists-fringe, scan-lists-of-lists, scan-plist, series, latch, or catenate, then the form the must be used to specify this type.
Finally, if the expression computing a non-series argument to a series variable is neither a variable nor a constant, the must be used to specify the type of its result.
For example, the declarations in the series expressions below are sufficient to ensure that every loop variable will have an accurate declaration.
(collect-last (choose-if #'plusp (scan '(list integer) data))) (collect '(vector * float) (map-fn 'float #'/ (series (the integer (car data))) (the (series integer) (scan-file f))))
The amount of information the user has to provide is reduced by the fact that this information can be propagated from place to place. For instance, the variable holding the output of choose-if holds a subset of the elements held by the input variable. As a result, it is appropriate for it to have the same type. When defining a new series function, the type specifier series-element-type can be used to indicate where type propagation should occur.
The type specifier (series-element-type variable) denotes the type of elements in the series held in variable. Variable must be a variable carrying a series value (for example, a series argument of a series function). series-element-type can be used only in three places: in a declaration in a let, mapping, producing, or other binding form in a series expression; in a declaration in a defun being used to define a series function; or in a type argument to a series function. As an example, consider that collect-last could have been defined as follows. The use of series-element-type ensures that the internal variable keeping track of the most recent item has the correct type.
(defun collect-last (items &optional (default nil)) (declare (optimizable-series-function)) (collect-fn '(series-element-type items) #'(lambda () default) #'(lambda (old new) new) items))