We’re now at one of those functions that I’ve never seen used. The upgraded-complex-part-type function returns the most specialized type that can hold the passed type and be part of a complex number representation. I don’t really have much to say about this. It receives a subtype of real, and returns a subtype of real. If somebody has actually used this in real code, please let me know in the comments.
Tag Archives: lisp
The less-familiar parts of Lisp for beginners — upgraded-array-element-type
The next function we’re describing is upgraded-array-element-type. This function takes a type specifier and returns another type specifier. So, let’s talk about Lisp type specifiers a bit. There are the expected ones for floating-point numbers, integers, complex numbers, and many more non-numeric types. The programmer can also declare certain variables to be within a specified sub-range of the intrinsic numeric types. For example, a variable can be declared as holding integers between 7 and 23, very much like the “subrange” types in the Pascal programming language. The programmer might, however, be interested to know how that variable is stored in an array, where multiple objects of the same type might be laid out contiguously in memory. The upgraded-array-element-type returns the most specific intrinsic type capable of holding the supplied type. It allows the programmer to query the implementation and determine what intrinsic types are available. I’ll post an example below.
This is a different perspective on data than that supplied in C++ by the stdint.h header file. There, the programmer chooses types based on the minimum range that the variable is to hold and the desired tradeoff between speed and space. The Lisp standard does not define something equivalent to, say uint_fast16_t, which might expand to different types on a 16-bit CPU and a 32-bit CPU, at the discretion of the implementation(s).
So, an example of the use of upgraded-array-element-type on SBCL 1.1.14:
CL-USER> (upgraded-array-element-type '(integer 7 23)) (UNSIGNED-BYTE 7) CL-USER> (upgraded-array-element-type '(integer 0 1)) BIT CL-USER> (upgraded-array-element-type '(integer -128 127)) (SIGNED-BYTE 8) CL-USER> (upgraded-array-element-type '(integer 0 1000000)) (UNSIGNED-BYTE 31) CL-USER> (upgraded-array-element-type '(integer 0 3000000000)) (UNSIGNED-BYTE 32) CL-USER> (upgraded-array-element-type '(double-float 0.0d0 2.0d0)) DOUBLE-FLOAT
The less-familiar parts of Lisp for beginners — update-instance-for-redefined-class
With the update-instance-for-redefined-class generic function, we encounter, once again, the concept of redefined classes in Lisp. This has no parallel in C++, the programmer changes the slots within a class even while there exist instances of the class.
The programmer should not call this function directly, it is invoked by the system when necessary.
This generic function behaves much like update-instance-for-different-class, and most of what was described there is true here, but with one important exception. In the former case, the original and new class definitions both existed, so objects of both types could be present at the same time, and the accessors for the original class were still available. Now, the redefined class means that the original class definition is no longer valid, so the code cannot examine it through any of the normal methods. The accessors are no longer available, and slot-value has nothing to work on. As the programmer might need to examine the contents of slots that have been deleted in the class redefinition, there is a mechanism for that in the invocation of this generic function. They are supplied in a property list that is passed to this method.
Once again, by writing a specialized :after method on this generic function, the programmer can examine the property list and take action based on the values in discarded slots.
The less-familiar parts of Lisp for beginners — update-instance-for-different-class
We’re back to another function that has no real analogue in C++. The update-instance-for-different-class generic function is not to be called by the programmer, but it is still useful to know about it, as the programmer can specialize an :after or :before method to take special action.
In Lisp, it is possible to convert an instance to a new type with the change-class generic function. This was demonstrated in an earlier article, along with a specialized :before method for update-instance-for-different-class that allowed me to display the contents of the object prior to its being changed.
What are the cases when the programmer might want to specialize this generic function? Well, the default behaviour of update-instance-for-different-class is to copy slots with the same name from the old to the new instance and initialize new slots according to their initarg or initform forms, if any. Slots in the old class whose names do not appear in the new class have their values discarded. A :before method allows the programmer to examine those slots soon to disappear and take action based on their contents. An :after method, on the other hand, has access both to the original instance and to the post-copy, post-initialize slots in the new class, and can take further action to initialize or change slots based on those values.
The less-familiar parts of Lisp for beginners — unwind-protect
While this is not really in the realm of uncommon commands, being a fairly useful and frequently-used feature, I want to point it out for the C++ programmers who are new to Lisp, just to reinforce its usefulness.
In both C++ and Lisp, a function or block can exit in some non-trivial ways. Flow could jump out of the block with a goto, or a called function could throw an exception that is only caught somewhere higher up. Lisp has equivalents to those. There is no setjmp()/longjmp() in Lisp, but the C++ programmer should consider himself or herself forbidden from using those, they do bad things.
There will be times when the program does something, and needs to have a guaranteed method of undoing it no matter how execution passes out of the block. In C++, this is commonly done by putting the shutdown behaviour in an object destructor and instantiating the object on the stack as an auto variable. So, where in C one writes fopen() and fclose() on a file, in C++ one commonly uses iostream objects whose destructors close the file descriptor associated with the open file. In this way, the programmer can avoid leaking file descriptors when an exception exits the function after the file has opened, but before it has closed.
An alternative in C++ is to enclose everything in a try{} block, and do the cleanups in the corresponding catch, then re-throw the exception. This is a bit klunky, though, and does require that the closing code is written twice, once in the regular flow, and once in the exception caught case.
In Lisp, there are no object destructors, so that mechanism isn’t available. Instead, in Lisp the programmer typically uses unwind-protect. This special operator is a bit different from the C++ try/catch sequence, because the unwind form is executed in all cases, not just in the exception case. It essentially says that, however you exit the protected form, immediately execute the cleanup form before proceeding further. If you want to think of it a C++ try/catch with an implicit catch of any exception followed by a re-throw, you can, but note that important distinction, the cleanup form is executed even in the normal flow, then the block exits normally.