Reasons to use Common Lisp
An actively-maintained-implementation, long-term-stable-specification programming language
There are many programming languages that don't change much, including
Common Lisp, but Common Lisp implementations continue to be developed.
For example, SBCL (Steel Bank Common Lisp) released its latest version
just last month.
Common Lisp can be extended through libraries. For example, cl-interpol
enables Perl-style strings to Common Lisp without requiring a new
version of Common Lisp. cl-arrows allows Common Lisp to create pipelines
using Clojure-style syntax without needing to update the Common Lisp
specification. This exceptional extensibility stems from macro and
particularly reader macro support in Common Lisp.
Feature-packed
Common Lisp includes many features found in modern programming
languages, such as:
- Garbage collection
- Built-in data structures (e.g., vectors, hash tables)
- Type hints
- Class definitions
- A syntactic structure similar to list comprehensions
Multi-paradigm
While Lisp is commonly associated with functional programming, Common
Lisp doesn't enforce this paradigm. It fully supports imperative
programming (like Pascal), and its object-oriented programming system
even includes advanced features. Best of all, you can freely mix all
these styles. Common Lisp even embraces goto-like code via TAGBODY-GO.
Performance
Common Lisp has many implementations, and some of them, such as SBCL,
are compilers that can generate efficient code.
With some (of course, not all) implementations, many programs written in
dynamic programming languages run slower than those in static ones, such
as C and Modula-2.
First, an example of the generated assembly will be shown, along with
more explanation about why it might be slowed down by some dynamic
implementations
The code listing below is a part of a program written in Modula-2, which
must be easy to read by programmers of languages in the extended ALGOL
family.
TYPE
Book = RECORD
title: ARRAY[1..64] OF CHAR;
price: REAL;
END;
PROCEDURE SumPrice(a, b: Book): REAL;
BEGIN
RETURN a.price + b.price;
END SumPrice;
The code is mainly for summing the price of books, and only the part
'a.price + b.price' will be focused on.
'a.price + b.price' is translated into X86-64 assembly code list below
using the GNU Modula-2 compiler.
movsd 80(%rbp), %xmm1
movsd 152(%rbp), %xmm0
addsd %xmm1, %xmm0
"movsd 80(%rbp), %xmm1' and 'movsd 152(%rbp), %xmm0' are for loading
'prices' to registers '%xmm1' and '%xmm0', respectively. Finally, 'addsd
%xmm1, %xmm0' is for adding prices together. As can be seen, the prices
are loaded from exact locations relative to the value of the '%rbp'
register, which is one of the most efficient ways to load data from
memory. The instruction 'addsd' is used because prices in this program
are REAL (floating point numbers), and '%xmm0', '%xmm1', and 'movsd' are
used for the same reason. This generated code should be reasonably
efficient. However, the compiler needs to know the type and location of
the prices beforehand to choose the proper instructions and registers to
use.
In dynamic languages, 'SumPrice' can be applied to a price whose type is
an INTEGER instead of a REAL, or it can even be a string/text. A
straightforward implementation would check the type of 'a' and 'b' at
runtime, which makes the program much less efficient. The checking and
especially branching can cost more time than adding the numbers
themselves. Moreover, obtaining the value of the price attribute from
'a' and 'b' might be done by accessing a hash table instead of directly
loading the value from memory. Of course, while a hash-table has many
advantages, it's less efficient because it requires many steps,
including comparing the attribute name and generating a hash value.
However, compilers for dynamic languages can be much more advanced than
what's mentioned above, and SBCL is one such advanced compiler. SBCL can
infer types from the code, especially from literals. Moreover, with
information from type hints and 'struct' usage, SBCL can generate code
that's comparably as efficient as static language compilers.
Given, the Common Lisp code listing below:
(defstruct book
title
(price 0 :type double-float))
(declaim (ftype (function (book book) double-float) add-price)
(optimize (speed 3) (debug 0) (safety 0)))
(defun add-price (a b)
(+ (book-price a)
(book-price b)))
SBCL can generate assembly code for '(+ (book-price a) (book-price b))'
as shown below:
; 86: F20F104A0D MOVSD XMM1, [RDX+13]
; 8B: F20F10570D MOVSD XMM2, [RDI+13]
; 90: F20F58D1 ADDSD XMM2, XMM1
The assembly code format is slightly different from the one generated by
the GNU Modula-2 compiler, but the main parts, the 'MOVSD' and 'ADDSD'
instructions and the use of XMM registers—are exactly the same. This
shows that we can write efficient code in Common Lisp at least for this
case. This shows that we can write efficient code in Common Lisp, at
least in this case, that is as efficient as, or nearly as efficient as,
a static language.
This implies that Common Lisp is good both for high-level rapid
development and optimized code, which has two advantages: (1) in many
cases, there is no need to switch between two languages, i.e., a
high-level one and a fast one; (2) the code can be started from
high-level and optimized in the same code after a profiler finds
critical parts. This paradigm can prevent premature optimization.
Interactive programming
Interactive programming may not sound familiar. However, it is a common
technique that has been used for decades. For example, a database engine
such as PostgreSQL doesn't need to be stopped and restarted just to run
a new SQL statement. Similarly, it is akin to a spreadsheet like Lotus
1-2-3 or Microsoft Excel, which can run a new formula without needing to
reload existing sheets or restart the program.
Common Lisp is exceptionally well-suited for interactive programming
because of (1) integrated editors with a REPL (Read Eval Print Loop),
(2) the language's syntax, and (3) the active community that has
developed libraries specifically designed to support interactive
programming.
Integrated editors with a REPL
With an integrated with a REPL, any part of the code can be evaluated
immediately without copying and pasting from an editor into a REPL. This
workflow provides feedback even faster than hot reloading because the
code can be evaluated and its results seen instantaneously, even before
it is saved. There are many supported editors, such as Visual Studio
Code, Emacs, Neovim, and others.
the language's syntax
Instead of marking region arbitrarily for evaluating, which is not very
convenient when it is done every few seconds, in Common Lisp, we can
mark a form (which is similar to a block in ALGOL) by moving a cursor to
one of the parentheses in the code, which is very easy with structural
editing, which will be discussed in the next section.
Moreover, even a method definition can be evaluated immediately without
resetting the state of the object in Common Lisp. Since method
definitions are not nested in defclass, this allows mixing interactive
programming and object-oriented programming (OOP) smoothly.
Here's the corrected code listing:
(defclass toto ()
((i :initarg :i :accesor i)))
(defmethod update-i ((obj toto))
(setf (i obj) (+ i obj) 1))
According to the code listing above, the method 'update-i' can be
redefined without interfering with the pre-existing value of 'i'.
Structural editing
Instead of editing Lisp code like normal text, tree-based operations can
be used instead, such as paredit-join-sexps and
paredit-forward-slurp-sexp. Moving cursor operations, such as
paredit-forward, which moves the cursor to the end of the form (a
block). These structural moving operations are also useful for selecting
regions to be evaluated in a REPL.
Conclusion
In brief, Common Lisp has unparalleled combined advantages, which are
relevant to software development especially now, not just an archaic
technology that just came earlier. For example, Forth has a
long-term-stable specification, and works well with interactive
programming, but it is not designed for defining classes and adding type
hints. Julia has similar performance optimization and OOP is even
richer, but it doesn't have a long-term-stable specification. Moreover,
Common Lisp's community is still active, as libraries, apps, and even
implementations continue to receive updates.
Top comments (0)