anchor
Blog /  
Clojure protocols

Clojure Protocols and the Path through the Expression Problem

April 18, 2024
->
6 min read

Understanding Clojure Protocols

So what are those? Protocols in Clojure describe a set of functions a data type must implement to be used polymorphically. Polymorphism, in turn, enables us to create reusable, extensible, and easy-to-maintain code at higher abstraction levels that can work with various data types rather than having to write separate implementations for each type.

Clojure Protocols define a standard "contract" that various data types can adhere to, allowing you to consistently create business logic that can work with those data types. Pretty similar to interfaces in OOP languages like Java, except protocols can be extended to types that don’t declare it.

Protocols are crucial for building adaptability and extensibility into your applications from the ground up. They allow you to seamlessly add new data types and functionality to your software without reworking or modifying your existing codebase. Rather than being limited to a rigid set of predefined features, protocols enable you to extend your applications in a modular, scalable way. New capabilities can be added in isolated, well-encapsulated namespaces or packages without disrupting the rest of your system.

Breaking Down the Expression Problem

As our programs grow, we must provide new data types and operations that extend the ones we already have. We want this to be a true extension, not just modifying already written code but respecting the existing abstractions. We likely want these extensions to happen in a separate namespace or package without breaking everything. 

This is where the so-called Expression Problem comes into play. The expression problem describes the challenge of adding new features, products, or data types to your software without reworking or rewriting your existing codebase.

Imagine you've built a successful e-commerce platform for selling widgets. But now your business is expanding into a new product line – gadgets. Ideally, you'd want to add support for the latest gadget product type to your platform without modifying the underlying code heavily designed for widgets.

This is the essence of the Expression problem. As your business requirements change and expand, your technology must grow and adapt in lockstep – without major reworks' risk, cost, and disruption. Failing to address the Expression problem can leave your software systems inflexible and unable to keep pace with your evolving business needs. You end up trapped in a cycle of costly, time-consuming rewrites just to add new functionality.

The Benefits of Protocols

{{bb25-5="/custom-block-to-blog/four-page"}}

Declaration of protocols

That was a theory, so let's get our hands dirty and define a simple protocol:

{{bb25-1="/custom-block-to-blog/four-page"}}

We defined the Length protocol with a single method called length, which has been pretty easy so far. But what happens under the hood?

  • a Var Length will be defonce-ed in the namespace, 
  • a Clojure immutable map with protocol information will be constructed,
  • a Java interface with Protocol methods and their signatures will be dynamically generated and loaded into the classloader,
  • the protocol map mentioned above is alter-var-root-ed to be the value of the Length var.

Let's extend a few Clojure built-in types with our protocol. We can use extend, which is pretty low-level for this. Still, usually, we'll be using extend-type if we want to extend a given type or class with one or more protocols or extend-protocol if we're going to extend one or more types or classes with a given protocol. extend-type and extend-protocol are macroses and expand in many extend calls under the hood. So we can write something like this:

{{bb25-2="/custom-block-to-blog/four-page"}}

or like this:

{{bb25-3="/custom-block-to-blog/four-page"}}

To define a default protocol implementation, we can extend it for java.lang.Object, as every data type in Clojure is derived from it.

{{bb25-4="/custom-block-to-blog/four-page"}}

Protocols vs. Multimethods

Clojure offers two primary tools for solving the expression problem and building adaptable software: protocols and multimethods. You might wonder, "If multimethods can solve the expression problem, why do I also need protocols?"

Protocols offer a critical advantage by allowing you to group related functions into cohesive abstractions. Rather than having independent, scattered multimethods, protocols will enable you to encapsulate all the necessary functionality for working with a particular data type or feature set.

This grouping of functions under a protocol provides two essential benefits for businesses:

{{bb25-6="/custom-block-to-blog/four-page"}}

In essence, protocols give you the best of both worlds: the flexibility to extend your software in new directions (solving the expression problem) and the performance benefits of tightly integrated, type-based dispatching. This allows you to build applications that are not only highly adaptable but also scalable and cost-effective to maintain.

For businesses seeking to future-proof their technology investments, protocols' advantages over multimethods can be game-changers. By promoting modularity, maintainability, and raw performance, protocols empower you to keep pace with evolving business requirements without sacrificing the integrity and efficiency of your underlying software systems.

Takeaway

Protocols introduce polymorphism, enabling you to seamlessly add new data types, features, and functionality to your Clojure-based application without reworking or compromising the existing codebase. The expression problem is a common challenge for growing businesses, and protocols provide an elegant solution. They give you the freedom to extend types or classes written, for example, in other packages imported into your project as a dependency without fear of breaking everything.

Clojure's hosting on platforms like the JVM allows protocols to take advantage of specialized dispatch optimizations. This makes protocol-based code faster and more efficient than more general-purpose polymorphism mechanisms.

Protocols empower you to build applications that evolve and scale alongside your business. By decoupling your core logic from specific data and feature implementations, you can more easily adapt to changing market demands, integrate with new partners, and expand into new product areas. If you'd like to discuss how Clojure can future-proof your technology investments, we’ll be happy to have a talk.

{{about-yur-druk-green="/material/static-element"}}

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