Lisp is one of the oldest high-level programming languages, known for its distinctive fully parenthesized prefix notation and its treatment of code as data. It has influenced many languages and is still used in various dialects such as Common Lisp, Scheme, and Clojure.
Lisp Anti-Patterns Overview
Lisp, despite being a powerful and flexible language, has several common anti-patterns that can lead to code that’s difficult to maintain, inefficient, or doesn’t follow the language’s idioms. Here are the most important anti-patterns to avoid when writing Lisp code.
Excessive Global Variables
Avoid excessive use of global variables (special variables in Common Lisp, denoted with asterisks). While they can be useful for truly global state, overusing them creates hidden dependencies, makes testing difficult, and can lead to unexpected behavior. Instead, pass dependencies explicitly through function parameters or use lexical closures.
Improper Error Handling
Don’t ignore errors with ignore-errors
unless you have a good reason. Instead, use handler-case
to catch specific conditions and handle them appropriately. For more complex error recovery, use Common Lisp’s powerful condition system with restart-case
to provide multiple recovery strategies.
Reinventing Built-in Functions
Avoid reinventing functions that are already part of the language or standard libraries. Lisp has a rich set of built-in functions for list processing, sequence operations, and more. Using them makes your code more readable and often more efficient.
Excessive Recursion Without Tail Calls
Be cautious with recursive functions that aren’t tail-recursive, as they can lead to stack overflow with large inputs. Use tail recursion (where the recursive call is the last operation) or iteration for operations that might involve large numbers of steps.
Excessive Use of EVAL
Avoid using eval
for runtime code execution. eval
is powerful but dangerous, as it can execute arbitrary code and makes your program harder to analyze and optimize. Instead, use funcall
or apply
to call functions dynamically.
Not Using CLOS Effectively
Make effective use of the Common Lisp Object System (CLOS) for object-oriented programming. CLOS provides powerful features like multiple inheritance, multiple dispatch, and method combinations that can make your code more modular and expressive.
Improper Use of Macros
Use macros judiciously. While Lisp’s macro system is powerful, overusing macros can make code harder to understand and debug. Use functions when they suffice, and only use macros when you need to control evaluation or create new syntax. When writing macros, be careful to avoid variable capture by using gensym
or with-gensyms
.
Not Using Loop Abstraction
Use higher-level loop abstractions like map
, reduce
, remove-if
, or the loop
macro instead of writing low-level iteration code. These abstractions make your code more declarative and often more concise.
Excessive Side Effects
Minimize side effects in your functions. Functions with side effects are harder to test, reason about, and compose. Try to write pure functions that take inputs and return outputs without modifying global state.
Not Using Packages Properly
Organize your code into packages based on functionality. This improves maintainability, reduces naming conflicts, and allows for better encapsulation of implementation details.
Improper List Manipulation
Use appropriate data structures and operations for your needs. Lists in Lisp are efficient for operations at the beginning (cons, car, cdr) but inefficient for random access or operations at the end. Consider using vectors, hash tables, or other data structures when appropriate.
Not Using Format Properly
Use the format
function for formatted output instead of concatenating strings or using multiple print statements. format
provides a powerful mini-language for controlling output formatting.
Not Using Loop Invariants
Identify and compute loop invariants (values that don’t change during the loop) outside the loop. This avoids unnecessary recomputation and makes your code more efficient.
Not Using Destructuring
Use destructuring to extract values from complex data structures. Lisp provides powerful destructuring capabilities through destructuring-bind
, multiple-value-bind
, and pattern matching in macros like loop
and iterate
.
Not Using Documentation Strings
Include documentation strings for your functions, classes, and packages. Documentation strings provide valuable information to users of your code and can be accessed programmatically through functions like documentation
.
Not Writing Tests
Write tests for your Lisp code. Testing helps ensure your code works correctly and continues to work as you make changes. Use testing frameworks like FiveAM, Prove, or RT to structure and run your tests.
Not Using Proper Naming Conventions
Follow Lisp naming conventions. Use kebab-case (words separated by hyphens) for function and variable names. Use *earmuffs*
for special variables and +plus-signs+
for constants. Consistent naming makes your code more readable and idiomatic.