Posts Tagged ‘Clojure’
[reClojure2025] Recognizing Regular Patterns in Mixed Type Sequences
Lecturer
Jim Newton is an Assistant Research Professor at EPITA, a prestigious engineering school in Paris, France. A veteran Lisp programmer since 1988, Jim has worked extensively with various dialects, including Common Lisp, SKILL++, and Clojure. His research focuses on the theoretical foundations of type systems in dynamically typed languages. At EPITA, he teaches courses on functional programming using Clojure and Scala. He is the author of several research papers and a PhD thesis titled “Representing and Computing with Types in Dynamically Typed Languages,” which forms the basis for the Regular Type Expression (RTE) library.
Abstract
While string-based regular expressions are a staple of modern programming, their application to sequences of heterogeneous types remains a relatively unexplored domain. This article details the development of Regular Type Expressions (RTEs), a framework for recognizing regular patterns within sequences of mixed-type elements in Clojure. We examine the transition from classical character-based Finite Automata to Symbolic Finite Automata, where transitions are governed by type predicates rather than literal characters. The discussion covers the theoretical challenges of implementing such a system, including the embedding of a Simple Type System (SETS) into the Clojure runtime, the construction of Deterministic Finite Automata (DFAs), and the complexities of subtype determination in a dynamic environment.
Beyond Strings: The Concept of Regular Type Expressions
Clojure programs frequently manipulate sequences—lists, vectors, or streams—that contain a variety of data types (e.g., a mixture of integers, strings, and keywords). While developers often need to validate the structure of these sequences, standard regular expressions are limited to character data. RTEs generalize the concept of regular languages to the level of types. Just as a standard regex might match the pattern a(a|b)*b, an RTE can be defined to match a sequence that “starts with an integer, contains zero or more strings or doubles, and ends with a keyword.”
Jim Newton’s work bridges the gap between the flexibility of dynamic typing and the rigor of formal language theory. By treating types as the alphabet of a regular language, RTEs allow developers to specify complex structural constraints on data. This is particularly useful in Clojure for validating macro arguments, processing heterogeneous data streams, or implementing sophisticated pattern-matching algorithms that go beyond simple structure-based destructuring.
Theoretical Challenges and Implementation
The implementation of RTEs in Clojure required solving several deep theoretical problems. Unlike character-based regex engines, where the alphabet is finite and each character is distinct, the “alphabet” of types is potentially infinite and overlapping. For example, a value might simultaneously satisfy the types Number, Integer, and Positive-Integer.
1. The Simple Type System (SETS)
To support RTEs, a fundamental type system (SETS) had to be embedded into the Clojure runtime. This system supports boolean algebraic operations on types: union, intersection, and complement. This allows for the definition of complex types such as “an element that is a String but not ‘admin'” or “an element that is either an Integer or a Keyword.”
2. Symbolic Finite Automata
The core of the RTE engine is a Symbolic Finite Automaton. In a standard DFA, a transition from one state to another is triggered by a specific character. In a Symbolic DFA, a transition is triggered if the next element in the sequence satisfies a given type predicate. A significant challenge here is ensuring the DFA remains deterministic. If an element matches multiple outgoing transitions (due to overlapping types), the automaton would become non-deterministic. To solve this, the system must be able to partition the type space into disjoint sets.
3. Subtype Determination
A critical requirement for DFA construction is the ability to determine if one type is a subtype of another. In a dynamic language like Clojure, which allows arbitrary predicates as types, this is not always decidable. Jim’s research introduces a “clever procedure” for DFA construction that maintains determinism even when the subtype relation cannot be fully determined, ensuring that the library remains robust across a wide range of use cases.
Code Sample: Using RTEs in Clojure
(require '[clojure-rte.core :refer [rte-match]])
;; Define an RTE: an Integer, followed by one or more Strings,
;; and ending with a Keyword.
(def my-pattern '(:cat Long (:* String) Keyword))
(rte-match my-pattern [1 "hello" "world" :done]) ; => true
(rte-match my-pattern [1 :done]) ; => true
(rte-match my-pattern ["wrong" :done]) ; => false
Practical Implications and Conclusion
The development of the clojure-rte library provides Clojure developers with a powerful tool for data validation and pattern recognition. It allows for the detection of unreachable code (by identifying patterns that can never be matched) and enables highly expressive type-based dispatch. Because the system is built on a solid theoretical foundation, it handles edge cases—such as empty sets or overlapping type definitions—with mathematical precision.
This project is part of a larger, multi-language research effort, with implementations also available in Scala, Python, and Common Lisp. By bringing the rigor of Symbolic Finite Automata to Clojure, Jim Newton has provided a compelling example of how theoretical computer science can enhance the practical tools of modern software engineering, particularly in the realm of dynamic, data-driven applications.
Links:
[reClojure2025] Datomic at Nubank: Simplicity Scaled
Lecturer
Jordan Miller is a Staff Software Engineer at Nubank, where she is a core member of the Datomic team, specifically focusing on developer success and the evolution of the database product. Jordan is a prominent figure in the Clojure community, serving as the co-founder of Clojure Camp, a co-author of Clojure Brain Teasers, and the producer/host of the Lost In Lambduhhs podcast. Her professional background is marked by a deep passion for functional programming and a commitment to making complex technical systems accessible to a wider audience.
Abstract
Since its acquisition by Nubank in 2020, Datomic has transitioned from a specialized tool for discerning architects to the foundational storage layer of one of the world’s largest independent banks. This article explores the symbiotic relationship between Nubank’s mission to simplify banking and Datomic’s philosophy of data-driven simplicity. We analyze the technical challenges of scaling Datomic to serve over 114 million customers, focusing on innovations such as “implicit partitions” and the architectural shifts required to maintain a global-scale financial system. Furthermore, we examine how the “Datomic-as-a-Service” model within Nubank empowers thousands of developers to build resilient, high-performance microservices while maintaining the lowest cost-to-serve in the banking industry.
Simplicity as a Mission: The Nubank and Datomic Alignment
The acquisition of Cognitect (the creators of Clojure and Datomic) by Nubank was more than a corporate transaction; it was a meeting of shared values. Nubank was founded with the explicit goal of reducing the extreme complexity and high costs of the Brazilian banking sector. At the time, millions of Brazilians were unbanked, and interest rates for personal loans often exceeded 100% per year. Nubank sought to disrupt this by building a bank that was digital-first, customer-centric, and architecturally simple.
Datomic, with its focus on immutability, “database-as-a-value,” and the separation of reads from writes, provided the perfect technical backbone for this mission. Jordan highlights that the alignment on “simplicity” allowed the partnership to flourish. Today, Nubank serves 114.3 million users with an 83% activity rate, largely fueled by word-of-mouth referrals. The bank’s ability to scale rapidly while maintaining the lowest cost-to-serve globally is a direct consequence of an architecture that prioritizes developer productivity and system reliability through Datomic.
Architectural Innovation: Scaling the Immutable Store
Scaling a database to handle the needs of over 100 million customers requires rethinking traditional storage patterns. Within Nubank, Datomic is not just a database; it is a managed service provided to over 3,000 developers working across 1,000 microservices. This “Datomic-as-a-Service” (DaaS) model ensures that individual product teams do not need to worry about provisioning or maintaining their own database clusters.
A key innovation discussed by Jordan is the concept of implicit partitions. In standard Datomic, partitions are a way to group related data for better locality and performance. However, as the number of services and the volume of data grew at Nubank, manual partition management became a bottleneck. The Datomic team developed implicit partitions to automate this process, allowing the system to handle massive growth without manual intervention. This technical advancement is part of a broader effort to make Datomic “invisible” to the developer, allowing them to focus on business logic rather than storage mechanics.
The Power of “Database-as-a-Value”
The fundamental innovation of Datomic—treating the database as an immutable value—is what enables Nubank’s high-scale operations. In a traditional SQL database, “the” database is a moving target that changes with every update. In Datomic, a process can take a “snapshot” of the database at a specific point in time and perform complex queries against that static value. This eliminates many of the concurrency issues found in traditional systems and makes it trivial to implement features like “time-travel” auditing, which is critical for financial compliance.
Developer Success and the Future of Datomic
Jordan’s role at Nubank emphasizes that the success of a technology is as much about the people using it as it is about the code itself. The Datomic team at Nubank is committed to “Developer Success,” which involves building tools, documentation, and a culture that helps engineers leverage the database effectively. This includes the creation of a “Datomic Workshop” and active engagement with the Clojure community to ensure that Datomic continues to evolve as a world-class product.
The results of this focus are evident in Nubank’s operational efficiency. By providing a unified, simple, and powerful storage abstraction, Nubank has managed to grow by an average of 13 million customers per year since 2015 while keeping its infrastructure costs significantly lower than traditional banks. This demonstrates that functional programming and immutable databases are not just academic curiosities but are viable, high-performance solutions for the world’s most demanding financial environments.
Links:
[reClojure2025] Writing Model Context Protocol (MCP) Servers in Clojure
Lecturer
Vedang Manerikar is the founder of Unravel.tech and a veteran software architect with over 15 years of experience in the Clojure ecosystem. Previously serving as the Head of Backend Engineering at Helpshift, Vedang has managed large-scale distributed systems and led complex technical migrations. At Unravel.tech, his work focuses on the intersection of Clojure and Artificial Intelligence, specifically building “Agentic Systems” and implementing Generative AI (GenAI) and Large Language Model (LLM) solutions. He is the author of mcp-cljc-sdk, a cross-platform Clojure SDK for the Model Context Protocol.
Abstract
The rapid advancement of Artificial Intelligence has created a need for standardized communication between AI agents and external systems. The Model Context Protocol (MCP), introduced by Anthropic, has emerged as a solution to the integration problem, providing a common interface for agents to interact with diverse data sources and tools. This article explores the architecture of MCP and argues that Clojure is uniquely positioned as an ideal language for implementing MCP servers. We analyze the protocol’s similarity to the Language Server Protocol (LSP), examine real-world applications in browser automation and communication platforms, and discuss how Clojure’s REPL-driven development and data-centric philosophy streamline the creation of powerful, composable AI workflows.
The Model Context Protocol: A New Standard for AI UX
At its core, MCP is an open standard designed to enable AI applications—such as Claude Desktop or Cursor—to access the external world in a structured manner. While one might ask why standard HTTP interfaces are insufficient, the answer lies in the integration problem. Without a standard, every AI agent would need a custom integration for every service (PostgreSQL, Google Drive, GitHub, etc.). MCP solves this by acting as a “USB port” for AI; developers write a server for their service once, and it becomes immediately accessible to any MCP-compliant agent.
Vedang describes MCP not just as a data access layer, but as a “baseline AI UX.” It defines how an agent discovers tools, reads resources, and follows prompts. This standardization allows for the creation of sophisticated workflows where an agent can, for example, use a Playwright MCP server to browse Hacker News, a WhatsApp MCP server to read messages, and a local filesystem server to summarize information and save it to a document. By providing a consistent interface, MCP shifts the focus from integration plumbing to the design of the agent’s behavior and user experience.
Clojure as the Premier Language for MCP
Clojure’s technical characteristics align remarkably well with the requirements of building MCP servers. The protocol is heavily reliant on JSON-RPC and the exchange of structured data, which plays directly into Clojure’s “data-as-code” philosophy. Vedang highlights several key reasons why Clojure developers are particularly well-prepared for the LLM world:
1. REPL-Driven Development: MCP servers often act as intermediaries between non-deterministic LLMs and deterministic systems. The ability to interactively test and refine server responses in a live REPL mirrors the iterative nature of working with AI.
2. Data Transformation: Clojure’s rich library for manipulating maps and vectors makes it trivial to transform complex API responses into the simplified “Context” required by LLMs.
3. Cross-Platform Capability: With the mcp-cljc-sdk, developers can write server logic once and deploy it on both the JVM (using clojure.main or GraalVM native images) and Node.js (via ClojureScript), providing flexibility in how the server is hosted and consumed.
Code Sample: Defining a Simple MCP Tool
(defmethod handle-request "tools/call" [request]
(let [{:keys [name arguments]} (:params request)]
(case name
"get-weather" (let [city (:city arguments)]
{:content [{:type "text"
:text (str "The weather in " city " is sunny.")}]})
{:error "Tool not found"})))
Practical Applications and Agentic Workflows
The power of MCP is best demonstrated through real-world “Agentic” use cases. Vedang shares examples of servers he has developed to automate complex tasks. One such server integrates with WhatsApp, allowing an AI agent to scan chat groups for business leads. Instead of a human manually reading hundreds of messages, the agent uses the MCP server to fetch the latest messages, identifies intent, and provides a summarized report of actionable items.
Another significant application is in browser automation. Using an MCP server for Playwright, an AI can navigate the web as a user would—logging into sites, extracting data from dynamically rendered pages, and performing actions. This allows for prompts like “Find me a hotel within walking distance of the reClojure conference,” where the agent autonomously searches maps, checks availability, and compares prices. These examples illustrate how MCP enables the transition from simple chatbots to true “agents” capable of multi-step reasoning and interaction with the physical or digital world.
The Future of Content-Centric AI
Looking ahead, the evolution of MCP suggests a shift toward a “content-is-king” paradigm. Current AI interactions are often limited by the UX of the chat box. However, with MCP, the focus can move toward the actual content being produced or modified—whether that is a codebase, a spreadsheet, or a document. Vedang envisions a future where multiple coding agents can work in parallel on the same repository, coordinated through a “better Git” or similar bidi-rectinal communication protocols enabled by MCP.
By standardizing the way agents interact with our tools, MCP paves the way for a new generation of software that is designed from the ground up to be AI-enhanced. For the Clojure community, this represents a significant opportunity to lead the development of the “AI UX” by building robust, composable servers that unlock the full potential of Large Language Models.
Links:
[reClojure2025] UI, Pure and Simple
Lecturer
Christian Johansen is a highly experienced software developer at the Norwegian Food Safety Authority, where he specializes in architecting robust systems using Clojure and Datomic. With a professional career spanning two decades, Christian has dedicated the last ten years to full-time Clojure development. His expertise is deeply rooted in web technologies, encompassing the entire delivery pipeline from infrastructure configuration and data modeling to sophisticated frontend implementation. Beyond his primary role, he is a prolific contributor to the Clojure ecosystem, maintaining various open-source libraries and frequently sharing insights through conference presentations, educational courses, and a collaborative professional blog.
Abstract
Modern frontend development is often plagued by the complexities of shared mutable state and pervasive side effects, which complicate testing and maintenance. This article examines the philosophical and technical foundations of achieving a truly functional user interface. By revisiting the original promise of React—that UI is a function of state—and refining it through the lens of Clojure’s immutability, we introduce a paradigm known as top-down rendering. Central to this discussion is Replicant, a small, dependency-free Clojure rendering library designed to treat UI as pure, deterministic data. We analyze the methodology of building modular UIs that decouple rendering from state management, utilize data-driven event handlers, and leverage declarative animations to create simpler, more testable applications.
Historical Context and the Pursuit of Purity
The evolution of modern web development reached a significant milestone in 2013 with the introduction of React. The framework proposed a revolutionary conceptual model: the user interface should be viewed as a pure function of application state. In this ideal scenario, developers would write code as if the entire UI were rendered from scratch with every update, leaving the heavy lifting of DOM manipulation to the framework. However, while React transformed the industry’s mental model, it did not fully deliver on the promise of functional purity. In practice, React applications often allow mutable state to proliferate throughout the component tree, leading to the very “side-effect-ridden” complexity it sought to solve.
The ClojureScript community recognized this gap early on. Developers sought a more rigorous adherence to functional principles. One notable advancement was the library Quiescent, which introduced the constraint of “top-down rendering.” In this model, components are prohibited from maintaining their own internal state or triggering their own re-renders. Instead, the entire UI is a literal projection of a central, immutable data structure. This approach aligns perfectly with Clojure’s core strengths, providing a foundation for UIs that are stateless, deterministic, and built entirely on data.
Methodology: Rendering with Replicant
Replicant serves as a realization of this top-down philosophy. It is a minimalist virtual DOM library that operates on Hiccup, a domain-specific language in Clojure that represents HTML structures as standard data vectors and maps. The core methodology involves creating pure functions that transform domain data into Hiccup data. Because these functions are pure, they are inherently predictable and easy to test in isolation.
To illustrate this, consider the rendering of a task card in a Kanban application. The developer defines a function that takes a task map and returns a Hiccup representation. Replicant’s render function then takes this data and a target DOM element to perform the initial mount. When the application state changes, the process is repeated: the pure function generates new Hiccup data, and Replicant calculates the minimal set of DOM mutations required to transition the view. This “diffing” process ensures efficiency without requiring the developer to manage state transitions manually.
Code Sample: Pure Hiccup Transformation
(defn render-task [task tags-lookup]
[:div.task-card
[:h3 (:task/title task)]
[:div.tags
(map (fn [tag-id]
(let [tag (get tags-lookup tag-id)]
[:span {:class (str "tag-" (:tag/color tag))}
(:tag/label tag)]))
(:task/tags task))]])
Advanced UI Patterns: Events and Animations
Beyond static rendering, the “pure and simple” approach extends to interactivity. In traditional frameworks, event handlers are often opaque functions that execute side effects directly. Replicant encourages data-driven event handlers. Instead of passing a callback function to an onClick attribute, the developer can pass a data structure—a vector or a map—representing the intent of the event. A central coordinator then interprets this data to update the global state. This decoupling makes the UI’s behavior as inspectable and testable as its appearance.
The same principle applies to complex UI requirements like animations and timed effects. By treating time and transitions as part of the data flow, developers can create declarative animations. These are not imperative commands to “fade in an element” but rather state-based descriptions of how an element should appear at a given point in the application lifecycle. This approach dramatically simplifies the creation of interactive features like drag-and-drop or live data streaming, as the UI remains a consistent reflection of the underlying store regardless of where the data originates.
Consequential Benefits and Conclusion
Adopting a stateless, data-centric approach to UI development yields significant benefits for software quality. Because the UI is composed of pure functions, it is highly modular and testable. Tools like Portfolio (similar to Storybook for ClojureScript) allow developers to render “scenes” in isolation by passing mock domain data to their rendering functions. This facilitates rapid prototyping and visual regression testing without the need to navigate through a live, stateful application.
Ultimately, the shift toward pure and simple UIs represents a move away from the “nashing of teeth” associated with shared mutable state. By leveraging Clojure’s immutable data structures and Replicant’s minimalist rendering engine, developers can build systems that are not only more robust and maintainable but also more enjoyable to create. The decoupling of rendering from state management allows for a degree of architectural clarity that is often missing in contemporary frontend development.
Links:
Clojure: A Modern Lisp for Durable, Concurrent Software
Clojure: A Modern Lisp for Durable, Concurrent Software
In the evolving landscape of programming languages, Clojure distinguishes itself not through novelty or aggressive feature growth, but through deliberate restraint. Rather than chasing trends, it revisits enduring principles of computer science—immutability, functional composition, and symbolic computation—and applies them rigorously to contemporary software systems. This approach results in a language that feels both deeply rooted in tradition and sharply attuned to modern challenges.
Clojure appeals to developers and organizations that prioritize long-term correctness, conceptual coherence, and system resilience over short-term convenience.
What Clojure Is and What It Aims to Solve
Clojure is a functional, dynamically typed programming language that runs primarily on the Java Virtual Machine. It is a modern Lisp, and as such it adopts a uniform syntax in which code is represented as structured data. This design choice enables powerful programmatic manipulation of code itself, while also enforcing consistency across the language.
Unlike many earlier Lisp dialects, Clojure was explicitly designed for production systems. It assumes the presence of large codebases, multiple teams, and long-lived services. As a result, its design is deeply influenced by concerns such as concurrency, data integrity, and integration with existing ecosystems.
Historical Context and Design Motivation
Rich Hickey introduced Clojure publicly in 2007 after years of observing recurring failures in large software systems. His critique focused on the way mainstream languages conflate identity, state, and value. In mutable systems, objects change over time, and those changes must be coordinated explicitly when concurrency is involved. The resulting complexity often exceeds human reasoning capacity.
Clojure responds by redefining the problem. Instead of allowing values to change, it treats values as immutable and represents change as a controlled transition between values. This shift in perspective underpins nearly every aspect of the language.
Immutability as a Foundational Principle
In Clojure, immutability is the default. Data structures such as vectors, maps, and sets never change in place. Instead, operations that appear to modify data return new versions that share most of their internal structure with the original.
(def user {:name "Alice" :role "admin"})
(def updated-user (assoc user :role "editor"))
;; user remains unchanged
;; updated-user reflects the new role
Because values never mutate, functions cannot introduce hidden side effects. This dramatically simplifies reasoning, testing, and debugging, especially in concurrent environments.
Functional Composition in Practice
Clojure encourages developers to express computation as the transformation of data through a series of functions. Rather than focusing on control flow and state transitions, programs describe what should happen to data.
(defn even-squares [numbers]
(->> numbers
(filter even?)
(map #(* % %))))
In this example, data flows through a pipeline of transformations. Each function is small, focused, and easily testable, which encourages reuse and composability over time.
Concurrency Through Explicit State Management
Clojure’s concurrency model separates identity from value. State is managed through explicit reference types, while the values themselves remain immutable. This design makes concurrent programming safer and more predictable.
(def counter (atom 0))
(swap! counter inc)
For coordinated updates across multiple pieces of state, Clojure provides software transactional memory, allowing several changes to occur atomically.
(def account-a (ref 100))
(def account-b (ref 50))
(dosync
(alter account-a - 10)
(alter account-b + 10))
Macros and Language Extension
Because Clojure code is represented as data, macros can transform programs before evaluation. This allows developers to introduce new syntactic constructs that feel native to the language rather than external utilities.
(defmacro unless [condition & body]
`(if (not ~condition)
(do ~@body)))
Although macros should be used with care, they play an important role in building expressive and coherent abstractions.
Interoperability with Java
Despite its distinct philosophy, Clojure integrates seamlessly with Java. Java classes can be instantiated and methods invoked directly, allowing developers to reuse existing libraries and infrastructure.
(import java.time.LocalDate)
(LocalDate/now)
Comparison with Java
Although Clojure and Java share the JVM, they differ fundamentally in how they model software. Java emphasizes object-oriented design, mutable state, and explicit control flow. Clojure emphasizes immutable data, functional composition, and explicit state transitions.
While Java has incorporated functional features over time, its underlying model remains object-centric. Clojure offers a more radical rethinking of program structure, often resulting in smaller and more predictable systems.
Comparison with Scala
Scala and Clojure are often compared as functional alternatives on the JVM, yet their philosophies diverge significantly. Scala embraces expressive power through advanced typing and rich abstractions, whereas Clojure seeks to reduce complexity by minimizing the language itself.
Both approaches are valid, but they reflect different beliefs about how developers best manage complexity.
Closing Perspective
Clojure is not designed for universal adoption. It demands a shift in how developers think about state, time, and behavior. However, for teams willing to embrace its principles, it offers a disciplined and coherent approach to building software that remains understandable, correct, and adaptable over time.