anchor
Insights /  
Clojure vs Common Lisp: Contrasting Approaches to Modern Development

Clojure vs Common Lisp: Contrasting Approaches to Modern Development

April 8, 2024
->
7 min read
Clojure
By
Oleksandr Druk
Clojure Developer

Two powerful dialects of the same language yet so different — Clojure and Common Lisp — each has their own fans and haters. Let’s look at what’s so adorable about these technologies, when to use them, and how easy it would be to find the team to support your web project using Clojure/Common Lisp.

Overview of Clojure and Common Lisp

Both technologies come from the same programming language — Lisp. Yet when you compare Clojure vs Common Lisp, you see they differ from the beginning of their history, starting from the approach of their creators. 

Clojure was created by Rich Hickey, who focused on specific goals and capabilities such as concurrency, multithreading, and immutable data structures. It operates on the JVM and restricts developers to using the functional paradigm, changing the established rules of Lisp-like languages. For example, it uses different syntaxes to describe different data structures besides ‘()’.

On the other hand, Common Lisp was designed by a committee, and the main focus during its design was freedom. It is imperative and has an object system — CLOS — as an option for utilizing OOP, but no one said you can’t use libraries or extensions to make it functional at the same time. Another point is that Common Lisp uses mutable data structures, giving programmers more freedom in solving tasks.

Syntax and Semantics

Clojure uses a prefix notation and is characterized by its simplicity and consistency. The data structures are immutable and implement the seq abstraction, which allows the same functions to be used for operations on different data structures. 

On the contrary, Common Lisp employs traditional infix notation with various syntactic constructs. It supports mutable data structures when each requires its own set of functions, providing a flexible approach to programming paradigms. 

We like Clojure's philosophy: "Everything is just a map." Working with data in Clojure is a pleasure, especially considering that almost all data structures implement the <span style="font-family: courier new">seq</span> abstraction, allowing the same functions to be used for operations on different data structures. Let’s compare the Clojure and Common Lisp approaches to data structures implementation:

