4.6. Hints for Using Guile
Here are a few principles I have found helpful in designing Guile-based applications:
- Implement performance-critical sections in C. Scheme code interpreted by Guile is much slower than compiled C code, but in well-designed programs, the interpreters speed is not critical to the applications overall performance. It is usually straightforward to identify the algorithms or operations that contribute most to the applications running time; those can then be implemented in C and exported to the Scheme level as primitives. The result is a system stratified into efficiently coded primitives and flexible Scheme code.
The GNU Emacs editor text exemplifies this approach. It provides C primitives for inserting and deleting text, finding line boundaries, and searching for literal strings and regular expression matches. Because these primitives were chosen and implemented carefully, operations on Emacss text buffers are so efficient that Emacs Lisp programmers frequently choose to use them instead of Lisps more traditional data structures.
- Interpreted code should be untrusted. As a general principle, Scheme code should not be allowed to crash the application. Primitives should verify the types of all their arguments and perform all checks necessary to raise an error instead of performing an illegal operation. For example, SCWMs lower-window primitive, discussed previously, calls the VALIDATE macro to verify that its argument, win, is indeed a live window object. If the user accidentally passes a string to the function, SCWM signals an error, instead of blindly extracting a pointer and wreaking havoc.
- Borrow Scheme features when possible. Guile provides a number of broadly useful features; applications should take advantage of them, instead of reinventing them:
- Schemes read and write functions provide a simple and versatile file format; everything from lists to trees to strings containing arbitrary binary data can be safely written out and restored with minimal effort.
- Using smobs, the garbage collector can manage the applications own internal data structures, as well as the users.
- When appropriate, let the user provide call-back procedures in Scheme, instead of offering a fixed list of possible responses.
- Plan for creeping featurism. Certain applications are especially prone to creeping featurism, the gradual acquisition of more and more features. If something is inevitable, it is best to plan for it. Guile-based applications allow users to implement some features on their own without modifying the core of the application. Whether or not to use such features becomes a matter of personal taste on the users part, but not a concern for the applications maintainer.
For example, the TWM window manager has an InterpolateMenuColors option, which allows rainbow-like effects in pop-up menus. In light of this evidence, I consider it safe to say that window managers are prone to creeping featurism. SCWM makes it possible for users to implement features such as interpolated menu colors without specific support from SCWM itself.
4.7. Related Work
Guile is certainly not the first package to promote the concept of an embeddable interpreter. Here I compare it with some notable predecessors:
- TclTcl is a clean, simple language designed for use as an embedded control language. It is usually paired with Tk, a powerful toolkit for building graphical user interfaces. Tcl was first implemented in 1988, making it a very mature system. (Scheme dates back to 1976, but Guile itself was first released in 1993.)
The fundamental data type in Tcl is the string. Tcl represents lists as strings, using spaces, brackets, and backslashes to delineate elements. Numbers are simply strings of digits, and even Tcl programs themselves are represented as strings. Tcls evaluation rules and syntax are very consistent but can lead to confusing results at times. The simplicity of the string-based values makes it easy to extend Tcl from C; because Tcl was designed from the beginning to interface with C, the issues discussed previously regarding tail calls, continuations, and garbage collection in Guile do not arise.
However, Tcls simplicity has a price: It is very slow. To retrieve the Nth element of a list, Tcl must first completely parse the first N1 elements. To manipulate values, Tcl spends a good deal of time constructing strings in cases where other languages simply maintain a pointer to the original data. Because numbers are represented as strings, adding two numbers entails parsing the addends, computing the sum, allocating space for the sum, and converting the sum back into text.
These problems have been partially rectified in Release 8.0 of Tcl, which compiles programs to a byte-code representation and uses more efficient internal representations for data types, automatically converting to and from strings as necessary to maintain the languages semantics. However, to use these improvements in your own code, you must give up the simplicity that made Tcl appealing in the first place. Furthermore, the new data representation relies on reference counts to manage storage, instead of a full garbage collector; as mentioned previously, reference counting is a frequent source of programmer error.
Tcl requires programmers to follow rather odd circumlocutions to refer to global variables and to pass arrays as arguments. In theory, these rules keep the interpreter simple, but in practice, they create a new class of coding mistake that I (at least) make frequently. Because the interpreter did not stay simple in the long run anyway, this design choice appears to have been a mistake.
Given these weaknesses, you can argue that Tcl has acquired most of the drawbacks of Scheme without matching Schemes power and simplicity. Guile has a richer and more consistent set of data types and control structures than Tcl, Schemes variable references work in the obvious way, the conservative garbage collector is simple to use correctly, and Schemes syntax, although parentheses-laden, has fewer pitfalls than Tcls.
- PythonPython is another interpreted language designed to be incorporated into other applications. It features a clean infix syntax, support for modules and object-oriented programming, and interfaces to a variety of graphical user interface systems.
Like Tcl, Python provides the traditional suite of control structures. Although it does provide a λ syntax for anonymous functions, Pythons λ expressions do not capture references to local variables and may only contain expressions, not statements; Schemes λ does not have these restrictions.
Pythons allocation system also uses reference counting to manage storage; reference counting is more difficult to use correctly than Guiles conservative garbage collector.
|