Apologia
Table of Contents
1. Programming Language
I'm still debating the programming language I should use for this endeavor. Among the choices, it seems that Lisp is best prepared for my task:
- Lisp has no syntax, so language extensions are natural, and everything has the same "sentence structure" (verb subject object)
- Macros make language extensions on par with special forms
- Symbolic computation can be handled easily and naturally (e.g., the pattern matching code and algebraic simplifier couldn't be easily done in non-Lisp)
- Lisp allows for Bignum computations, so integer and rational arithmetic is precise
At the same time, I wish to use IEEE floating point (which seems to
rule out Common Lisp, except sbcl
and possibly a few other
implementations). The easiest solution is to use SBCL, and not
worry about the details. One way to resolve this problem is to
implement my own floating point arithmetic with arbitrary precision
algorithms, or fall back to using the GNU Scientific Library, or
something similar. I suppose I could write some unit tests to check
if the system uses IEEE floating point, so we can be certain.
FEMLISP look interesting, it's a Common Lisp library for solving partial differential equations via finite element methods.
Scheme has the disadvantage of being interpreted (read: slow) and limited support for macros. Guile looks like it remedies these problems by having the programmer implement the critical parts in C, then embed a Scheme interpreter/VM in the C code. I like this approach better.
- One problem with Guile is that complex numbers are necessarily inexact.
- On the other hand, arithmetic operators can be extended as if they were generic functions in Guile.
1.1. Dialects
Scheme is probably the first dialect most people encounter, since it's used in the textbook Structure and Interpretation of Computer Programs ("SICP"). The Scheme dialect has the hall-mark of minimalism. In fact, it's so minimal that the reader implements a scheme interpreter in chapter 4 of SICP.
Common Lisp emerged from the 1980s after Lisp 1 fractured in a Cambrian explosion of dialects. Common Lisp sought to unite them all into one common language. Unlike Scheme, Common Lisp compiles to machine code, has a comparatively large number of functions (Scheme fits on a cocktail napkin, Common Lisp requires a bit more space). This dialect also has macros, which Scheme did not until relatively recently.
Emacs Lisp is a cousin to Common Lisp, because both descend from Maclisp (a shared Cambrian ancestor). They share similar syntax, and are about as distinct as Latin to modern Italian, or Urdu and Farsi.
Clojure is the newest dialect, which borrowed as heavily from Haskell
as from other Lisps. It uses immutability, emphasizes functional style,
and runs on the Java Virtual Machine or in the browser as Javascript.
While it has marginally cleaner syntax (using not just parentheses but
also square braces [...]
for vectors, and braces {...}
for hashmaps),
its STM memory model leads to accidentally bloated software all too easily.
1.2. Flaws
The biggest flaws that come to mind are figuring out which dialect to use. Each of them have their quirks and shortcomings.
1.2.1. Scheme
1.2.1.1. No Canonical Implementation
One of the features of Scheme's minimalism is that it's not hard to write your own Scheme interpreter. The problem: there are dozens of Scheme implementations.
- MIT/GNU Scheme
- What SICP and SICM use
- Guile
- GNU's official Scheme implementation for extensible usage
- Racket
- Flashy new Scheme implementation
- Chicken
- Compiles Scheme to C
- Gambit
- Another Scheme-to-C compiler, plus an interpreter
- Chez Scheme
- An older implementation, among the fastest
And on and on and on. If starting from scratch, it's unclear which one to pick, they're all decent choices. But each one has language-dependent variations of expected features — e.g., modules are either unimplemented or use different syntax.
Although I really like Guile, I'm afraid it may be unstable and remove functionality I'd use.
1.2.2. Common Lisp
Probably the biggest complaint is how baroque the language is (for
example: setf
and setq
are both included, but only one is really
needed). Equality testing also exemplifies this problem (we have =
for
numbers, eq
for pointers, eql
for pointers or numbers, equal
, and
equalp
, but we'll need to roll our own if we want equality of CLOS
instances [objects]).
1.2.2.1. Floating Point Arithmetic isn't part of the Standard
The Language standard was written before IEEE 754 floating point was finalized (or written), so this is a serious shortcoming with Common Lisp for numerical analysts. Since it's not part of the standard, it's not violating the standard to use IEEE floating point arithmetic. But it's just compiler dependent.
- SBCL uses IEEE-754 on x86
- ABCL uses IEEE-754 if the JVM running it uses the standard
1.2.3. Clojure
Slow startup with repls and bloated software are two of the biggest issues facing Clojure.
1.2.3.1. Memory Usage
Clojure's memory usage can easily run out of control. The STM memory model requires greater diligence when programming, otherwise duplicate data can be created and not freed all-too-easily by accident. For example, holding onto the head of a cons will keep the rest of the list, even if the rest of the list is not needed or used. The JVM garbage collector doesn't seem to handle this memory model all too well, either.
This frequently leads to rewriting the code in Java.
1.2.3.2. There is no standard
Unlike Scheme and Common Lisp, there is no standard. So, is mapcat
doing what I expect? Erm…maybe?
1.2.3.3. JVM Historically didn't have IEEE 754 floating point
This may be a subtle source of bugs, but floating point support is comparatively modern. For old-timers using JVM 6 (or whatever), this is problematic.
1.3. Further Reading
- Antoine Kalmbach, Between two Lisps. Posted Oct 5, 2020.
- Antoine Kalmbach, Recutils, GOOPS and virtual slots. Posted Dec 6, 2020.
- Thoughts on Lisps compares various implementations of Scheme and Common Lisp for performance and clarity
- Pascal Costanza's Highly Opinionated Guide to Lisp
- Packages in Common Lisp, a tutorial (PDF)