Ниже представлен список спецификаторов деклараций для использования в declare.
Следует отметить, что по правилам хорошего тона, имена специальных переменных окружаются звёздочками.
Декларация special не охватывает все связывания. Внутренние связывания переменных неявно скрывают декларацию special и должны быть явно передекларированы в специальные. (Однако прокламация special имеет глобальный эффект. Это сделано для совместимости с MacLisp’ом.) Например:
В приведенном выше коде, внешние и внутренние связывания переменной y являются специальными и, таким образом, действуют в динамической области видимости. Однако среднее связывание действует в лексической области видимости. Два аргумента + различаются, один содержит значение 3 лексически связанной переменной y, другой содержит значение специальной переменной y. Все связывания и переменной x и ссылки на неё является специальными, так как в коде использовалась прокламация special.
В целях стиля, необходимо избегать использование прокламаций special. Для объявления специальных переменных предназначены макросы defvar и defparameter.
X3J13 voted in January 1989 to alter the interpretation of type declarations. They are not to be construed to affect “only variable bindings.” The new rule for a declaration of a variable to have a specified type is threefold:
One may think of a type declaration (declare (type face bodoni)) as implicitly changing every reference to bodoni within the scope of the declaration to (the face bodoni); changing every expression exp assigned to bodoni within the scope of the declaration to (the face exp); and implicitly executing (the face bodoni) every time execution enters the scope of the declaration.
These new rules make type declarations much more useful. Under first edition rules, a type declaration was useless if not associated with a variable binding; declarations such as in
at best had no effect and at worst were erroneous, depending on one’s interpretation of the first edition. Under the interpretation approved by X3J13, such declarations have “the obvious natural interpretation.”
X3J13 noted that if nested type declarations refer to the same variable, then all of them have effect; the value of the variable must be a member of the intersection of the declared types.
Nested type declarations could occur as a result of either macro expansion or carefully crafted code. There are three cases. First, the inner type might be a subtype of the outer one:
This is the case most likely to arise in code written completely by hand.
Second, the outer type might be a subtype of the inner one. In this case the inner declaration has no additional practical effect, but it is harmless. This is likely to occur if code declares a variable to be of a very specific type and then passes it to a macro that then declares it to be of a less specific type.
Third, the inner and outer declarations might be for types that overlap, neither being a subtype of the other. This is likely to occur only as a result of macro expansion. For example, user code might declare a variable to be of type integer, and a macro might later declare it to be of type (or fixnum package); in this case a compiler could intersect the two types to determine that in this instance the variable may hold only fixnums.
The reader should note that the following code fragment is, perhaps astonishingly, not in error under the interpretation approved by X3J13:
The variable maxwell is declared to be an integer over the scope of the type declaration, not over its extent. Indeed maxwell takes on the non-integer value .007 while the Trapp family make their escape, but because no reference to maxwell within the scope of the declaration ever produces a non-integer value, the code is correct.
Now the assignment to maxwell during the first call to spy-swap, and the reference to maxwell during the second call, do involve non-integer values, but they occur within the body of spy-swap, which is not in the scope of the type declaration! One could put the declaration in a different place so as to include spy-swap in the scope:
and then the code is indeed in error.
X3J13 also voted in January 1989 to alter the meaning of the function type specifier when used in type declarations (see section 4.5).
Observe that this covers the particularly common case of declaring numeric variables:
In many implementations there is also some advantage to declaring variables to have certain specialized vector types such as base-string.
Следует отметить, что соблюдаются правила лексической области видимости. Если одна из указанных функций лексически ограничена (с помощью flet или labels), то декларация применится для локального определения, а не для глобального.
X3J13 voted in March 1989 to extend ftype declaration specifiers to accept any function-name (a symbol or a list whose car is setf—see section 7.1). Thus one may write
to indicate the type of the setf expansion function for cadr.
X3J13 voted in January 1989 to alter the meaning of the function type specifier when used in ftype declarations (see section 4.5).
X3J13 voted in January 1989 to remove this interpretation of the function declaration specifier from the language. Instead, a declaration specifier
is to be treated simply as an abbreviation for
just as for all other symbols appearing in table 4.1.
X3J13 noted that although function appears in table 4.1, the first edition also discussed it explicitly, with a different meaning, without noting whether the differing interpretation was to replace or augment the interpretation regarding table 4.1. Unfortunately there is an ambiguous case: the declaration
can be construed to abbreviate either
or
The latter could perhaps be rejected on semantic grounds: it would be an error to declare nil, a constant, to be of type function. In any case, X3J13 determined that the ice was too thin here; the possibility of confusion is not worth the convenience of an abbreviation for ftype declarations. The change also makes the language more consistent.
Следует отметить, что соблюдаются правила лексической области видимости. Если одна из указанных функций лексически ограничена (с помощью flet или labels), то декларация применится для локального определения, а не для глобального.
X3J13 voted in October 1988 to clarify that during compilation the inline declaration specifier serves two distinct purposes: it indicates not only that affected calls to the specified functions should be expanded in-line, but also that affected definitions of the specified functions must be recorded for possible use in performing such expansions.
Looking at it the other way, the compiler is not required to save function definitions against the possibility of future expansions unless the functions have already been proclaimed to be inline. If a function is proclaimed (or declaimed) inline before some call to that function but the current definition of that function was established before the proclamation was processed, it is implementation-dependent whether that call will be expanded in-line. (Of course, it is implementation-dependent anyway, because a compiler is always free to ignore inline declaration specifiers. However, the intent of the committee is clear: for best results, the user is advised to put any inline proclamation of a function before any definition of or call to that function.)
Consider these examples:
X3J13 voted in March 1989 to extend inline declaration specifiers to accept any function-name (a symbol or a list whose car is setf—see section 7.1). Thus one may write (declare (inline (setf cadr))) to indicate that the setf expansion function for cadr should be compiled in-line.
Следует отметить, что соблюдаются правила лексической области видимости. Если одна из указанных функций лексически ограничена (с помощью flet или labels), то декларация применится для локального определения, а не для глобального.
X3J13 voted in January 1989 to clarify that the proper way to define a function gnards that is not inline by default, but for which a local declaration (declare (inline gnards)) has half a chance of actually compiling gnards in-line, is as follows:
The point is that the first declamation informs the compiler that the definition of gnards may be needed later for in-line expansion, and the second declamation prevents any expansions unless and until it is overridden.
While an implementation is never required to perform in-line expansion, many implementations that do support such expansion will not process inline requests successfully unless definitions are written with these proclamations in the manner shown above.
Также реализацией могут представляться другие свойства. value должно быть неотрицательным целым числом, обычно на интервале 0 и 3. Значение 0 означает, что свойство полностью не важно, и 3, что свойство полностью важно. 1 и 2 промежуточные значение, 1 «нормальный» или «обычный» уровень. Для наизначимого уровня существует аббревиатура, то есть (quality 3) можно записать, как просто quality. Например:
The declaration declaration specifier may be used with declaim as well as proclaim. The preceding examples would be better written using declaim, to ensure that the compiler will process them properly.
(dynamic-extent item1 item2 ... itemn) declares that certain variables or function-names refer to data objects whose extents may be regarded as dynamic; that is, the declaration may be construed as a guarantee on the part of the programmer that the program will behave correctly even if the data objects have only dynamic extent rather than the usual indefinite extent.
Each item may be either a variable name or (function f ) where f is a function-name (see section 7.1). (Of course, (function f ) may be abbreviated in the usual way as #’f .)
It is permissible for an implementation simply to ignore this declaration. In implementations that do not ignore it, the compiler (or interpreter) is free to make whatever optimizations are appropriate given this information; the most common optimization is to stack-allocate the initial value of the object. The data types that can be optimized in this manner may vary from implementation to implementation.
The meaning of this declaration can be stated more precisely. We say that object x is an otherwise inaccessible part of y if and only if making y inaccessible would make x inaccessible. (Note that every object is an otherwise inaccessible part of itself.) Now suppose that construct c contains a dynamic-extent declaration for variable (or function) v (which need not be bound by c). Consider the values w1,…,wn taken on by v during the course of some execution of c. The declaration asserts that if some object x is an otherwise inaccessible part of wj whenever wj becomes the value of v, then just after execution of c terminates x will be either inaccessible or still an otherwise inaccessible part of the value of v. If this assertion is ever violated, the consequences are undefined.
In some implementations, it is possible to allocate data structures in a way that will make them easier to reclaim than by general-purpose garbage collection (for example, on the stack or in some temporary area). The dynamic-extent declaration is designed to give the implementation the information necessary to exploit such techniques.
For example, in the code fragment
it is not difficult to prove that the otherwise inaccessible parts of x include the three conses constructed by list, and that the otherwise inaccessible parts of y include three other conses manufactured by the three calls to cons. Given the presence of the dynamic-extent declaration, a compiler would be justified in stack-allocating these six conses and reclaiming their storage on exit from the let form.
Since stack allocation of the initial value entails knowing at the object’s creation time that the object can be stack-allocated, it is not generally useful to declare dynamic-extent for variables that have no lexically apparent initial value. For example,
would permit a compiler to stack-allocate the list in x. However,
could not typically permit a similar optimization in f because of the possibility of later redefinition of g. Only an implementation careful enough to recompile f if the definition of g were to change incompatibly could stack-allocate the list argument to g in f.
Other interesting cases are
and
In each case some compilers might realize the optimization is possible and others might not.
An interesting variant of this is the so-called stack-allocated rest list, which can be achieved (in implementations supporting the optimization) by
Note here that although the initial value of x is not explicitly present, nevertheless in the usual implementation strategy the function f is responsible for assembling the list for x from the passed arguments, so the f function can be optimized by a compiler to construct a stack-allocated list instead of a heap-allocated list.
Some Common Lisp functions take other functions as arguments; frequently the argument function is a so-called downward funarg, that is, a functional argument that is passed only downward and whose extent may therefore be dynamic.
The following three examples are in error, since in each case the value of x is used outside of its extent.
The preceding code is obviously incorrect, because the cons cells making up the list in x might be deallocated (thanks to the declaration) before length is called.
In this second case it is less obvious that the code is incorrect, because one might argue that the cons cells making up the list in x have no effect on the result to be computed by length. Nevertheless the code briefly violates the assertion implied by the declaration and is therefore incorrect. (It is not difficult to imagine a perfectly sensible implementation of a garbage collector that might become confused by a cons cell containing a dangling pointer to a list that was once stack-allocated but then deallocated.)
In this third case it is even less obvious that the code is incorrect, because the value of x returned from the let construct is discarded right away by the progn. Indeed it is, but “right away” isn’t fast enough. The code briefly violates the assertion implied by the declaration and is therefore incorrect. (If the code is being interpreted, the interpreter might hang on to the value returned by the let for some time before it is eventually discarded.)
Here is one last example, one that has little practical import but is theoretically quite instructive.
Since j is an integer by the definition of dotimes, but eq and eql are not necessarily equivalent for integers, what are the otherwise inaccessible parts of j, which this declaration requires the body of the dotimes not to “save”? If the value of j is 3, and the body does (setq foo 3), is that an error? The answer is no, but the interesting thing is that it depends on the implementation-dependent behavior of eq on numbers. In an implementation where eq and eql are equivalent for 3, then 3 is not an otherwise inaccessible part because (eq j (+ 2 1)) is true, and therefore there is another way to access the object besides going through j. On the other hand, in an implementation where eq and eql are not equivalent for 3, then the particular 3 that is the value of j is an otherwise inaccessible part, but any other 3 is not. Thus (setq foo 3) is valid but (setq foo j) is erroneous. Since (setq foo j) is erroneous in some implementations, it is erroneous in all portable programs, but some other implementations may not be able to detect the error. (If this conclusion seems strange, it may help to replace 3 everywhere in the preceding argument with some obvious bignum such as 375374638837424898243 and to replace 10 with some even larger bignum.)
The dynamic-extent declaration should be used with great care. It makes possible great performance improvements in some situations, but if the user misdeclares something and consequently the implementation returns a pointer into the stack (or stores it in the heap), an undefined situation may result and the integrity of the Lisp storage mechanism may be compromised. Debugging these situations may be tricky. Users who have asked for this feature have indicated a willingness to deal with such problems; nevertheless, I do not encourage casual users to use this declaration.
Реализация может поддерживать другие спецификаторы деклараций (специфичные для неё). С другой стороны, компилятор Common Lisp может игнорировать некоторые виды деклараций (например, неподдерживаемые компилятором), за исключением спецификатора декларации declaration. Однако, разработчики компиляторов поощряются в том, чтобы выдавать предупреждение об использовании неизвестных деклараций.