In the implementation we tried to paid attention to the following design issues and guidelines:
Stability, Compatibility, Performance, Readability, Extendability
Most of them are not mutually exclusive. Every function should acomplish the first guidelines and the fifth if appropriate.
Out of range errors, bad argument types:
All functions should accept all described data-types and react on exceptional situations as desribed, such ignoring or obeing out-of-range indices, bad argument types and so on.
Generally most of the functions don't implicitly try to fix such errors. They throw the according error instead.
Exceptions: STD-STRJOIN
explicitly converts all elements in the list argument to strings.
ADS Errors
Erros during evaluation in external ADS functions will break the evaluation. Additionally ADS errors set the global symbol *STD-ERRNO*
to a numeric value according the type of error, to be able to suppress errors with vl-catch-all-apply
(AutoCAD 2000)
Use (std-print-errno [errno])
to print the ADS error string. See adsrx/args.h
for the list of error numbers.
OLE Exceptions
These can only be caught be the new AutoCAD 2000 VL-CATCH-ALL-APPLY
function. It is not used or supported in the stdlib yet. In my private opinion it is not safe to use VLA (OLE Automation) at all, but is very attractive and transparent (DLL, EXE, remote) to use.
Under certain cases it might be useful, esp. if you know the OLE server very well, such as AutoCAD. But it was reported that additional DLL's installed by completely unrelated applications (eg. MS Office 2000) broke lisp functions using VLA.
Beware of locale issues! Such as commas instead of dots, alphabetic order and such.
Stack Overflows
Functions accepting long lists should avoid stack overflows by using iterative methods.
It is not allowed to implicitly suppose that the list size or tree depth is smaller than the accepted stack size if the function accepts lists or trees and that the possible recursive function will break with an stack overflow. Recursive methods may be used, but for such cases special conditions must be used, for example using iteration on large lists.
Numeric Stability
Numercial stability is very poor in AutoLISP as in the underlying C and AutoCAD libraries. In general always try to use the Acad functions or commands instead of hand-written functions. Esp. use TRANS
and UCS.
Special ways to overcome integer overflows are provided in some mathematical functions. Also type preservation which is better than in the corresponding AutoLISP functions.
Rounding errors are a big problem in AutoLISP. Especially long arithmetic date conversions and matrix operations may produce rounding errors. There should be some work done.
The random generator should provide highly stable methods to overcome problems in 3d-space. Some generators degrade to produce a low number of planes for random points. Some tested generators produce only about 30 planes in 3D-space. The period length is not so important as the sequential and geometric correlation. The std-make-random-state function will accept changing the method from a fast system provided method to a highly stable but slower one.
See also the special chapter on portability issues which handles this more detailled. This library code is compatible under the below explained terms, but you as user have to take care also.
We try to be as backwards compatible as possible and managable. New AutoLISP functions introduced in earlier releases are not implicitly called. First it is tested if the function may be called or if a workaround should be used.
We also try to be as forwards compatible as possible. For most of the new extensions appropriate versions for older releases are provided. Optional arguments cannot be provided in all cases because that increases costs for maintaining external libraries. New undocumented functions very likely to appear in future versions are provided for all earlier versions.
(defun func (a1 a2 a3) (list a1 a2 a3)) (func) => (nil nil nil)
(func 1)
=> (1 nil nil)
Workarounds for new functions that will harm the understanding of STDLIB functions will be provided as soon as they appear. Eg: VL-STRING->LIST
produces a list of ints and not a list of single strings as the counterpart in old STDLIB versions. This was changed to match the new functionality. However the functionality of STD-STRING-SUBST
is not changed to match the functionality of the new undocumented function VL-STRING-SUBST
. STD-STRING-SUBST
replaces all occurances of the substring, as expected. The yet undocumented VL-STRING-SUBST
replaces only the first occurance.
We try to be as Lisp compatible and conservative as possible. In special:
(APPLY func args)
is used instead of implicit function evaluation like ((EVAL func) args...)
In certain cases the latter method may be faster than (APPLY func args)
or even ((func args))
, but it hard to read, forbidden in Common Lisp and therefore not used here. The first method is also generally faster with (FUNCTION (LAMBDA ..))
constructs.
(EVAL)
should be avoided at all, for times when a native compiler for AutoLISP will be available or for code analyzers such as the Packager, PEI-Findvars, ALLY, Rapid Lisp, .... That's why we use (STD-SYMBOL-VALUE)
instead of (EVAL)
with symbols.
We don't blindly assume that the underlying operating system is Win32 or DOS based. Or that filenames are case insensitive.
We still want to support older platforms like OS2, Apple Macintosh, various unix versions, for backward compatibility and -who knows- for future platforms.
Compatibility testing for IntelliCAD® or LISP flavours of other AutoCAD® clones is a problem for me and should be tested and maintained by someone else if desired. However, I will not throw any stones on them. As reported does IntelliCAD 98 and FelixCad 4 work in alisp-only compatibility mode, the ads code might need some minor fixes and linking to their libs.
With the first releases (prior to 0.4) readability was favored over performance, but this changed. Usually a library should favor performance over readability. Because of the unique situation of being the first public and free complete AutoLISP library, some probably acceptance and learning problems I favored readability in the early versions. Function call unrolling for performance optimizations was added later. Nevertheless this code is usually much faster than other code that I know of and will further improve on its openess.
Besides optimized algorithms and data structures and specialized internal functions for different argument types some special features are used to improve performance.
These are in particular:
Provide special functions for different LISP versions if appropriate. VL functions may use internally destructive list functions, special arithmetic operators and better data structures, so these versions are generally much faster and stable than plain AutoLISP functions.
Some list accessors use unsafe methods such as STD-%FIRSTN
, STD-%SETNTH
instead of the safe and public variants. Stability is not affected because it's only used internally where it is assured that indices cannot be out of range.
Provide extensions for costly operations such as certain list and file functions.
Avoid double definitions at load-time if it's costly.
Use memoized versions of simple AutoCAD predicates, such as (STD-PLATFORM)
to avoid costly (GETVAR)
calls.
Use Entget-Caching to avoid costly entget
AutoCAD database access.
Long functions names are often preferred to improve readability.
In cases where high performant but unreadable methods may be used, the lightly slower but readable version is used.
If the performance loss is significant the worse readable version is favored. This is especially true with our used scheme of version specific markers which must be provided to create specialized packages on the fly by the stdlib-pp.pl script ("Stdlib PreProcessor").
Writing Lisp code is always a matter of beauty. Beautiful code sometimes relies on short and readable code but also on correct indentation and using less lines, keeping readability in comparison to other programming languages. Lisp code is known as programming language using the least space for most functionality without relying on obscure and hard to remember syntax or operator precedence rules. There's just a single parenthesesis instead of other block constructs "{;,[(".
Just think of perl or C.
Needing less space contradicts to longer readable names. But we think conservative, target novice users and to read old code in a few years. Keeping long names and readable names certainly improves readability even if sometimes there is a line break too much here and there.
The library itself is most of the time a good example how to keep readable code, but sometimes we had to convert easy-to-read recursive functions to iterative versions, which are awful in terms of beauty, much needs less stack size for older AutoLISP's. Sometimes the recursive variants are kept in the code but outcommented. Just to get a feeling for the readable version.
Localization issues and error handling should be handled extendable if performance is not critical.
The user should be able to extend support for foreign languages, codepages, error messages, default user prompts, statistics generated by std-time, language settings such as preferred date and time formats, number delimiters, function hooks and functional arguments and such.
Some extensions require recompilation of a project (error messages), some are done at load-time with special hooks (extensions) and some may be done at run-time (e.g: date format strings).
Recompilation of private extensions would require a message to the maintainer of the library to bring the benefit to other users too if this code will be copied or published.
Otherwise load-time hooks will have to be used.
This would include additional support for more languages with detecting languages, error messages, default user prompts and command options (such as osnap strings).
Extendability might conflict with performance. This is solved according the circumstances. User functions must be extendable, core library functions not.
Functional arguments to enable dynamic, run-time modifications, esp. for STDINPUT, might cause problems with the "funarg problem", shadowing inner variables by user variables in the provided funarg. AutoLISP is dynamic so we can overcome this only by hopefully unique names. So far it works okay in reality but it will be improved by better obfuscation, using our namespace or gensyming. Performance problem might affect the most popular funcarg functions STD-SORT
and STD-REMOVE-IF-NOT,
but this is the price we have to pay for a dynamic lisp. In VL this is a non-issue, only new STDINPUT functions will be affected then.
Some people complained about the high level of abstraction in this library. One even said: "As typical with Reini, much too abstract."
The opposite of abstraction would be cut-and-paste.
I understand this argument especially when it makes learning and understanding the library quite complicated.
Larry Wall wrote this in the Camel book : "We've all fallen into the trap of using cut-and-paste when we should have chosen to define a higher-level abstraction, if only just a loop or subroutine.To be sure, some folks have gone to the opposite extreme of defining ever-growing mounds of higher-level abstractions when they should have used cut-and-paste. Generally, though, most of us need to think about using more abstraction rather than less.
[1] This is a form of False Laziness.
[2] This is a form of False Hubris.
(Caught somewhere in the middle are the people who have a balanced view of how much abstraction is good, but who jump the gun on writing their own abstractions when they should be reusing existing code.)
[3] You guessed it, this is False Impatience. But if you're determined to reinvent the wheel, at least try to invent a better one.
Whenever you're tempted to do any of these things, you need to sit back and think about what will do the most good for you and your neighbor over the long haul. If you're going to pour your creative energies into a lump of code, why not make the world a better place while you're at it? (Even if you're only aiming for the program to succeed, you need to make sure it fits its ecological niche.)" - Larry Wall, "Programming Perl", Chapter 5
The causes for higher abstraction are Laziness, Impatience, and Hubris. The advantage is good software design. Easier maintainance, you have to fix code only once, better structuring, easier portability, at all being better prepared if something unforeseen will happen.
The disadvantage is besides the higher learning curve in some parts a tighter coupling between modules which could be decoupled and therefore loaded seperately.
However practice showed that nobody used any of the main single modules.
I have to admit that the C++ standard library is also much too abstract for me. But this was the same with the Common Lisp library as well, until I understood it and used it. the AutoLISP stdlib is much smaller than those. Less than 10% if their sizes.