1;;; Clojure
2  ;; List
3  (first '(1 2 3))                 ;; => 1
4  ;; Vector
5  (first [1 2 3])                  ;; => 1
6  ;; Set
7  (first #{1 2 3})                 ;; => 1
8  ;; Map
9  (first {:one 1 :two 2 :three 3}) ;; => [:one 1]
1;;; Common Lisp
2;; List
3(car '(1 2 3))                   ; => 1
4
5;; Vector
6(defparameter *v* (make-array 0 :fill-pointer t :adjustable t))
7(vector-push-extend 1 *v*)
8(vector-push-extend 2 *v*)
9(vector-push-extend 3 *v*)
10(vector-pop *v*)                 ; => 3
11
12;; Map
13(defparameter *h* (make-hash-table))
14(setf (gethash :one *h*) 1)
15(setf (gethash :two *h*) 2)
16(setf (gethash :three *h*) 3)
17(gethash :one *h*)               ; => 1

Lisp-1 vs Lisp-2 

Clojure is a Lisp-1 representative, where one name can be used only for function or only for values, while Common Lisp, as a Lisp-2 representative, has separate spaces for function and value names.

Let’s better look at the examples. Here’s a Clojure code sample:

1;;; Clojure
2;; Value
3(def lisp-1 "Hello from Lisp-1 value")
4lisp-1  ;; => "Hello from Lisp-1 value"
5
6;; Function
7(defn lisp-1 [] 
8  (println "Hello from Lisp-1 function"))
9
10lisp-1  ;; => #object[...]
11(lisp-1)  ;; => "Hello from Lisp-1 function"
12          ;; => nil

And now, let’s compare it to the Common Lisp implementation:

1;;; Common Lisp
2;; Value
3(defparameter lisp-2 "Hello from Lisp-2 value")
4lisp-2  ; => "Hello from Lisp-2 value"
5
6;; Function
7(defun lisp-2 ()
8  (print "Hello from Lisp-2 function"))
9
10lisp-2  ; => "Hello from Lisp-2 value"
11(lisp-2)  ; => "Hello from Lisp-2 function"
12          ; => "Hello from Lisp-2 function"

We consider Lisp-1 a more accurate way as it allows for more elegant implementation, though it’s more about taste, so it might not be the decision-making point for you.

Laziness

Clojure supports lazy evaluation for some data structures, meaning that sequence elements are computed only when they are needed, which can be useful when working with large or infinite sequences. Let’s look closer:

1;;; Clojure
2(cycle [1 2 3])                    ;; => [1 2 3 1 2 3 ... 1 2 3 ....]
3(take 7 (cycle [1 2 3]))           ;; => '(1 2 3 1 2 3 1)
4(range)                            ;; => [1 2 3 4 ...]
5(take 10 (filter even? (range)))   ;; => '(0 2 4 6 8 10 12 14 16 18)

While laziness can offer benefits such as improved memory usage and support for infinite sequences, it can turn its other side to you, causing performance overhead or making debugging more complex.  Even the language community called it Clojure's deadly sin, highlighting that it's essential to use it reasonably. 

Development Ecosystem

Both languages have vast ecosystems with tooling, libraries, and frameworks, but when you compare Common Lisp vs Clojure, you will see some differences. Clojure has a significant advantage — a wider range of libraries due to its integration with the JVM and access to the whole Java ecosystem. Typically, when using Clojure, things just work. 

In Common Lisp, due to many implementations, many libraries may work only with a specific implementation and not function with others. Many things need to be figured out on your own due to the absence of standards and various approaches, each chosen according to individual preferences. 

Choosing suitable libraries or frameworks for Common Lisp might be challenging, considering that some may not have been updated for several years and may not work without some effort. Therefore, many developers choose to write their own mini-libraries that satisfy their specific needs instead of looking for a particular one and overcoming its possible limitations. 

As for the tools, both languages have excellent tooling that significantly aids in development:

Clojure
Common Lisp

Performance and Scalability

Comparing Clojure vs Common Lisp performance, from my experience, even in an uberjar with JIT compilation, Clojure significantly lags behind the speed of highly optimized SBCL code. However, this is not surprising since Clojure uses the JVM, which is optimized for programs launched once but running for a long time. Still, it offers good performance and scalability, especially for concurrent and parallel programming tasks, due to its software transactional memory and concurrency primitives.

On the other hand, Common Lisp is known for its high performance, especially when utilizing compiler optimizations. It's also worth mentioning that Common Lisp has means for direct memory access, allowing for writing very fast and optimized code. The standard does not describe the aspect of concurrency in Common Lisp, so its implementation heavily depends on the compiler's implementation. Various libraries like Bordeaux Threads or Lparallel enhance interaction with threads and parallelism, and some compilers support STM; still, in this aspect, Clojure provides much more well-thought-out and user-friendly tools.

If your project needs to run on different operating systems or has requirements for working with Windows, Common Lisp can lack stability and you might often encounter problems that won't be present in Clojure due to its use of a virtual machine.

Typical Use Cases and Applications

Clojure is a general-purpose language that allows you to conveniently complete various tasks, including distributed systems and concurrency-oriented applications. Among the most common usages are:

1
APIs
Building web applications and APIs, frameworks like Ring and Compojure make web development straightforward and efficient.
2
Data
Data processing and analysis, including working with large data streams and transforming them, which is helpful, for example, in building finance analytics apps.
3
Async Capabilities
Working with asynchronous data streams with the help of libraries like core.async.

At the same time, Common Lisp is more widely applied in a broad range of tasks related to artificial intelligence:

1
Advanced Text Processing
Developing expert systems, natural language processing, and symbolic computation: libraries like CLIPS and ACL2 enable developers to create complex logical systems.
2
Embedded
Developing embedded systems where speed and direct access to resources are crucial, such as medical devices and autopilots.
3
Prototyping
Experimental development and prototyping to rapidly create and test new ideas thanks to its high expressiveness and dynamic capabilities.

Learning Curve and Community

Clojure features a relatively gentle learning curve, especially for developers already familiar with functional programming concepts and JVM. Common Lisp can have a steeper learning curve due to its rich feature set and flexibility, though it also requires more confidence from the developer. 

The Clojure community is active and friendly, with helpful members ready to solve non-trivial tasks and issues. There are regular meetings, conferences, and other events to let community members develop their skills and engage in networking. There’s also a Clojure Slack Space, where one can always reach for help and get it even from the language creators and core contributors. Many other chats and forums are also always buzzing. 

The Common Lisp community, although it is much smaller, is still active and friendly and always ready to help with any questions or concerns. Most communication is held on forums or in Telegram and IRC channels.

Interactive Development

A distinctive feature of Lisp-like languages has always been interactive or "REPL-driven” development. Both Clojure and Common Lisp excel in supporting such a development style. However, where they significantly differ is in their handling of exceptions. 

When an exception is encountered in Clojure, the developer receives a JVM stack trace, which might not be very clear and needs to be parsed and analyzed to determine the exact location of the error. Nonetheless, many excellent libraries, such as debux, portal, clojure.tools.trace, and others, significantly enhance the debugging experience.

In Common Lisp, there is a functionality called Condition System that provides extensive capabilities for detecting and handling errors. When an exception occurs, the programmer can see a list of actions depending on the exception and the context to overcome it. There's the possibility of interactively examining the stack, executing code with a different context, resuming code execution from a certain point, and much more. For those who are not seeking the easy ways, it's still possible to write custom handlers for different exceptions.

Current State

Both Clojure and the most popular implementations of Common Lisp are actively developed to this day and often have new releases with significant improvements.

However, it's worth mentioning that Clojure supports a broader ecosystem of active projects and passionate developers, making it easier for employers to find the perfect match. In contrast, locating Common Lisp programmers usually demands more effort, involving searches through specialized forums and chat groups.

Summing up

If you're choosing where to start writing a new project or looking for the right technology to extend your existing project, the choice almost always leans towards Clojure. It's more active and popular, with the JVM and the entire Java ecosystem behind it. So, for those unfamiliar with Clojure or Common Lisp, there would be a much smaller chance of encountering significant problems. Arguments "for" also include:

  • Immutability and STM that allow writing concurrent code reasonably simply, which is very important in many projects;
  • The functional paradigm and philosophy of using plain maps everywhere which make working with data very comfortable;
  • ClojureScript, which allows for full stack development, having the same language on both the backend and frontend and even sharing code between them;
  • Simple and understandable syntax, adding to the app's maintainability and your ability to scale the team when needed;
  • Active community always ready to help solve problems or tasks, which increases your chances for success. 

There are still cases when you might want to use Common Lisp to achieve better performance or shorten time to market so that you can test your hypotheses earlier and start scaling your idea. Moreover, complex data analysis projects will also require Common Lisp for its powerful symbolic computation capabilities and libraries. However, it's worth noting that its historical lack of a unified direction may pose challenges for new projects. Yet, for scenarios demanding low-level control or quick prototyping, Common Lisp remains a viable choice. 

Ultimately, the choice between Clojure and Common Lisp depends on your project's specific needs, existing expertise, and long-term goals. If you need help making the right decision for your particular case, benefit from our extensive experience. Contact us to have the experts analyze your needs and find the team to implement your project beyond expectations.

Build Your Team
with Freshcode
Author
linkedin
Oleksandr Druk
Clojure Developer

Self-taught developer. Programming languages design enthusiast. 3 years of experience with Clojure.

Shall we discuss
your idea?
Uploading...
fileuploaded.jpg
Upload failed. Max size for files is 10 MB.
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
What happens after you fill this form?
We review your inquiry and respond within 24 hours
We hold a discovery call to discuss your needs
We map the delivery flow and manage the paperwork
You receive a tailored budget and timeline estimation
Looking for a Trusted Outsourcing Partner?