内容简介:Common Lisp is a very powerful programming language, and yet it seems to only enjoy a shy popularity. What is happening?I believe that for a large part the lack of popularity stems from myths of forgotten times which bear little relevance today.Some commo
Common Lisp is a very powerful programming language, and yet it seems to only enjoy a shy popularity. What is happening?
I believe that for a large part the lack of popularity stems from myths of forgotten times which bear little relevance today.
Some common myths include:
- Common Lisp does not have compile-time type checking.
- Common Lisp is for imperative, object-oriented programming.
- Common Lisp is too specialized, it’s not for general-purpose development.
- Common Lisp applications are hard to deploy.
And yet, some of these myths are so strong that they subsist today even among Common Lisp developers who sometimes get stuck with bad practices or poor tools simply because the news of the recent developments in the ecosystem haven’t reached them. Indeed, Common Lisp, as an extensible language, has modernized tremendously over the last decades.
The following article is structured over the following axes:
- How to get started with Common Lisp, both in terms of language style, practices and tooling, without dealing with the aging crufts that have been superseded today.
- For both beginning and experienced Common Lisp developers, I’ll share some recommendations about the best practices, the style, the libraries and the tooling.
- Debunking some myths around the language itself as well as the practicalities.
Common Lisp is a gigantic programming language and ecosystem. As such, this article is in no way exhaustive! I invite the community to reach out and share their recommendations, corrections and other discussions!
If you enjoy this article and would like to help me keep writing, consider chipping in, every little bit helps to keep me going :)
Thank you!
Getting started with the right tools and learning resources
Tools
Common Lisp has earned the reputation of being difficult to set up. Today, some good solutions exist to help newcomers:
- Portacle is an all-in-one bundle that includes SBCL , Emacs , SLIME and more. While the user must learn the basics of Emacs, Portacle is known to be relatively friction-less.
-
The Guix package manager can set up a Common Lisp development environment in one go:
guix install sbcl git emacs emacs-slime emacs-helm-slime emacs-company emacs-magit emacs-paredit emacs-rainbow-delimiters
Replace
guix install
withguix environment --ad-hoc
if you don’t want to persist a profile.If you prefer VI-style bindings:
guix install sbcl git emacs emacs-slime emacs-helm-slime emacs-company emacs-magit emacs-paredit emacs-rainbow-delimiters emacs-evil emacs-evil-collection
If you prefer Lispy over paredit to automate s-expression manipulation (such as automatically balancing parentheses):
guix install sbcl git emacs emacs-slime emacs-helm-slime emacs-company emacs-magit emacs-lispy emacs-rainbow-delimiters
Should you need Common Lisp libraries, you can also install many of them with Guix. For the rest, you’ll have to resort to Quicklisp which is not provided by Guix, so you’ll have to install it manually: https://www.quicklisp.org/beta/
Learning resources
The community-maintained Common Lisp cookbook is probably the best starting point with excellent references to everything from string manipulation to continuous integration.
It’s also a good portal to other resources such as:
- awesome-cl : A curated list of Common Lisp frameworks and libraries.
- Practical Common Lisp : A free online book to learn Common Lisp.
That said, the introductory documentation can be rather overwhelming in my opinion and I’ve invited the community to work on a gentle, modern primer for the complete beginner:
https://github.com/LispCookbook/cl-cookbook/issues/270
Feedback welcome!
Editor and offline documentation
Emacs setup
Many Common Lisp developers use Emacs. You don’t have to, many other editors support Common Lisp as well.
SLIME and SLY are famous for providing a stellar interactive development experience.
On top of this, there are Helm packages to enhance your SLIME / SLY experience with live, narrowing fuzzy-search and completion for about everything:
Local Common Lisp HyperSpec
The HyperSpec is the Common Lisp reference available here: http://www.lispworks.com/documentation/HyperSpec/Front/index.htm .
While the website look is rather dated, SLY and SLIME will interface with it so that you can browse the specifications directly from Emacs without having to open a browser.
Moreover, the HyperSpec can be installed locally, to be consulted offline. See http://quickdocs.org/clhs/ .
There is also a Guix package for it available from the Nonguix channel.
SLIME vs. SLY
SLY is a fork of SLIME. SLIME tends to be more conservative while SLY is slightly more featureful.
A few benefits of SLY over SLIME:
-
It has more consistent, cleaner code and reuses more of Emacs facilities (like
comint-mode
for the REPL). - It has a better color theme in my opinion.
- It offers better fuzzy completion.
- A feature unique to SLY: stickers allow the user to “stick” non-persistent code to help with debugging by annotating it. See https://github.com/joaotavora/sly#stickers .
- The setup is essentially zero-config with sane defaults, with all the features like mrepl, stickers, etc. enabled by default. Of course you can disable the features you don’t like.
- It compiles with ASDF which makes it easier to package, bundle or deploy.
SLIME does include some features that are only available in SLY as plugins, such as ASDF management. I recommend the following plugins for SLY:
SLIME / SLY tips and tricks
Check out the following not-so-well-known commands, they could dramatically improve your workflow! (Most of them have a SLY equivalent.)
-
slime-who-*
-
slime-eval-last-expression-in-repl
(C-c C-j
) -
slime-list-compiler-notes
-
slime-export-symbol-at-point
(C-c x
) -
slime-export-class
,slime-export-structure
-
slime-trace-dialog-toggle-trace
(C-c M-t
) -
slime-inspect-definition
-
slime-delete-system-fasls
(Useful when .fasls are out-of-sync) -
slime-repl-clear-buffer
(C-c M-o
: useful whenlispy
orparedit
goes berserk) -
slime-profile-package
, then run the desired functions, thenslime-profile-report
. -
hyperspec-lookup-format
andhyperspec-lookup-reader-macro
.
In particular, note that slime-who-specializes
lists the methods of a given
class, which answers a common complaint coming from people used to languages
from the Algol family: the ability to complete the methods of the foo
class by
typing foo.<TAB>
.
ASDF
ASDF is the de-facto standard build system for Common Lisp.
To use a FOO
system, place it in in ~/common-lisp
and call (asdf:load-system :FOO)
.
Better: from the SLIME or SLY REPL, press ,load-system
and you’ll be provided
with a list of systems that can be loaded.
Best practices
See https://gitlab.common-lisp.net/asdf/asdf/blob/master/doc/best_practices.md .
Inferred systems
Historically ASDF required you to explicitly list the files to compile. This
was cumbersome and error-prone, but thankfully it has been fixed some time ago
with the package-inferred-system
extension which I highly recommend:
Benefits:
- No need to list files.
- No need to list dependencies. With non-inferred-systems, the dependency list can be wrong, e.g. include too many of them or miss some. In that sense inferred-systems are more robust.
- Enforces that every file must be a package.
Trick:
- If your system has many files, and thus that many packages, it can get quickly cumbersome for the user to load all those packages one by one. Instead, you can create a “meta package” which imports all the other packages of your system, so that the user only has to load this one to access the whole system.
-
How can we get the list of dependencies since we don’t list them explicitly in the
.asd
anymore? Thankfully the snippet below will retrieve them programmatically for us:(declaim (ftype (function (string)) package-dependencies)) (defun package-dependencies (pkg-name) (let (depends) (labels ((iter (openlist) (if (null openlist) depends ;; is this a subsystem of foo? (let ((find (search pkg-name (first openlist)))) (if (and find (zerop find)) (iter (append (asdf:system-depends-on (asdf:find-system (first openlist))) (rest openlist))) ;; if not, it's a direct dependency: collect it (progn (pushnew (first openlist) depends :test 'equalp) (iter (rest openlist)))))))) (iter (list pkg-name)))))
Then:
(package-dependencies "dbus") ; => ("ironclad cl-xmlspam split-sequence flexi-streams ieee-floats iolib babel trivial-garbage alexandria
Modernizing the language with packages
Myth: Common Lisp is old and does not enjoy the features of modern languages from recent years.
Truth: While the Common Lisp standard is from the 1990s, many modern libraries have continuously updated the language.
One of the biggest selling points of Common Lisp (and Lisp languages in general) is that the language itself is extensible. This means that if new paradigms become fashionable, they can easily be added to the language.
In particular, Common Lisp has the reputation among functional programmers of being very imperative and object-oriented. While these styles were popular in the past, nothing prevents us from programming functionally in Common Lisp.
As with many languages, Common Lisp packages can be use
-d, which means all
symbols will be merged in the current name space, or import
-ed, which means
all symbols will be accessible only via the package prefix.
The benefit of use
is that importing a language-enhancing package gives us a
feel that the enhancements are first class.
The drawback however is that if you change your mind and decide to import
the
package instead, you’ll have to add the package prefix to all the related
symbols manually. Plus it’s harder to see from just a glance which symbols
comes from which package.
This drawback could be eliminated with sufficient editor support to refactor symbols by their packages. See https://github.com/slime/slime/issues/532 for a discussion.
Recommended language-enhancing packages
The following packages are widespread and useful enough that can be use
-d
without hesitation:
- Alexandria : various language and functional programming procedures.
- UIOP: Macros and operating-system-level procedures. Included with ASDF in most Common Lisp compilers.
- trivia : Pattern matching.
The following libraries are also extremely useful, although not to everyone’s taste:
- Trivial-types : More common type definitions.
- Serapeum : a huge collection of utilities (named let, local definitions, hooks, file “human” size, string manipulation, etc.), built as a complement to Alexandria and UIOP. It greatly extends the language.
- Series : Lazy sequences and supporting higher-order functions.
Other language-enhancing libraries:
- Rove : The (only?) testing framework with coverage support.
- ppcre : Advanced string manipulation with unicode support.
- named-readtables : First class name space for readtables.
- generic-cl : Generic function wrapper over various functions in the Common Lisp standard, such as equality predicates and sequence operations.
Functional programming
Myth: Common Lisp is a procedural, object-oriented language. Truth: Many functional programming paradigms are available in Common Lisp.
Scheme developers in particular will be happy to find:
-
compose
,curry
in Alexandria. -
named
let
, local definitions,flip
and more in Serapeum. -
match
in Trivia. - Lazy sequences in Series.
The availability of the Series package means in particular that you don’t have
to use the (in)famous LOOP
macros if you don’t like them.
Static, parametric and algebraic typing
Myth: Common Lisp does not support static typing.
Truth: ECL, CCL and SBCL at least support compile-time type-checking.
While not exactly static typing since it’s optional, the technical term for it is “gradual typing.”
Note that types defined with a satisfies
predicate are not checked at
compile-time unless at the top-level (this is true for SBCL 2.0 at least).
Classes are a whole different beast and there type checking can currently be done with a separate library: https://github.com/fisxoj/sanity-clause . Also see https://lisp-journey.gitlab.io/blog/how-to-check-slots-types-at-make-instance/ .
Serapeum provides helpers for type manipulation: https://github.com/ruricolist/serapeum/blob/master/REFERENCE.md#types .
Algebraic types are supported out of the box, but notations can be improved with https://github.com/stylewarning/cl-algebraic-data-type .
There is even an ML-style library for Common Lisp: Coaltron .
More on the topic:
Type examples
From the HyperSpec:
(defun equidimensional (a) (or (< (array-rank a) 2) (apply #'= (array-dimensions a)))) ; => EQUIDIMENSIONAL (deftype square-matrix (&optional type size) `(and (array ,type (,size ,size)) (satisfies equidimensional))) ; => SQUARE-MATRIX
Some useful types from the trivial-types
package:
(deftype pathname-designator () '(or string file-associated-stream pathname)) (deftype function-designator () '(or symbol function)) (deftype string-designator () '(or character symbol string))
In practice:
(declaim (ftype (function (trivial-types:pathname-designator) pathname) my-basename)) (defun my-basename (path) (uiop:pathname-parent-directory-pathname path)) (compile nil '(lambda () (my-basename 17))) ; in: LAMBDA () ; (MY-BASENAME 17) ; ; note: deleting unreachable code ; ; caught WARNING: ; Constant ; 17 conflicts with its asserted type ; (OR (VECTOR CHARACTER) (VECTOR NIL) BASE-STRING ; (AND STREAM (SATISFIES TRIVIAL-TYPES:FILE-ASSOCIATED-STREAM-P)) PATHNAME). ; See also: ; The SBCL Manual, Node "Handling of Types" ; ; compilation unit finished ; caught 1 WARNING condition ; printed 1 note
Export at definition site
One of the main pain points in Common Lisp development is exporting public symbols.
Traditionally, symbols were exported from a dedicated “package” file that’s loaded first. This is cumbersome and error-prone since the symbols and their exports may run out of sync.
There is an export
function which can be called at definition site. Sadly,
since it’s a function, it won’t be called at compilation time and thus will yield
an error if you attempt to access the symbol from a foreign package at this point.
The trick is to force the evaluation of export
at compilation time with:
(eval-when (:compile-toplevel :load-toplevel :execute) (export FOO))
Since this is rather lengthy, I recommend you use Serapeum’s export-*
macros: https://github.com/ruricolist/serapeum/blob/master/REFERENCE.md#export-always-symbols-optional-package-nil-package-supplied
.
Hopefully the syntax will get even more practical once issue 38 is resolved.
Documentation
Contextual documentation is part of the Common Lisp standard, the so-called “docstrings” which can be placed at the beginning of variable and function definitions.
A detail that is perhaps not so well known is that classes, slots and packages
also support docstrings via the :documentation
keyword. It’s underused in the
community and I believe we should encourage its use.
Another perk of Common Lisp is the ability to declare the types of functions, variables, class slots, etc.
So instead of adding a comment about the accepted types of a function arguments
and the type of the return value, simply declaim
it:
(declaim (ftype (function (number) string) number->string)) (defun number->string (n) ...)
This has the double benefit of documenting the argument types and the return types of your function, as well as enforcing compile-time type-checking.
Same goes with class slots:
(defclass my-compiler () ((name :accessor name :initarg :name :initform "" :type string :documentation "Name of your favourite Common Lisp compiler.") ...))
Compilers
Common Lisp is a standard and Common Lisp compilers are legions.
Some target specific use cases.
- ECL targets embedded development, with good interop with C.
- ABCL is build on top of the Java virtual machine and thus has good interop with Java.
For general purpose development, the go-to compiler these days is SBCL . It is actively developed and has many benefits:
- Probably the best gradual type checking of all compilers.
- Fast compilation.
- Fast executables.
- Statistical profiler.
- Coverage analysis.
Debugging and stepper
Many Common Lisp compilers offer great support for debbuging.
In particular, the step
macro is part of the standard.
With SBCL, you must first compile the code you want to debug with
(declaim (optimize (speed 0) (space 0) (debug 3)))
Then you can run
(step (foo bar))
to step through the execution of (foo bar)
. With SLIME / SLY, the Emacs point
will follow the currently-executed expression!
Compiler warnings
To increase the level of warning detection, call
(declaim (optimize (speed 0) (space 0) (debug 3)))
before loading a system.
Also reload
-ing a system might raise more warnings.
You can use ,reload-system
in SLIME / SLY.
Deployment and executable size
The myth: Common Lisp scripts and binaries are hard to distribute; binaries are too big.
The truth: Common Lisp compilers offer a lot of flexibility.
#!sbcl --script
Tip: Add this to your .asd
to automatically create compressed binaries with
SBCL:
#+sb-core-compression (defmethod asdf:perform ((o asdf:image-op) (c asdf:system)) (uiop:dump-image (asdf:output-file o c) :executable t :compression t))
Guix vs. Roswell
Roswell is a Common Lisp environment manager that can install various Common Lisp compilers, deploy applications, etc.
While less popular among Common Lisp developers, I believe that Guix advantageously supersedes Roswell:
- Guix does not distribute opaque binaries, it produces reproducible packages (which are also increasingly more bootstrappable). Thus Guix is much more trustable.
- Guix environments can mix in any other packages, such as libraries or other programming languages. It is thus possible to create environments for complex projects dealing with multiple programming languages at the same time.
- Guix can run environments in containers.
- Guix can automatically “pack” a Common Lisp application with all its dependencies (including non-Lisp ones) as a tarball or a Docker image.
Take-away
I believe that Common Lisp deserves its reputation of a robust, practical, general purpose programming language. It’s high time we moved on from the old myths and we fixed the few pain points that still remain.
The setup and the learning curve is still a bit steep today, but it does not have to be this way: with the right web sites, documentation and tools, I’m convinced we could boost Common Lisp accessibility and reach out to many more developers out there.
Fashions are changing and many third-party libraries allows the language to keep up with the latest trendy paradigms.
Finally, Common Lisp is a gigantic ecosystem that we never stop exploring. I believe it’s important for the community to be dynamic and communicative about the evolution of the language. So don’t hesitate to reach out to me, share you critics and tell me what you think about this article!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。