Some Civitas notebooks should only be run locally

(Work-In-Progress Draft)

Usually, when we wish to create Clojure Civitas posts, we enjoy the fact that Civitas runs our notebooks in the GitHub Actions as it renders the website.

While this is the default behavior, sometimes, we cannot expect our notebooks to be run in GitHub Actions. For example, they may depend on a local file or service.

This notebook, for example, assumes that you have a local secrets file, and it will not work without it!

(slurp "/home/daslu/my-secret.txt")
"this is my secret!!!!!\n"

If you are the author of such a notebook, the recommended practice is to render the notebook locally usinc Clay in Quarto .qmd format, and include that file in your Pull Request.

The .qmd file is all that Civitas needs to include your notebook in the website. As long as the .qmd file is included in the PR, and is not older than your source .clj file, Civitas will just rely on it and not even try to generate it in GitHub Actions.

To do that, you will need to make the file locally with a Quarto target.

Here are two ways to do that:

  1. Use the command line. Note that here, we use the path to the notebook, relative to src.
clojure -M:clay -A:markdown scicloj/clay/skip_if_unchanged_example.clj
  1. … Or do the same in Clojure code.
(comment
  (require '[scicloj.clay.v2.api :as clay])
  (clay/make! {:source-path "scicloj/clay/skip_if_unchanged_example.clj"
               :aliases [:markdown]}))

Now, need to git add the generated qmd file. Here is how.

(WIP)

Permalink

The Hidden Lessons in a re-frame App

by Laurence Chen

I took over a web application whose frontend was built with re-frame, and not long after I started working on it, I felt a bit of discomfort. So, I decided to investigate the source of that discomfort. And my first suspect was re-frame.

The reason I had this suspicion was mainly because in the frontend stack most commonly used at Gaiwan, we usually only use Reagent.

So let’s start with a simpler example comparison—maybe that will shed some light.

Understanding the Abstraction Cost of re-frame Through an Example

Feature description: Display a button and a number. Each time the button is clicked, the number increments by one.

  • Counter implemented using Reagent
(ns example.core
  (:require [reagent.core :as r]
            [reagent.dom :as rdom]))

(defonce counter (r/atom 0))

(defn counter-component []
  [:div
   [:p "Count: " @counter]
   [:button {:on-click #(swap! counter inc)}
    "Increment"]])

(defn ^:export init []
  (rdom/render [counter-component]
               (.getElementById js/document "app")))
  • Counter implemented using re-frame
(ns example.core
  (:require [reagent.dom :as rdom]
            [re-frame.core :as rf]))

;; -- Event Handlers ---------------------

(rf/reg-event-db
  :initialize
  (fn [_ _]
    {:count 0}))

(rf/reg-event-db
  :increment
  (fn [db _]
    (update db :count inc)))

;; -- Subscriptions ----------------------

(rf/reg-sub
  :count
  (fn [db _]
    (:count db)))

;; -- View Component ---------------------

(defn counter-component []
  (let [count @(rf/subscribe [:count])]
    [:div
     [:p "Count: " count]
     [:button {:on-click #(rf/dispatch [:increment])}
      "Increment"]]))

;; -- Entry Point ------------------------

(defn ^:export init []
  (rf/dispatch-sync [:initialize])
  (rdom/render [counter-component]
               (.getElementById js/document "app")))

After comparing the two, I found it difficult to conclude that “re-frame was the reason I felt uncomfortable.”

It’s true that when first using re-frame, the code seems to get longer. However, considering that the project I’m maintaining is a multi-page web application, the fact that re-frame requires you to extract pieces like subscriptions and event handlers from view components isn’t really a problem—because even without re-frame, such extractions would still be necessary in a project with multiple pages and complex UI components.

So, I decided to change my approach and directly investigate the discomfort by examining the actual development challenges.

The Difficulty of Locating Code to Modify

When taking over a new project, the earliest phase is often the one where I use grep the most. Due to a lack of familiarity, even if I have a clear idea of the feature I want to implement and how to implement it, I still spend time locating where the code needs to be changed.

Let’s take a UI workflow modification example to illustrate:

Refer to the image below. In this workflow, the user clicks two different submit buttons. Clicking Submit Button 1 opens a modal page to collect more detailed information from the user, and shows Submit Button 2. When Submit Button 2 is clicked, the collected information is sent to the backend to trigger a chain of subsequent actions.

Now consider a required modification: “Due to changes in backend logic, simplify the two-step submission into a single-step submission.”

Here’s how I approached the actual task:

  1. Skim through the view page code, find the event name dispatched by Submit Button 1, and link the view page to Event Handler 1.
  2. Skim through the modal view code, find the event name dispatched by Submit Button 2, and link the modal view to Event Handler 2.
  3. I needed Submit Button 1 to dispatch an event that includes all the data that was previously only gathered during the Submit Button 2 dispatch. So, besides finding where the modal view dispatches Event 2, I also had to trace all the subscriptions used in that modal view. Also, I had to examine the subscriptions associated with Submit Button 1.
  4. It was during this “tracing upward from dispatch to subscriptions” process that I felt a high cognitive load. I often had to go up one to three levels of function calls to locate them. Also, due to similarities between the view page and the modal view code, it was easy to get confused.
  5. After mentally organizing the cognitive load from steps 1–4, I was finally able to start rewriting the code.

Mixing of Concerns: The Real Source of Complexity

Only after carefully analyzing the development process and recognizing moments of elevated cognitive load was I able to articulate my challenge in code.

Here’s what I encountered:

(defn modal-form [{:keys [data3]}]
  (let [data4 @(rf/subscribe [:user/data4])]
    [:div.modal
     ...
     [:button {:on-click #(rf/dispatch [:event2 {:data3 data3 :data4 data4}])}
      "Submit"]]))

(defn submit-button1 [data1]
  ...
  (let [data2 @(rf/subscribe [:user/data2])]
    ... 
    [:button {:on-click #(rf/dispatch [:event1 {:data1 data1 :data2 data2}])}
     "Submit"]))

(defn show-list-table [{:keys [data1]}]
  ...
  ... ;; table data
  ...
  [submit-buttion-1 data1])

(defn main-page []
  (let [other-states @(rf/subscribe [:ui/other-states])
        should-show-modal @(rf/subscribe [:ui/show-modal])]
    [:div
     (when should-show-modal
       [modal-form other-states])
     [show-list-table other-states]]))

In this code structure, you can see that rf/subscribe and rf/dispatch are scattered throughout many components. Especially when modal-form and submit-button1 are relatively small components, each still handles its own subscriptions and dispatches, making the control flow difficult to follow. Readers have to understand the UI, its state dependencies, and the resulting triggered events—all at once. This mixture of concerns increases cognitive load significantly.

Design Pattern for Separating Concerns

Fortunately, this kind of cognitive load is improvable. The solution is the Presentational/Container Components Pattern.

By applying this pattern, we separate data sources from UI rendering. In short:

  • Presentational components focus only on how things should look. They read data from arguments, are unaware of where the data comes from, and hand off user events via injected handlers.
  • Container components focus on what data to show and what events to dispatch when the user interacts. They handle state and behavior.

After applying this pattern, the code would be refactored as follows:

;; Presentational Component (no subscribe)
(defn submit-button1-view [{:keys [data1 data2 on-submit]}]
  [:button {:on-click #(on-submit {:data1 data1 :data2 data2})}
   "Submit"])

(defn modal-form-view [{:keys [data3 data4 on-submit]}]
  [:div.modal
   ...
   [:button {:on-click #(on-submit {:data3 data3 :data4 data4})}
    "Submit"]])

;; Container Component (only state and dispatch)
(defn show-list-table-container []
  (let [data1 @(rf/subscribe [:user/data1])
        data2 @(rf/subscribe [:user/data2])]
    ...
    ... ;; table data
    ... 
    [submit-button1-view
     {:data1 data1
      :data2 data2
      :on-submit #(rf/dispatch [:event1 %])}]))

(defn modal-form-container []
  (let [data3 @(rf/subscribe [:user/data3])
        data4 @(rf/subscribe [:user/data4])]
    [modal-form-view
     {:data3 data3
      :data4 data4
      :on-submit #(rf/dispatch [:event2 %])}]))
      
;; main-page
(defn main-page []
  (let [should-show-modal @(rf/subscribe [:ui/show-modal])]
    [:div
     (when should-show-modal
       [modal-form-container])
     [show-list-table-container]]))

Conclusion: Correctly Applying re-frame Is More Than Just Using the Framework

Looking back on the whole process, re-frame itself wasn’t actually the root cause of my initial discomfort. Its event-driven architecture and data flow offer real benefits for large, state-heavy applications. However, when view components mix data access (via subscriptions) and behavior logic (via dispatch), even simple UI tasks can create high cognitive burdens for developers.

What I really struggled with was the lack of proper separation of concerns. This isn’t a problem unique to re-frame; any frontend architecture can suffer from it when poorly organized.

Thus, my biggest takeaway is:

When using re-frame, consciously applying the Presentational/Container Component pattern can significantly reduce cognitive load and improve the predictability and maintainability of your code.

Coming back to re-frame itself: its event and subscription mechanisms are designed to help developers build predictable, testable, and composable applications. But whether these mechanisms fulfill their potential depends on whether developers use them in a structured and intentional way.

re-frame’s design is well-suited to complex systems. But for that very reason, it reminds us:

The more complex the system, the more we need to actively create good structure, instead of expecting the framework to make the right decisions on its own.

>

Permalink

Introducing seqfind.com

I'm officially launching seqfind.com – the Clojure(script) code explorer.

Technically, this is a relaunch. I did a soft launch with just a handful of repos a few years ago, but for various reasons I was unable to push it to the next stage until now. It now contains the majority of the Clojure OSS ecosystem on GitHub, courtesy of the fantastic Dewey data set maintained by phronmophobic (Adrian Smith) which uses clj-kondo analysis to extract data from GitHub.

This post explains what seqfind is, how to use it, what's new and what's planned since that initial early release back in 2022.

A Brief Intro

Would you like to optimize your learning of Clojure? Would you like to focus on learning only the most useful parts of the language first? Take this lesson from second language learning: learn the expressions in order of frequency of use.

This article was the inspiration for seqfind and also a big part of my Clojure journey. I realized that the approach of becoming familiar with the most frequently used parts of the Clojure language could also be extended to the many libraries in its ecosystem to optimize learning. Further, because its a lisp, traversing code as a set of lists makes it well-suited for analysis.

The idea was clear: make it easy to look at any library to find its most useful parts and how people are using them in real applications. In most libraries, a small number of functions account for the vast majority of usage, while the rest are infrequently used. seqfind is there to help you identify and study them.

This project is a living counterpart to project documentation. It's a database of real-world usage examples of functions from across the open source Clojure ecosystem on GitHub.

My hope is that this helps both library users as well as their authors. Perhaps it can complement written docs and allow maintainers to identify usage patterns they weren't aware of and never intended.

An Example: clojure.core/map

Suppose you're new to Clojure and want to see more examples of using map. You navigate to the corresponding seqfind page at seqfind.com/clojure/clojure/clojure.core/map/usages and see something like this:

Screenshot of seqfind.com showing usage examples for the clojure.core/map function.

The results are fairly illuminating. From the fn's docstring you learn that map returns a lazy sequence and some other details, but the writing is fairly terse:

Returns a lazy sequence consisting of the result of applying f to the set of first items of each coll, followed by applying f to the set of second items in each coll, until any one of the colls is exhausted. Any remaining items in other colls are ignored. Function f should accept number-of-colls arguments. Returns a transducer when no collection is provided.

The seqfind results show a different angle. map can be used in at least a few different ways (in order):

  • with a simple fn name like count
  • as an into transducer's xform with function composition through juxt
  • with an shorthand anonymous fn #(...)
  • as part of a ->> sequence transformation macro with a keyword argument :e

If you're new to Clojure, these examples can teach you many things that aren't plainly evident from the docs.

While I chose to showcase the well-known map fn here, what I want to emphasize is that you can get similar examples for most functions in the entire ecosystem - whether they are from any of the official Clojure libraries or some relatively unknown library with just a few users.

What I found very satisfying was that I used seqfind myself in the creation of seqfind! While trying to figure out different parts of Fulcro (which seqfind is built on), I regularly consulted the relevant function profiles in seqfind to help me better understand what I read in Fulcro's docs.

Some Details

Quite a bit has changed since that initial early release in 2022:

  • URL structure now takes inspiration from cljdoc.org:
    • /clojure/clojure/functions: the repo's profile showing popular functions
      • /clojure/clojure/clojure.core the namespace's profile showing its functions
      • /clojure/clojure/clojure.core/map/usages the function's profile showing its usages
      • etc.
  • search and pagination
  • a LOT more repos

Admittedly, much more polish is needed for both performance and UI improvements, but since it's already a useful resource and having it live will help me learn more about making better, I decided to go ahead with the release.

Coming Soon

You can expect many more features and improvements to land in the near future. I'll continue to refine the data pipeline and improve usability.

Since my post on Slack last week about relaunching, I addressed the few bugs that were reported in that thread:

  • repo stargazer counts have been updated to be much more recent (not realtime)
  • a small UI bug duplicating fn arities in the tooltip was fixed
  • a bug in the DBT pipeline was fixed, adding approximately 400 repos that were previously missed

Please contact me on Slack @sheluchin if you have any feedback at all.

As for next, my top priorities are:

  • stability, performance
  • UI cleanup
  • a REST API

At the moment, I'm not worried about the complete lack of mobile support, since I don't think this tool is very likely to be used on the go - you'll most likely be sitting at a desktop. I'm happy to re-prioritize this if I get feedback to the contrary.

I hope you find it useful. Go explore the ecosystem at seqfind.com and please let me know what you think!

Permalink

Scaling fraud defense: How Nubank evolved its risk analysis platform

At the Engineering Meetup #13, a packed audience gathered to learn about one of the most critical systems protecting Nubank and its customers: the Defense Platform. 

This session was led by three experts deeply involved in building and supporting this technology: Alessandro Bottmann, a Staff Software Engineer with over 30 years in tech, known for leading engineering teams and delivering high-impact solutions; Jairo Júnior, a Senior Software Engineer passionate about architecture and scalable systems; and Rafael Rodrigues, a seasoned Solutions Architect at AWS with more than 15 years of experience in cloud computing.

Together, they walked us through the architecture, evolution, and future of the system that powers fraud prevention across all of Nubank’s products and regions.

A platform born from complexity

In the early days, fraud detection at Nubank was decentralized. Each product team had to integrate with separate systems and services to detect and respond to suspicious activity. It worked, but it didn’t scale. There were inefficiencies, inconsistencies, and long delays to get a new defense up and running.

That’s where the idea for the Defense Platform was born. Rather than continuing to patch together fragmented solutions, the team envisioned a unified system, one that could handle millions of events per day, integrate easily with new products, and operate reliably across multiple regions. Over the last five years, this vision became reality.

Today, the platform processes hundreds of millions of events every day with over 99.98% availability. It’s used in Brazil, Mexico, and Colombia, and is ready to expand even further. All of this is made possible by a design focused on reliability, scalability, low-latency execution, and cost efficiency: a system built for long-term evolution.

What happens when a transaction hits the platform?

Let’s say a PIX transaction comes in. The Defense Platform receives the event with all its context: origin, destination, amount, and more. From there, a component called the Flow Orchestrator takes over. It knows exactly what that type of event needs to go through: in the case of PIX, over 40 different processes might run in parallel, including both business rules and machine learning models.

These components pull data from what Nubank calls features, a flexible system that can access internal databases, third-party providers, and other services. Once the rules and models evaluate the risk, the orchestrator makes a decision: is the transaction safe to proceed, or should it trigger an action?

Actions can happen in real time, like blocking the transaction or displaying a warning in the app, or they can run asynchronously, such as opening an internal case for investigation. Either way, the event is logged and pushed through Nubank’s distributed ETL system — which aggregates over 100 terabytes of logs every day — for continuous analysis and improvement.

Built to handle millions, designed to adapt

The numbers behind the platform are massive. It processes around 450 million events per day, generating about 5 million internal requests per minute. That’s because a single event, like a PIX transaction, may trigger dozens of downstream processes. 

To support this load, Nubank relies on a highly distributed architecture with 20 “shards” in Brazil alone, essentially full replicas of the entire system that help spread the traffic and maintain low latency for millions of users.

This architecture is powered by a tech stack rooted in Clojure, Datomic (on top of DynamoDB), and Kafka. Machine learning models are built in Python, and observability is deeply embedded at every layer through logs, traces, and real-time metrics.

Optimizing the heart of risk detection

At its core, the platform revolves around a dual structure: detection and action. Detection can be powered by hand-written rules or machine learning models. Models tend to be slower to execute, so the platform uses a clever strategy to determine when they’re needed. If a rule can already confidently mark a transaction as high-risk, the model is skipped entirely, saving both time and resources.

All defenses go through a shadow testing phase before they’re fully released. In this stage, new rules and models run in parallel with the production environment, using real inputs but not affecting real users. This allows the team to validate accuracy and performance in live conditions without introducing risk.

The orchestrator, reimagined

The first version of the orchestrator was simple but inefficient. It executed components layer by layer, which meant even low-latency processes had to wait unnecessarily for others to finish. Recently, the team refactored this into a DAG-based model (Directed Acyclic Graph), using an open-source library developed at Nubank called Nodely.

In the new model, each component waits only for the data it depends on — nothing more. As a result, processing time in complex flows dropped from 550 milliseconds to around 350, a significant improvement when you’re dealing with millions of transactions every day.

Making defense more accessible

Right now, writing rules still requires engineering work. But the team is working to change that. By moving more of the platform to a declarative, configuration-based model, the goal is to let fraud analysts and other non-engineering roles contribute directly. That means faster deployments, greater ownership, and better responsiveness to emerging threats.

The platform is also evolving to give product teams more visibility into the operational cost of each fraud defense. With that, teams will be able to make smarter decisions not just about risk, but about efficiency too.

Insights from AWS: Taking fraud detection further

Following the walkthrough of Nubank’s platform, Rafael Rodrigues from AWS gave a practical look at how financial institutions are using cloud tools to fight fraud. He showed how Amazon Rekognition can handle document and facial verification, including liveness detection. 

He also demonstrated how Textract can extract data from ID documents, and how SageMaker can be used to train fraud detection models, whether through traditional supervised learning or more advanced techniques like anomaly detection and graph-based modeling.

One highlight was AWS CleanRooms, which allows companies to collaborate securely on shared datasets without exposing sensitive data, opening the door to joint anti-fraud efforts across institutions.

Why graphs change the game

Graph-based modeling was a standout topic. Unlike traditional fraud detection, which looks at transactions in isolation, graph models reveal the relationships between users, devices, IPs, and more.

Rafael showed how modeling these connections can quickly expose fraud rings, identify stolen identities being reused across multiple accounts, and spot suspicious behavior that’s otherwise hard to detect. With Amazon Neptune as the graph database and SageMaker for training, the potential for more powerful, contextual defenses is clear.

Final thoughts

Fraud never stops evolving — and neither does our platform. With an architecture that combines performance, flexibility, and observability, the Defense Platform is constantly improving. Whether it’s adopting new models, rethinking orchestration, or empowering more people to build defenses, the mission stays the same: keep customers safe, at scale.

And by combining in-house expertise with external collaboration — like the tools and services from AWS — we’re building a security ecosystem that grows stronger with every transaction.

The post Scaling fraud defense: How Nubank evolved its risk analysis platform appeared first on Building Nubank.

Permalink

Top 11 Deep Learning Frameworks in 2025: Comparative Guide & Use Cases

As we move halfway into 2025, the deep learning ecosystem is more vibrant and diverse than ever before. A new generation of software libraries is making it simpler to design, train, and deploy powerful neural networks.

Whether you're targeting cloud infrastructure, edge devices, or research-grade experimentation, these platforms offer sophisticated support for distributed training, hardware acceleration, and streamlined model optimization, catering to workloads ranging from real-time inference to massive-scale data processing.

Moreover, developer-focused enhancements such as modular APIs, plug-and-play components and more have significantly lowered the barrier to entry. This has enabled even newcomers to construct complex AI systems with confidence.

Beyond the libraries themselves, interoperability standards and distributed-training toolkits are gaining traction. These technologies accelerate research-to-production pipelines by enabling easy model export, cross-framework compatibility, and seamless scaling across machines and accelerators.

In this post, we present a curated list of the 15 most popular deep learning frameworks shaping the AI landscape in 2025. We'll explore each tool’s unique strengths and innovations to help you choose the perfect foundation for your next AI project.

What is a Deep Learning Framework?

A deep learning framework is a software library or tool that provides building blocks to design, train, and validate deep neural networks. It simplifies complex mathematical operations, model architecture setup, and GPU acceleration, making it easier for developers and researchers to build AI models.

Popular frameworks like TensorFlow, PyTorch, and Keras offer pre-built components, optimization algorithms, and APIs to streamline development, allowing users to focus on model innovation rather than low-level programming.

Why Use a Deep Learning Framework?

Using a deep learning framework streamlines the development of neural networks by handling complex tasks like tensor operations, backpropagation, and hardware acceleration.

It saves time, reduces coding errors, and provides pre-built modules for common functions, enabling faster experimentation and deployment.

Frameworks like TensorFlow and PyTorch also support scalability, integration with cloud platforms, and strong community support, making them ideal for both research and production environments in AI development.

11 Most Popular Deep Learning Frameworks to Know in 2025

Each framework is built in a different manner for different purposes. Here, we look at some of the most popular 11 deep learning frameworks (in no particular order) for you to get a better idea of which one of the following is a popular deep learning framework and is the perfect fit for solving your business challenges.

1. TensorFlow

TensorFlow best deep learning frameworks

TensorFlow is inarguably one of the most popular deep learning frameworks. Developed by the Google Brain team, TensorFlow supports languages such as Python, C++, and R to create deep learning models along with wrapper libraries. It is available on both desktop and mobile.

The most well-known use case of TensorFlow has got to be Google Translate coupled with capabilities such as natural language processing, text classification, summarization, speech/image/handwriting recognition, forecasting, and tagging.

TensorFlow’s visualization toolkit, TensorBoard, provides effective data visualization of network modeling and performance.

TensorFlow Serving, another tool of TensorFlow, is used for the rapid deployment of new algorithms/experiments while retaining the same server architecture and APIs. It also provides integration with other TensorFlow models, which is different from the conventional practices and can be extended to serve other models and data types.

TensorFlow is one of the most preferred deep learning frameworks as it is Python-based, supported by Google, and comes loaded with top-notch documentation and walkthroughs to guide you.

Highlights of TensorFlow

  • Robust multiple GPU support
  • Graph visualization and queues using TensorBoard
  • Known to be complex and has a steep learning curve
  • Excellent documentation and community support

*What is TensorFlow best suited for? *

  • Large-scale machine learning model development
  • Production-ready deployment on multiple platforms
  • Distributed training across GPUs and TPUs
  • Deep learning for computer vision applications
  • Natural language processing and speech recognition
  • Custom neural network architecture experimentation
  • Cross-platform mobile and embedded AI deployment

2. TORCH/PyTorch

pytorch-top-deep-learning-framework
Torch is a scientific computing framework that offers broad support for machine learning algorithms. It is a Lua based deep learning framework and is used widely amongst industry giants such as Facebook, Twitter, and Google.

It employs CUDA along with C/C++ libraries for the processing and was made to scale the production of building models and overall flexibility. As opposed to Torch, PyTorch runs on Python, which means that anyone with a basic understanding of Python can get started on building their deep learning models.

In recent years, PyTorch has seen a high level of adoption within the deep learning framework community and is considered to be quite the competitor to TensorFlow. PyTorch is basically a port to Torch deep learning framework used for constructing deep neural networks and executing tensor computations that are high in terms of complexity.

Given the PyTorch framework’s architectural style, the entire deep modeling process is far more straightforward as well as transparent in comparison to Torch.

Highlights of PyTorch

Excellent at rapid prototyping
Strong support for GPUs as parallel programs can be implemented on multiple GPUs
Provides cleaner interface and is easier to use
Facilitates the exchange of data with external libraries

What is PyTorch best suited for?

  • Dynamic computational graphs for flexible experimentation
  • Research-focused deep learning model development
  • Seamless integration with the Python scientific stack
  • Rapid prototyping and iterative model testing
  • Cutting-edge applications in NLP and vision
  • Strong community support for academic research
  • Optimized training on GPUs for high performance

3. DEEPLEARNING4J

The j in Deeplearning4j stands for Java. Needless to say, it is a deep learning library for the Java Virtual Machine (JVM). It is developed in Java and supports other JVM languages like Scala, Clojure, and Kotlin.

Parallel training through iterative reduces, micro-service architecture adaption coupled with distributed CPUs and GPUs are some of the salient features when it comes to Eclipse Deeplearning4j deep learning framework.

Widely adopted as a commercial, industry-focused, and distributed deep learning platform, Deeplearning4j comes with deep network support through RBM, DBN, Convolution Neural Networks (CNN), Recurrent Neural Networks (RNN), Recursive Neural Tensor Network (RNTN) and Long Short-Term Memory (LTSM).

Since this deep learning framework is implemented in Java, it is much more efficient in comparison to Python. When it comes to image recognition tasks using multiple GPUs, DL4J is as fast as Caffe. This framework shows matchless potential for image recognition, fraud detection, text-mining, parts of speech tagging, and natural language processing.

With Java as your core programming language, you should undoubtedly opt for this deep learning framework if you’re looking for a robust and effective method of deploying your deep learning models to production.

*Highlights of DL4J *

Brings together the entire Java ecosystem to execute deep learning
Can process massive amounts of data quickly
Includes both multi-threaded and single-threaded deep learning frameworks
Can be administered on top of Hadoop and Spark

What is DEEPLEARNING4J best suited for?

  • Enterprise-grade deep learning in the Java ecosystem
  • Integration with big data tools like Hadoop
  • Scalable training on CPUs and GPUs
  • Building production-ready AI in JVM languages
  • Deep learning for business and enterprise solutions
  • Distributed training across multi-node clusters
  • Support for reinforcement learning and neural nets

4. THE MICROSOFT COGNITIVE TOOLKIT/CNTK

CNTK is undoubtedly one of the most popular deep learning frameworks, known for its easy training and use of a combination of popular model types across servers. The Microsoft Cognitive Toolkit (earlier known as CNTK) is an open-source framework for training deep learning models. It performs efficient Convolution Neural Networks and training for image, speech, and text-based data.

Given its coherent use of resources, the implementation of Reinforcement Learning models or Generative Adversarial Networks (GANs) can be done quickly using the toolkit. The Microsoft Cognitive Toolkit is known to provide higher performance and scalability as compared to toolkits like Theano or TensorFlow while operating on multiple machines.

When it comes to inventing new complex layer types, the users don’t need to implement them in a low-level language due to the fine granularity of the building blocks. The Microsoft Cognitive Toolkit supports both RNN and CNN type of neural models and is thus capable of handling image, handwriting, and speech recognition problems. Currently, due to the lack of support on ARM architecture, the capability on mobile is relatively limited.

Highlights of The Microsoft Cognitive Toolkit
Highly efficient and scalable for multiple machines
Supported by interfaces such as Python, C++, and Command Line
Fit for image, handwriting and speech recognition use cases
Supports both RNN and CNN type of neural networks

What is THE MICROSOFT COGNITIVE TOOLKIT/CNTK best suited for?

  • Training deep learning models at a massive scale
  • Optimized performance on multi-GPU and clusters
  • Speech recognition and natural language processing tasks
  • Integration with Microsoft Azure and cloud services
  • Custom neural network design with fine-grained control
  • High-performance computing for enterprise AI solutions
  • Support for reinforcement learning and deep networks

5. KERAS

Keras library was developed, keeping quick experimentation as its USP. Written in Python, the Keras neural networks library supports both convolutional and recurrent networks that are capable of running on either TensorFlow or Theano.

As the TensorFlow interface is tad challenging and can be intricate for new users, Keras deep learning framework was built to provide a simplistic interface for quick prototyping by constructing active neural networks that can work with TensorFlow.

In a nutshell, Keras is lightweight, easy-to-use, and has a minimalist approach. These are the very reasons as to why Keras is a part of TensorFlow’s core API.

The primary usage of Keras is in classification, text generation, and summarization, tagging, translation along with speech recognition, and others. If you happen to be a developer with some experience in Python and wish to delve into deep learning, Keras is something you should definitely check out.

Highlights of Keras

Easy-to-understand and consistent APIs
Seamlessly integrates with TensorFlow workflow.
Supports multiple deep learning backends
Built-in support for distributed training and multi-GPU parallelism

What is Keras best suited for?

  • Quick prototyping of deep learning models
  • Beginner-friendly API for neural network development
  • Seamless integration with multiple backend engines
  • Building and training models with minimal code
  • Experimentation with custom layers and architectures
  • Educational purposes and teaching deep learning concepts
  • Deploying lightweight models to mobile and web

Read More:- Top 11 Deep Learning Frameworks

Permalink

Inside Nubank’s engineering: Discover the technical backstage powering our innovation

At Nubank, building technology for financial services goes far beyond simply creating robust systems. Here, every technical decision aims to support millions of customers with security, efficiency, and constant innovation. 

In this article, we’ll reveal some of the behind-the-scenes of our engineering, highlighting how we approach continuous delivery, immutability, standardization, cloud computing, intelligent data use, rigorous security, and artificial intelligence.

Continuous delivery: speed with confidence

Our engineering enables frequent and safe releases of new features. We heavily invest in automated testing practices to ensure each change is validated before reaching users. 

We use techniques such as canary deployments, controlled rollouts, and feature flags, allowing incremental and reversible changes. 

This approach enables quick adaptation to market demands and significantly reduces production errors.

Simplifying complexity

One of Nubank’s technical approaches is immutability. We use tools that minimize state changes, such as the immutable database Datomic, Kafka for asynchronous communication, and functional programming with Clojure. 

This drastically reduces system complexity, making maintenance easier and simplifying the identification and correction of problems, as well as improving service consistency and predictability.

Scaling together

At Nubank, standardization is considered a strategic advantage that accelerates development and fosters collaboration. By setting clear standards for languages, frameworks, databases, and processes, we reduce cognitive load on our teams. 

This allows our engineers to focus on solving complex challenges while continuously sharing and improving technical best practices.

Cloud-first: agility at scale

From the beginning, we’ve adopted a cloud-first approach, leveraging infrastructure provided by specialized partners. This strategy allows us to scale quickly, focusing on developing features that directly add value for our customers without managing infrastructure complexity. 

Although specific cases require on-premises solutions due to technical or regulatory needs, the predominant use of cloud ensures essential flexibility and scalability for rapid growth.

Democratizing access to data

At Nubank, data is crucial for strategic and operational decision-making. Our ETL infrastructure processes around 7 terabytes daily, providing internal teams with secure and rigorously controlled access to information. 

We have evolved to a Data Mesh model, segmenting datasets into domains with standardized interfaces. This approach promotes autonomy, simplifies data access, and accelerates the development of data-driven solutions.

Security and privacy come first

Security and privacy are top priorities at Nubank. Customer trust directly depends on our ability to protect information and maintain system stability. We employ advanced authentication techniques, rigorous access controls, continuous monitoring, and chaos engineering to anticipate and mitigate failures. 

This ensures high service availability, protection against threats, and compliance with strict financial sector regulations.

Artificial intelligence and responsible innovation

We apply artificial intelligence strategically in areas such as fraud detection, enhancing security, and continually improving customer experience. Additionally, we actively explore generative AI and assistants like Devin to optimize internal processes and increase technical productivity. We view AI as a powerful tool that, when applied responsibly and strategically, generates significant positive impacts both internally and externally.

These practices and approaches evolve as Nubank grows and faces new challenges. We remain committed to building technology that consistently delivers security, efficiency, and innovation for our customers.

If you want to know more about the technical backstage and real stories of Nubank’s engineering, check out the episode of the Hipster Ponto Tech podcast, with the participation of the CTO at Nubank, Vitor Olivier.

The post Inside Nubank’s engineering: Discover the technical backstage powering our innovation appeared first on Building Nubank.

Permalink

What is Java Development Kit (JDK)?

<p>The Java Development Kit (JDK) is a development environment for building Java applications and applets that can then run on any Java Virtual Machine (<a href="https://www.theserverside.com/definition/Java-virtual-machine-JVM">JVM</a>). The JDK includes a variety of development tools, libraries and utilities, including a debugger, disassembler, stub file generator and documentation generator. These tools perform a variety of tasks, including compiling <a href="https://www.techtarget.com/searchapparchitecture/definition/source-code">source code</a> into <a href="https://www.techtarget.com/whatis/definition/bytecode">bytecode</a>, packaging applications, spinning up JVMs and managing the runtime environment of Java applications.</p> <div class="ad-wrapper ad-embedded"> <div id="halfpage" class="ad ad-hp"> <script>GPT.display('halfpage')</script> </div> <div id="mu-1" class="ad ad-mu"> <script>GPT.display('mu-1')</script> </div> </div> <p>The JDK is a combination of tools, utilities and the Java Runtime Environment (<a href="https://www.theserverside.com/definition/Java-Runtime-Environment-JRE">JRE</a>). The tools within the JDK are used to develop Java programs, while the JRE runs<i> </i>those programs.</p> <div class="youtube-iframe-container"> <iframe id="ytplayer-0" src="https://www.youtube.com/embed/Cft45c-F2XI?autoplay=0&amp;modestbranding=1&amp;rel=0&amp;widget_referrer=null&amp;enablejsapi=1&amp;origin=https://www.theserverside.com" type="text/html" height="360" width="640" frameborder="0"></iframe> </div> <section class="section main-article-chapter" data-menu-title="JDK components, tools and utilities"> <h2 class="section-title"><i class="icon" data-icon="1"></i>JDK components, tools and utilities</h2> <p>The JDK provides a variety of features and tools that aid in the software development process. Some of these tools in this <a href="https://www.techtarget.com/whatis/definition/software-developers-kit-SDK">software development kit</a> are considered basic, including the following:</p> <ul class="default-list"> <li><b>Javac.</b> This utility is used to compile Java source code into Java bytecode class files. It provides multiple options to read Java <a href="https://www.techtarget.com/whatis/definition/class">class</a> and interface definitions and compile them into bytecode. Additionally, it can process annotations in Java source files and classes. For the compiler to run properly, the source and class files must have root names that identify the class.</li> <li><b>Javah. </b>This command is used to generate C header and source files from a Java class. These headers and files are needed by C programs to reference an object's instance variables from <a href="https://www.techtarget.com/searchapparchitecture/definition/native-code">native source code</a><b>.</b></li> <li><b>JAR.</b> This compression utility aggregates a multitude of files into a single Java Archive (<a href="https://www.theserverside.com/definition/JAR-file-Java-ARchive">JAR</a>) file. Based on ZIP and the ZLIB compression formats, JAR allows developers to package Java <a href="https://www.techtarget.com/whatis/definition/applet">applets</a> or applications into one archive. They can quickly download the components of the JAR file using a web browser in a single <a href="https://www.techtarget.com/whatis/definition/HTTP-Hypertext-Transfer-Protocol">HTTP</a> transaction. The author can sign the individual entries in a JAR file; this allows for the origin of those entries to be authenticated.</li> <li><b>Javadoc.</b> This utility is an application programming interface (<a href="https://www.techtarget.com/searchapparchitecture/definition/application-program-interface-API">API</a>) <a href="https://www.techtarget.com/searchsoftwarequality/definition/documentation">documentation</a> generator. It can analyze the declarations and comments in source files and produce HTML pages describing the classes, interfaces, constructors, methods and fields. Developers can use the Javadoc Doclet API to inspect the source-level structures of programs and libraries and to generate customized Javadoc output. By creating a Doclet program (using the Doclet API), they can generate any kind of text file output, including <a href="https://www.theserverside.com/definition/HTML-Hypertext-Markup-Language">HTML</a>, SGML, <a href="https://www.techtarget.com/whatis/definition/XML-Extensible-Markup-Language">XML</a>, <a href="https://www.techtarget.com/whatis/definition/Rich-Text-Format-RTF">RTF</a> and maker interchange format (MIF).</li> <li><b>Extcheck. </b>The Extcheck utility detects conflicts between a target JAR file and currently installed extension JAR files. It is useful to check whether the same or a more recent version of the extension is already installed.</li> <li><b>Javap.</b> Javap disassembles class files, and prints the package, protected and public fields, and methods of the classes passed to it. It provides numerous options to print help messages, print local variable tables, show all classes and members, and more.</li> <li><b>Jdeps.</b> This Java class dependency analyzer shows the package-level or class-level dependencies of Java class files and then generates those dependencies in DOT language.</li> <li><b>Jdb (Java Debugger).</b> Jdb is a command-line debugger for Java classes that finds and fixes bugs in Java platform programs (in local or remote JVMs).</li> </ul> <p>The JDK also includes numerous security tools, internationalization tools, Remote Method Invocation (<a href="https://www.theserverside.com/definition/Remote-Method-Invocation-RMI">RMI</a>) tools and tools for the following:</p> <ul class="default-list"> <li>Java deployment.</li> <li>Java Web Start.</li> <li>Java monitoring.</li> <li>Java troubleshooting.</li> <li>Java web services.</li> <li>Scripting.</li> </ul> <p>One of the many RMI tools in the JDK is called <i>rmic</i><b>.</b> This utility creates skeletons and stubs (both are class files) using the Java Remote Method Protocol (JRMP). Oracle recommends the use of dynamically generated JRMP stubs and skeletons because support for static generation of these entities has been deprecated.</p> <p>One example of a JDK<b> </b>security tool is <a href="https://www.theserverside.com/blog/Coffee-Talk-Java-News-Stories-and-Opinions/jarsigner-digitially-sign-JARs-Java-keytool-certificates"><i>jarsigner</i></a><b>. </b>Developers can use<b> </b>this command to both sign and verify JAR files. The command uses a <a href="https://www.techtarget.com/searchsecurity/definition/public-key">public key</a> and <a href="https://www.techtarget.com/searchsecurity/definition/private-key">private key</a>, plus certificate information from a keystore to generate <a href="https://www.techtarget.com/searchsecurity/definition/digital-signature">digital signatures</a> for the files. A JAR file is successfully verified if and only if two conditions are met: the signatures are valid and the files that were in the JAR file have not changed since the signatures were generated. Apart from jarsigner, other security tools are available in the JDK to do the following:</p> <ul class="default-list"> <li>Manage keystores and certificates.</li> <li>Manage policy files.</li> <li>Obtain <a href="https://www.techtarget.com/searchsecurity/definition/Kerberos">Kerberos</a> v5 tickets.</li> <li>List entries in credential cache and key tab.</li> <li>Manage entries in the key table.</li> </ul> <p>A popular Java deployment tool is <i>javapackager</i><b>. </b>This utility manages tasks related to packaging, compiling and signing Java and JavaFX applications for distribution. The JDK also provides <i>javaws</i>, a tool to launch Java Web Start (JWS), a useful software to download and run Java applications from the web, as well as <i>wsgen</i><b>, </b>a <a href="https://www.techtarget.com/searchapparchitecture/definition/Web-services">web services</a> tool that generates portable artifacts required by Java API for XML Web Services (JAX-WS).</p> <p>Developers can also pick from numerous Java monitoring and troubleshooting tools within the JDK, as well as tools to run scripts that interact with the Java platform. For example, the Java Mission Control (<a href="https://www.theserverside.com/definition/Java-Mission-Control">JMC</a>) tools are useful to monitor and manage Java applications with minimal performance overhead. Similarly, <i>jvisualvm</i> is a useful graphical tool to gather detailed information about Java applications running in a JVM. That said, many of the monitoring and troubleshooting tools in the JDK are experimental and might not be available in future JDK versions. Also, some of the troubleshooting tools are not currently (as of May 2025) available on Windows platforms.</p> </section> <section class="section main-article-chapter" data-menu-title="JDK vs. JVM"> <h2 class="section-title"><i class="icon" data-icon="1"></i>JDK vs. JVM</h2> <p>The JDK is needed to develop Java applications, while the JVM is needed to execute bytecode. When a Java application's source code (.java) is compiled by the Java compiler, it is compiled into bytecode. Bytecode is an intermediary between Java source code and low-level <a href="https://www.techtarget.com/whatis/definition/machine-code-machine-language">machine code</a>. The bytecode is contained within .class files, which have the same class names present in the .java file. All of this is possible because of the JVM.</p> <p>JVMs, which are part of the JRE, exist for operating systems (<a href="https://www.techtarget.com/whatis/definition/operating-system-OS">OSes</a>). In fact, different JVM implementations are required to interact with different OSes and the specific machine's underlying hardware. However, bytecode is platform independent. The ability of JVMs to facilitate on-the-fly conversion from Java bytecode to low-level computer instructions (machine code) is the key to making a Java application cross-platform and hardware-agnostic, or write once run anywhere (WORA). WORA means that Java code can be written on any machine with any OS and run it on a different machine (with a different OS) without making any modifications or adjustments to the code.</p> </section> <section class="section main-article-chapter" data-menu-title="JDK vs. JRE"> <h2 class="section-title"><i class="icon" data-icon="1"></i>JDK vs. JRE</h2> <p>The Java Development Kit also includes the Java Runtime Environment (JRE). It consists of the following:</p> <ul class="default-list"> <li>JVM.</li> <li>Java platform core classes.</li> <li>Supporting Java platform libraries.</li> <li>Integration libraries.</li> <li>Additional libraries, e.g., internationalization libraries, IDL libraries and Java packages for versioning and management.</li> <li>Java plug-in software.</li> <li>Java Naming and Directory Interface (JNDI) directory service.</li> </ul> <p>The main difference between the JDK and JRE is that the tools within the JDK are used for developing Java programs, while the JRE is only for running those programs. The JDK contains a compiler, a <a href="https://www.techtarget.com/searchsoftwarequality/definition/debugging">debugger</a> and other development tools. These resources are needed to write and compile code and then pass the bytecode to the JRE. In contrast, the JRE contains the resources needed to run the bytecode on any device and platform, including class libraries, supporting files and the JVM.</p> <p>The JRE is where all Java source code is executed and where the various software plugins, JAR files and support libraries (required for the source code to run) are integrated. Its main job is to facilitate communication between the Java program and the underlying OS so that the program can run on that OS without requiring developers to modify the code for each OS the program is meant to run on.</p> <p>The JRE uses the JDK and its libraries (which are nothing but additional built-in code) to create a JVM <a href="https://www.techtarget.com/whatis/definition/instance">instance</a> (local copy) that will run the Java program. While JVMs can exist for different OSes, the JRE creates a single OS-agnostic copy of the Java code. This is what makes it possible for developers to write Java code only once and then run it from anywhere.</p> <p>In addition to facilitating <a href="https://www.theserverside.com/answer/Why-is-Java-platform-independent">platform independence</a> for Java apps, the JRE allows the programs to access all required system resources, such as memory, program files and dependencies, so that they can run. Within the JDK, the JRE is separated from the regular JRE, which is why it is also known as the <i>private runtime</i>.</p> <p>Some Java applications can be launched via some browsers using the JRE. For example, to run Java Web Start applications from a supported web browser, only the JRE is needed.</p> </section> <section class="section main-article-chapter" data-menu-title="JDK libraries"> <h2 class="section-title"><i class="icon" data-icon="1"></i>JDK libraries</h2> <p>The JDK does not include support for components such as servlets, Java Server Pages (JSP) and Enterprise JavaBeans (EJB). These components are part of the Java EE platform, which is built on top of Java SE. Similarly, the JDK does not have inherent support for microdevice development with Java ME, although Java ME does build on top of and utilize all of the features and tools that are made available through the JDK. The JDK represents a core set of libraries, utilities and tools that Java developers can use. Subsequent APIs, platforms and <a href="https://www.techtarget.com/whatis/definition/framework">frameworks</a> that are built with Java all build upon the facilities provided by the JDK.</p> </section> <section class="section main-article-chapter" data-menu-title="JDK versions and the Java platform"> <h2 class="section-title"><i class="icon" data-icon="1"></i>JDK versions and the Java platform</h2> <p>The JDK is a software package that contains a variety of tools and utilities to develop, package, monitor and deploy applications for the Java Platform, Standard Edition (Java SE).</p> <p>Java SE is meant for developing and deploying Java applications on desktops and servers. As of May 2025, the latest edition of Java SE is Java SE 24.0.1. Multiple JDK versions are available for Java SE:</p> <ul class="default-list"> <li>JDK 24.</li> <li>JDK 21.</li> <li>JDK 17.</li> <li>JDK 11.</li> <li>JDK 8.</li> </ul> <p>Some older versions (JDK 23, JDK 16, JDK 15, JDK 7, etc.) are also available, but they are currently in sustaining support, meaning Oracle provides maintenance only for as long as users use the software.</p> <p>Within Java SE, Oracle provides the JDK Mission Control (JMC), a set of advanced tools for managing, monitoring and troubleshooting Java applications.</p> </section> <section class="section main-article-chapter" data-menu-title="JDK and other (non-Java) languages"> <h2 class="section-title"><i class="icon" data-icon="1"></i>JDK and other (non-Java) languages</h2> <p>In the past, the JDK was specifically meant for the Java programming language. From Java 7 onward, special constructs were introduced that made it easier for peripheral programming languages, such as Groovy, Clojure, Scala or Kotlin, to run on a JVM, compile source code into Java bytecode and then execute it using the JRE.</p> <p>These languages provide various features that overcome some limitations of Java. For example, <a href="https://www.techtarget.com/searchapparchitecture/feature/Exploring-the-versatility-of-Groovy-programming">Groovy</a> provides strong <a href="https://www.techtarget.com/whatis/definition/scripting-language">scripting</a> features and the scripts can run on any platform that supports JVMs. <a href="https://www.techtarget.com/searchapparchitecture/tip/Scala-Lightweight-functional-programming-for-Java">Scala</a> and <a href="https://www.techtarget.com/searchapparchitecture/tip/Explore-Clojure-programming-language-and-its-multithread-style">Clojure</a> solve various scalability issues that Java program might run into in high-traffic environments. And <a href="https://www.techtarget.com/whatis/definition/Kotlin">Kotlin</a> greatly simplifies <a href="https://www.techtarget.com/searchmobilecomputing/definition/Android-OS">Android</a> application development, thanks to its concise syntax and support for functional programming. All these languages take advantage of the standard libraries and other features that are built into the Java platform.</p> <p>Once an application is developed using the JDK on one system, it can be used on another system, thanks to Java class files. Because these files are portable to any standard JVM, there's no need to change or recompile the code to use the application on multiple JVMs.</p> </section> <section class="section main-article-chapter" data-menu-title="How to get started with JDK"> <h2 class="section-title"><i class="icon" data-icon="1"></i>How to get started with JDK</h2> <p>The Java Development Kit, with all the tools needed to compile and run Java applications, is distributed by the <a href="https://www.techtarget.com/whatis/definition/open-source">open source</a> <a href="https://www.theserverside.com/definition/OpenJDK">OpenJDK</a> project. Developers can download and install JDK 24 for Java SE from the <a target="_blank" href="https://openjdk.org/" rel="noopener">OpenJDK site</a>. Since Mar. 18, 2025, this version of the JDK is in general availability, meaning it is ready for production use.</p> <p>Developers can <a target="_blank" href="https://jdk.java.net/24/" rel="noopener">download</a> free, GPL-licensed, production-ready OpenJDK JDK 24 binaries for Linux, macOS and Windows. In addition, Oracle's commercially licensed JDK 24 binaries and commercial builds of JDK 24.0.1 from Oracle, under a non-open source license can be downloaded directly from Oracle.</p> <p>Early access builds for OpenJDK JDK 25 are also available. These open source builds are provided under the GNU General Public License, version 2, and can be downloaded from <a target="_blank" href="https://jdk.java.net/25/" rel="noopener">here</a>. It's important to note that EA builds might be removed at any time. Also, they might include some security vulnerabilities, and Oracle does not provide support for them.</p> <figure class="main-article-image full-col" data-img-fullsize="https://www.techtarget.com/rms/onlineimages/openjdk_java_development_projects-f.png"> <img data-src="https://www.techtarget.com/rms/onlineimages/openjdk_java_development_projects-f_mobile.png" class="lazy" data-srcset="https://www.techtarget.com/rms/onlineimages/openjdk_java_development_projects-f_mobile.png 960w,https://www.techtarget.com/rms/onlineimages/openjdk_java_development_projects-f.png 1280w" alt="OpenJDK Java development projects diagram." height="224" width="560"> <figcaption> <i class="icon pictures" data-icon="z"></i>There are a number of OpenJDK projects currently available that address enhancements to the open source product. </figcaption> <div class="main-article-image-enlarge"> <i class="icon" data-icon="w"></i> </div> </figure> <p><a href="https://www.theserverside.com/answer/Why-is-Java-platform-independent"><i>Learn more about Java</i></a><i> and why it was designed to be platform independent. Explore </i><a href="https://www.theserverside.com/blog/Coffee-Talk-Java-News-Stories-and-Opinions/7-Benefits-Java-advantages-dynamic-robust-performance-security-objects-simple"><i>seven benefits of Java</i></a><i> and the </i><a href="https://www.theserverside.com/feature/Top-Java-programming-tools-used-in-application-development"><i>top Java programming tools used in application development</i></a><i>.</i></p> </section>

Permalink

Clojure - Fascinated, Disappointed, Astonished

I’ve had a pleasure to work with Piotrek Jagielski for about two weeks on Clojure project. I’ve learned a lot, but there is still a lot to know about Clojure for me. In this post I’ll write what fascinated, disappointed and astonished me about this programming language.

Clojure & InteliJ IDEA tips

Before you start your journey with Clojure:

  • Use Cursive plugin for InteliJ IDEA. In ‘14 Edition it was not in the standard plug-in repository (remove La Clojure plug-in and Cursive repository manually). For IDEA ‘15 it is in repository.
  • Colored brackets help me a lot. You can find configuration for colored brackets on Misophistful Github.

Fascinated

Syntax

For many people Clojure brackets are reasons to laugh. Jokes like that were funny at first: “How many brackets did you write today?”
I have to admit, that at the beginning using brackets was not easy for me. Once I’ve realized that the brackets are just on the other side of the function name, everything was simple and I could code very fast.
After few days I’ve realized that this brackets structure forces me to think more about the structure of the code. As a result the code is refactored and divided into small functions.
Clojure forces you to use good programming habits.

Data structure is your code

Clojure is homoiconic, which means that the Clojure programs are represented by Clojure data structures. This means that when you are reading a Clojure code you see lists, maps, vectors. How cool is that! You only have to know few things and you can code.

Do not restart your JVM

Because Clojure code is represented as data structures, you can pass data structure (program) to running JVM. Furthermore, compiling your code to bytecode (classes, jars) may be eliminated.

For example, when you want to test something you are not obligated to start new JVM with tests. Instead you can just synchronize your working file with running REPL and run the function.

Traditional way of working with JVM is obsolete.

REPL code synchronization

In the picture above, on the left you can see an editor, on the right there is running REPL.

The same way you can run tests, which is extremely fast. In our project we had ~80 tests. Executing them all took about one second.

Easy to read

Simplicity is the ultimate sophistication.

Leonardo da Vinci

After getting familiar with this language, it was really easy to read code. Of course, I was not aware of everything what was happening under the hood, but consistency of the written program evoked sense of control.

Disapointed

Data structure is your code

When data structure is your code, you need to have some additional operators to write effective programs. You should get to know operators like ‘->>’, ‘->’, ‘let’, ‘letfn’, ‘do’, ‘if’, ‘recur’ …

Even if there is a good documentation (e.g. Let), you have to spend some time on analyzing it, and trying out examples.

As the time goes on, new operators will be developed. But it may lead to multiple Clojure dialects. I can imagine teams (in the same company) using different sets of operators, dealing with the same problems in different ways. It is not good to have too many tools. Nevertheless, this is just my suspicion.

Know what you do

I’ve written a function that rounds numbers. Despite the fact that this function was simple, I wanted to write test, because I was not sure if I had used the API in correct way. There is the test function below:

Rounding test
1
2
(let [result (fixture/round 8.211M)]
(is (= 8.21M result))))

Unfortunately, tests were not passing. This is the only message that I received:

Rounding test
1
2
3
4
:error-while-loading pl.package.calc-test
NullPointerException [trace missing]
(pst)
NullPointerException

Great. There is nothing better than a good exception error. I’ve spent a lot of time trying to solve this, and solution was extremely simple.
My function was defined with defn-, instead of defn. defn- means private scope and test code, could not access testing function.

Do not trust assertions

Assertions can be misleading. When tested code does not work properly and returns wrong results, error messages are like this:

Assertions problems
1
2
3
4
ERROR in math-test/math-operation-test (RT.java:528)
should round using half up
expected: (= 8.31M result)
actual: java.lang.IllegalArgumentException: Don't know how to create ISeq from: java.math.BigDecimal

I hadn’t got time to investigate it, but in my opinion it should work out of the box.

Summary

It is a matter of time, when tools will be better. Those problems will slow you down, and they are not nice to work with.

Astonished

The Clojure concurrency impressed me. Until then, I knew only standard Java synchronization model and Scala actors model. I’ve never though that concurrency problems can be solved in a different way. I will explain Clojure approach to concurrency, in details.

Normal variables

The closest Clojure’s analogy to the variables are vars, which can be created by def.

Vars
1
2
3
4
(defn a01 []
(def amount 10)
(def amount 100)
(println amount))

We also have local variables which are only in let scope. If we re-define scope value of amount, the change will take place only in local context.

Lets
1
2
3
4
5
(defn a02 []
(let [amount 10]
(let [amount 100]
(println amount))
(println amount)))

The following will print:

Lets output
1
2
100
10

Nothing unusual. We might expect this behavior.

Concurrent access variables

The whole idea of concurrent access variables can be written in one sentence. Refs ensures safe shared access to variables via STM, where mutation can only occur via transaction.
Let me explain it step by step.

What is Refs?

Refs (reference) is a special type to hold references to your objects. As you can expect, basic things you can do with it is storing and reading values.

What is STM?

STM stands for Software Transactional Memory. STM is an alternative to lock-based synchronization system. If you like theory, please continue with Wikipedia, otherwise continue reading to see examples.

Using Refs

Refs reads
1
2
3
(defn a03 []
(def amount (ref 10))
(println @amount))

In the second line, we are creating reference. Name of this reference is amount. Current value is 10.
In the third line, we are reading value of the reference called amount. Printed result is 10.

Modifying Refs without transaction

Refs writes without transaction
1
2
3
4
(defn a04 []
(def amount (ref 10))
(ref-set amount 100)
(println @amount))

Using ref-set command, we modify the value of the reference amount to the value 100. But it won’t work. Instead of that we caught exception:

Exception
1
IllegalStateException No transaction running  clojure.lang.LockingTransaction.getEx (LockingTransaction.java:208)

Using transaction

Refs writes with transaction
1
2
3
4
(defn a05 []
(def amount (ref 10))
(dosync (ref-set amount 100))
(println @amount))

To modify the code we have to use dosync operation. By using it, we create transaction and only then the referenced value will be changed.

Complete example

The aim of the previous examples was to get familiar with the new operators and basic behavior.
Below, I’ve prepared an example to illustrate bolts and nuts of STM, transactions and rollbacks.

The problem

Imagine we have two references for holding data:

  • source-vector containing three elements: “A”, “B” and “C”.
  • empty destination-vector.

Our goal is to copy the whole source vector to destination vector. Unfortunately, we can only use function which can copy elements one by one - copy-vector.

Moreover, we have three threads that will do the copy. Threads are started by the future function.

Keep in mind that this is probably not the best way to copy vectors, but it illustrates how STM works.

Refs writes with transaction
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
(defn copy-vector [source destination]
(dosync
(let [head (take 1 @source)
tail (drop 1 @source)
conj (concat head @destination)]
(do
(println "Trying to write destination ... ")
(ref-set destination conj)
(println "Trying to write source ... ")
(ref-set source tail)
(println "Sucessful write " @destination)))))

(defn a06 []
(let [source-vector (ref ["A" "B" "C"]) destination-vector (ref [])]
(do
(future (copy-vector source-vector destination-vector))
(future (copy-vector source-vector destination-vector))
(future (copy-vector source-vector destination-vector))
(Thread/sleep 500)
@destination-vector
)))
Execution

Below is the output of this function. We can clearly see that the result is correct. Destination vector has three elements. Between Sucessful write messages we can see that there are a lot of messages starting with Trying to write.
What does it mean? The rollback and retry occurred.

Printed messageslang:Clojure
1
2
3
4
5
6
7
8
9
10
11
12
13
14
(l/a06)
Trying to write destination ...
Trying to write source ...
Trying to write destination ...
Trying to write destination ...
Sucessful write (A)
Trying to write destination ...
Trying to write destination ...
Trying to write source ...
Sucessful write (B A)
Trying to write destination ...
Trying to write source ...
Sucessful write (C B A)
=> ("C" "B" "A")
Rollback

Each thread started to copy this vector, but only one succeed. The remaining two threads had to rollback work and try again one more time.

Software Transaction Memory

When Thread A (red one) wants to write variable, it notices that the value has been changed by someone else - conflict occurs. As a result, it stops the current work and tries again whole section of dosync. It will try until every write operation succeed.

Pros and cons of STM

Cons:

  • Everything that happens in dosync section has to be pure, without side effects. For example you can not send email to someone, because you might send 10 emails instead of one.
  • From performance perspective, it makes sense when you are reading a lot from Refs, but rarely writing it.

Pros:

  • Written code is easy to read, understand, modify.
  • Refs and transactions are part of standard library, so you can use it in Vanilla Java. Take a look at this blog post for more examples.

Summary

There is a lot that Java developers can gain from Clojure. They can learn how to approach the code and how to express the problem in the code. Also they can discover tools like STM.

If you like to develop your skills, you should definitely experiment with Clojure.

Permalink

Clojure - How to replace current time creation?

Recently, I’ve begun my adventure with Clojure programming language. As a result I’ve decided to share gathered knowledge and my opinion, in this and in few upcoming posts.

The problem

I had to implement algorithm that depends on the current date. Core information for this algorithm is number of days between current date and some date in the future, expressed in days.
Therefore, there is a call somewhere in the code:

Current time with Java 8
1
(. java.time.LocalDate now)

For the tests to be stable, I had to make sure that this call always return the same day.

Approach 1

I’ve decided to extract creation of the current date functionality to the function:

now-date function will return current time
1
(defn now-date [] (. java.time.LocalDate now))

During tests I’ve declared different function:

fixed-date function will return current time
1
(defn fixed-date [] (. java.time.LocalDate of 2015 01 28))

Passing function that creates a current date, solved the problem. It worked great, but it had the following disadvantages:

  • Passing to algorithm function that creates current time.
  • Using java notation (with dot) in Clojure.

Approach 2

Having a function, that returns a current time, I’ve decided to find a way of overwriting its definition in tests. I’ve found out that there is operation called with-redefs-fn, which allows re-defining the function temporarily in the local context. Having defined fixed-date function, block of code looks like this:

Replacing now-date with fixed-date
1
2
3
4
5
6
7
8
9
(deftest my-test
(with-redefs-fn { #'fixture/now-date fixed-date }
#(do
(testing "should work method a"
(let [result (fixture/do-stuff 30)]
(is (.equals 8.22M result))
))
;More tests
)))

fixture/now-date is a reference to function that I wanted to replace. This time I was amazed by language possibilities. But there was one more problem to solve. I did not want to use java notation.

Approach 3

There is a library called Clj-time. It wraps Joda Time library and makes Clojure code more friendly. I wanted to hold on Java 8 library, but I did not see any alternatives.

So I replaced (. java.time.LocalDate now) to (t/now) and also creation of fixed dates, and then I came up with an idea.

Approach 4

Maybe should I replace the Clj-time itself? My production code will be simpler and the test code will be simpler too!

Replacing t/now with fixed date
1
2
3
4
5
6
7
8
9
(deftest my-test
(with-redefs-fn { #'t/now #(t/date-time 2015 11 19) }
#(do
(testing "should work method a"
(let [result (fixture/do-stuff 30 1000)]
(is (.equals 8.22M result))
))
;More tests
)))

This is my final solution. I am still impressed how easily it that can be done.

I use Clojure for a week. If you have any other ideas how to solve this problem comment, let me know.

Photo credit: Vintage alarm clock, Thumbnail

Permalink

Collections as grids with borders

A key idea of Lisp is that all syntax is a list

()
()

Rich innovated by introducing collection literals

[]
[]
{}
{}
#{}
#{}

Data is well represented by these collections

(def data
  {:numbers [2 9 -1]
   :sets    #{"hello" "world"}
   :mix     [1 "hello world" (kind/hiccup [:div "hello" [:strong "world"] "hiccup"])]
   :nest    {:markdown             (kind/md "hello **markdown**")
             :number               9999
             :nothing-is-something #{nil #{} 0}}
   :dataset (tc/dataset {:x (range 3)
                         :y [:A :B :C]})})

In a notebook, we like to visualize values. Often those visualizations summarize the data in some way.

  1. The collection represents something
  2. The collection organizes some things
  3. We are interested in the collection itself

In website design, everything is a grid. Grids organize and align content, achieving visual hierarchy. Can we apply this idea to data structures? Let’s start with a vector:

(def v [1 2 3 4 5 6 7 8 9])
v
[1 2 3 4 5 6 7 8 9]

Pretty printing the vector is a fine choice, but what if we made it a grid?

(defn content [x columns]
  (kind/hiccup
    (into [:div {:style {:display               :grid
                         :grid-template-columns (str "repeat(" columns ", auto)")
                         :align-items           :center
                         :justify-content       :center
                         :text-align            :center
                         :padding               "10px"}}]
          x)))
(defn vis [x opt]
  (kind/hiccup
    [:div {:style {:display               "grid"
                   :grid-template-columns "auto auto auto"
                   :gap                   "0.25em"
                   :align-items           "center"
                   :justify-content       "center"}}
     [:div {:style {:padding     "0 0.25em"
                    :font-weight :bold
                    :align-self  (when opt "start")
                    :align-items (when-not opt "stretch")}}
      "["]
     (content x 1)
     [:div {:style {:padding     "0.25em"
                    :font-weight :bold
                    :align-self  (when opt "end")
                    :align-items (when-not opt "stretch")}}
      "]"]]))

In some situations this feels better, especially when nesting visualizations. But it does raise the question of where the braces belong.

(vis v false)
[
123456789
]
(vis v true)
[
123456789
]

Another idea is to use borders to indicate collections.

(defn vis2 [x]
  (kind/hiccup
    [:div {:style {:border-left  "2px solid blue"
                   :border-right "2px solid blue"}}
     (content x 1)]))
(vis2 v)
123456789

Borders can be stylized with images

(defn svg [& body]
  (kind/hiccup
    (into [:svg {:xmlns        "http://www.w3.org/2000/svg"
                 :width        100
                 :height       100
                 :viewBox      [-100 -100 200 200]
                 :stroke       :currentColor
                 :fill         :none
                 :stroke-width 4}
           body])))
(defn border-style [x]
  {:border              "30px solid transparent"
   :border-image-slice  "30"
   :border-image-source (str "url('data:image/svg+xml;utf8,"
                             (hiccup/html {:mode :xml} x)
                             "')")
   :border-image-repeat "round"})

We can create a curly brace shape to use as the border

(def curly-brace-path
  "M -10 -40 Q -20 -40, -20 -10, -20 0, -30 0 -20 0, -20 10, -20 40, -10 40")
(def map-svg
  (svg (for [r [0 90 180 270]]
         [:path {:transform (str "rotate(" r ") translate(-30) ")
                 :d         curly-brace-path}])))
map-svg
(def map-style
  (border-style map-svg))
(defn vis3 [style columns x]
  (kind/hiccup [:div {:style style} (content x columns)]))

We now have a style that can be used to indicate something is a map

(vis3 map-style 2 (vec (repeat 10 [:div "hahaha"])))
');">
hahaha
hahaha
hahaha
hahaha
hahaha
hahaha
hahaha
hahaha
hahaha
hahaha

Usually we’d put this in a CSS rule, I’m just illustrating what it would look like.

(def set-svg
  (svg
    [:line {:x1 -80 :y1 -20 :x2 -80 :y2 20}]
    [:line {:x1 -70 :y1 -20 :x2 -70 :y2 20}]
    [:line {:x1 -90 :y1 10 :x2 -60 :y2 10}]
    [:line {:x1 -90 :y1 -10 :x2 -60 :y2 -10}]
    (for [r [0 90 180 270]]
      [:path {:transform (str "rotate(" r ") translate(-30) ")
              :d         curly-brace-path}])))

A set could have a hash on the left

set-svg
(def set-style (border-style set-svg))
(vis3 set-style 1 (vec (repeat 10 [:div "hahaha"])))
');">
hahaha
hahaha
hahaha
hahaha
hahaha
hahaha
hahaha
hahaha
hahaha
hahaha

Sets could instead have a Venn inspired border

(def set2-svg
  (svg
    (for [r [0 90 180 270]]
      [:g {:transform (str "rotate(" r ")")}
       [:ellipse {:cx -60 :cy -15 :rx 12 :ry 25}]
       [:ellipse {:cx -60 :cy 15 :rx 12 :ry 25}]])))
set2-svg
(def set2-style (border-style set2-svg))
(vis3 set2-style 1 (vec (repeat 10 [:div "hahaha"])))
');">
hahaha
hahaha
hahaha
hahaha
hahaha
hahaha
hahaha
hahaha
hahaha
hahaha

But I think it’s better to use the more obvious # hash.

(def sequence-svg
  (svg
    (for [r [0 90 180 270]]
      [:g {:transform (str "rotate(" r ")")}
       [:circle {:r 75}]])))
sequence-svg

Parenthesis style border for sequences

(def sequence-style (border-style sequence-svg))
(vis3 sequence-style 1 (vec (repeat 10 [:div "hahaha"])))
');">
hahaha
hahaha
hahaha
hahaha
hahaha
hahaha
hahaha
hahaha
hahaha
hahaha
(def vector-svg
  (svg
    (for [r [0 90 180 270]]
      [:g {:transform (str "rotate(" r ")")}
       [:path {:d "M -65 -65 v 10 "}]
       [:path {:d "M -65 65 v -10 "}]
       [:path {:d "M -65 -30 H -75 V 30 H -65"}]])))
vector-svg

And rectangular brace style border for vectors

(def vector-style (border-style vector-svg))
(vis3 vector-style 1 (vec (repeat 10 [:div "hahaha"])))
');">
hahaha
hahaha
hahaha
hahaha
hahaha
hahaha
hahaha
hahaha
hahaha
hahaha

Nesting collections

(vis3 map-style 2
      [:some-key (vis3 vector-style 1 v)
       :some-other (vis3 set-style 1 (repeat 5 (vis3 sequence-style 1 ["ha" "ha" "ha"])))])
');">
some-key
');">
123456789
some-other
');">
');">
hahaha
');">
hahaha
');">
hahaha
');">
hahaha
');">
hahaha

I think this scheme achieves a visually stylized appearance. It maintains the expected hierarchy. And it is fairly obvious what collections are represented.

What do you think?

Permalink

Clojure Deref (July 20, 2025)

Welcome to the Clojure Deref! This is a weekly link/news roundup for the Clojure ecosystem (feed: RSS).

The Clojure/conj 2025 Call for Presentations is open now until July 27! We are seeking proposals for both 30 minute sessions and 10 minute lightning talks.

Upcoming Events

Libraries and Tools

New releases and tools this week:

Permalink

Writing your tests in EDN files

I've previously written about my latest approach to unit tests (which I have been informed is called "snapshot testing"):

[Y]ou define only the input data for your function, and then the expected return value is generated by calling your function. The expected value is saved to an EDN file and checked into source, at which point you ensure the expected value is, in fact, what you expect. Then going forward, the unit test simply checks that what the function returns still matches what’s in the EDN file. If it’s supposed to change, you regenerate the EDN file and inspect it before committing.

I still like that general approach, however my previous implementation of it ended up being a little too heavy-weight/too reliant on inversion of control. The test runner code had all these things built into it for dealing with fixtures, providing a seeded database value, and other concerns. Writing new tests ended up requiring a little too much cognitive overhead, and I reverted back to manual testing (via a mix of the REPL and the browser).

I have now simplified the approach so that writing tests is basically the same as running code in the REPL, and there's barely anything baked into the test runner itself that you have to remember. I put all my tests in EDN files like this (named with the pattern my_namespace_test.edn):

{:require
 [[com.yakread.model.recommend :refer :all]
  [com.yakread.lib.test :as t]
  [clojure.data.generators :as gen]],
 :tests
 [{:eval (weight 0), :result 1.0}
  _
  {:eval (weight 1), :result 0.9355069850316178}
  _
  {:eval (weight 5), :result 0.7165313105737893}
  ...]}

(weight is a simple function for the forgetting curve, which I'm using in Yakread's recommendation algorithm.)

I only write the :eval part of each test case. The test runner evaluates that code, adds in the :result part, and pprints it all back to the test file. Right now there isn't a concept of "passing" or "failing" tests. Instead, when the tests are right, you check them into git; if any test results change, you'll see it in the diff. Then you can decide whether to commit the new results (if the change is expected) or go fix the bug (if it wasn't). If I had CI tests for my personal projects, I'd probably add a flag to have the test runner report any test cases with changed results as failed.

In my lib.test namespace I've added a couple helper functions, such as a t/with-db function that populates an in-memory XTDB database value:

{:require
 [[com.yakread.work.digest :refer :all]
  [com.yakread.lib.test :as t]
  [clojure.data.generators :as gen]],
 :tests
 [{:eval
   (t/with-db
    [db
     [{:xt/id "user1", :user/email "user1@example.com"}
      {:xt/id "user2",
       :user/email "user2@example.com",
       :user/digest-last-sent #time/instant "2000-01-01T00:00:00Z"}]]
    (queue-send-digest
     {:biff/db db,
      :biff/now #time/instant "2000-01-01T16:00:01Z",
      :biff/queues
      {:work.digest/send-digest
       (java.util.concurrent.PriorityBlockingQueue. 11 (fn [a b]))}}
     :start)),
   :result
   {:biff.pipe/next
    ({:biff.pipe/current :biff.pipe/queue,
      :biff.pipe.queue/id :work.digest/send-digest,
      :biff.pipe.queue/job
      {:user/email "user1@example.com", :xt/id "user1"}})}}
  ...]}

(queue-send-digest returns a list of users who need to be sent an email digest of their RSS subscriptions and other content.)

I like this approach a lot more than the old one: you just write regular code, with test helper functions for seeded databases or whatever if you need them. It's been pretty convenient to write my "REPL" code in these _test.edn files and then have the results auto-update as I develop the function under test.

There are a couple other doodads: if the code in :eval throws an exception, the test runner writes the exception as data into the test case, albeit under an :ex key instead of under :results:

{:require
 [[com.yakread.model.recommend :refer :all]
  [com.yakread.lib.test :as t]
  [clojure.data.generators :as gen]],
 :tests
 [{:eval (weight 0)
   :ex
   {:cause "oh no",
    :data {:it's "sluggo"},
    :via
    [{:type clojure.lang.ExceptionInfo,
      :message "oh no",
      :data {:it's "sluggo"},
      :at
      [com.yakread.model.recommend$eval75461$weight__75462
       invoke
       "recommend.clj"
       60]}],
    :trace
    [[com.yakread.model.recommend$eval75461$weight__75462
      invoke
      "recommend.clj"
      60]
     [tmp418706$eval83727 invokeStatic "NO_SOURCE_FILE" 0]
     [tmp418706$eval83727 invoke "NO_SOURCE_FILE" -1]
     [clojure.lang.Compiler eval "Compiler.java" 7700]
     [clojure.lang.Compiler eval "Compiler.java" 7655]
     [clojure.core$eval invokeStatic "core.clj" 3232]
     [clojure.core$eval invoke "core.clj" 3228]]}}
  ...]}

The stack trace gets truncated so it only contains frames from your :eval code (mostly—I could truncate it a little more).

I also capture any tap>'d values and insert those into the test case, whether or not there was an exception. It's handy for inspecting intermediate values:

:tests
[{:eval (weight 1),
  :result 0.9355069850316178,
  :tapped ["hello there" "exponent: -1/15"]}
  ...

And that's it. If you want to try this out, you can copy run-examples! (the test runner function) into your own project. It searches your classpath for any files ending in _test.edn and runs the tests therein. I call it from a file watcher (Biff's on-save function) so your test results get updated whenever you save any file in the project.

Permalink

My programming environment journey

No one actually cares about my programming environment journey, but I’ve often been asked to share it, perhaps for the sake of social media algorithms. I post it here, so later, I can copy and paste this conveniently.

My first computer, in the sense that I, not someone else, made the decision to buy it, ran Debian in 2002. It was a used Compaq desktop with a Pentium II processor, which I bought from Zeer Rangsit, a used computer market that may be the most famous in Thailand these days. When I got it home, I installed Debian right away. Before I bought my computer, I had used MBasic, mainly MS-DOS, Windows 3.1 (though rarely), and Solaris (remotely). For experimentation, I used Xenix, AIX, and one on DEC PDP-11 that I forgot.

Since I started with MBasic, that was my first programming environment. I learned Logo at a summer camp, so that became my second. Later, my father bought me a copy of Turbo Basic, and at school, I switched to Turbo Pascal.

After moving to GNU/Linux, I used more editors instead IDEs. From 1995 to 2010, my editors were pico, nvi, vim, TextMate, and Emacs paired with GCC (mostly C, not C++), PHP, Perl, Ruby, Python, JavaScript, and SQL. I also used VisualAge to learn Java in the 90s. I tried Haskell, OCaml, Objective C, Lua, Julia, and Scala too, but it was strictly for learning only.

After 2010, I used IntelliJ IDEA and Eclipse for Java and Kotlin. For Rust (instead of C), I used Emacs and Visual Studio Code. I explored Racket for learning purposes, then later started coding seriously in Clojure and Common Lisp. I tried using Vim 9.x and Neovim too, they were great, but not quite my cup of tea.

In 2025, a few days ago, I learned Smalltalk with Pharo to deepen my understanding of OOP and exploratory programming.

Permalink

Annually-Funded Developers' Update: May/June 2025

Hello Fellow Clojurists! This is the third report from the 5 developers receiving Annual Funding in 2025.

Dragan Duric: Apple M Engine Neanderthal, MKL, OpenBlas
Eric Dallo: ECA, metrepl, clojure-lsp, clojure-lsp-intellij
Michiel Borkent: clj-kondo, babashka, SCI, quickblog, edamame, and more…
Oleksandr Yakushev: CIDER
Peter Taoussanis: Trove, Sente, Tufte

Dragan Duric

2025 Annual Funding Report 3. Published July 4, 2025.

My goal with this funding in 2025 is to support Apple silicon (M cpus) in Neanderthal (and other Uncomplicate libraries where that makes sense and where it’s possible).

In May and June, I completed Neanderthal’s Apple Silicon engine, updated MKL and OpenBLAS engines to be compatible with the introduced improvements to the overall code, fixed and polished many incompatibilities between the engines, and bugs that were introduced or discovered during the process, and updated all technologies newer versions. One huge improvement that the users will notice is AOT version of Neanderthal that is loading engines quickly.

I also reorganized Deep Diamond as separate technology-specific projects (so the users can configure their own combination appropriate to their systems) and an AOT project. I started working on the CPU engine for tensors and neural networks. It’s an uphill battle, since Apple’s API are:

  • not very nice compared to Intel and Nvidia (which are atrocities themselves compared to Clojurians' standards :))
  • poorly documented
  • very inconsistent -
  • contain several generations of deprecations and new ways of doing things
  • buggy
  • (most annoyingly) there are literally no code examples anywhere on the internet, so I have to discover everything by poking inside the REPL (I can’t imagine how people do that without Clojure/REPL :) So, I made lots of progress, but not nearly enough for a stable engine. It will take time.

As I also made some contributions to JavaCPP, I had to wait for it to release new version for the proper release of Neanderthal. They finally did it on 1st July, so I released Neanderthal as soon as I could (not in June, but day or two later :)

I also made assorted improvements and bugfixes to related Uncomplicate projects, all of them under the hood, but some of them very useful!

Here’s what I’ve proposed when applying for the CT grant.

I proposed to Implement an Apple M engine for Neanderthal. This involves:

  • buying an Apple M2/3 Mac (the cheapest M3 in Serbia is almost 3000 USD (with VAT).
  • learning enough macOS tools (Xcode was terrible back in the days) to be able to do anything.
  • exploring JavaCPP support for ARM and macOS.
  • exploring relevant libraries (OpenBLAS may even work through JavaCPP).
  • exploring Apple Accelerate.
  • learning enough JavaCPP tooling to be able to see whether it is realistic that I build Accelerate wrapper (and if I can’t, at least to know how much I don’t know).
  • I forgot even little C/C++ that I did know back in the day. This may also give me some headaches, as I’ll have to quickly pick up whatever is needed.
  • writing articles about relevant topics so Clojurians can pick this functionality as it arrives.

Projects directly involved: https://github.com/uncomplicate/neanderthal
https://github.com/uncomplicate/deep-diamond
https://github.com/uncomplicate/clojure-cpp
https://github.com/uncomplicate/clojurecuda

Looking at the funding proposal, I can say that I’m very satisfied that all the features that I promised to build are nicely progressing.

As the Deep Diamond Tensor stuff is brewing, I might start looking into Apple Metal and low-level GPU computing, so I can hope to also provide GPU computing on Apple silicon at least for Neanderthal, if not for Deep Diamond.

All in all, I feel optimistic about how this project progresses!


Eric Dallo

2025 Annual Funding Report 3. Published June 30, 2025.

In these last 2 months I could work on 2 main things, multiple improvements for clojure-lsp with fixes and new features and a exciting new project called ECA.

eca

ECA (Editor Code Assistant) is a OpenSource, free, standardized server written in Clojure to make any editor have AI features like Cursor, Continue, Claude and others. The idea is simple, it has a protocol heavily inspired by LSP, and editors can communicate with the eca server which will have the logic to communicate with multiple LLMs and provide AI features like chat, edit, MCPs, completion etc.

The project is in alpha state and there are so many things to do, but I hope community will help make this project a success!

Give a star to the project 🌟 and for more details check the roadmap

eca-emacs

clojure-lsp

There were so many improvements for clojure-lsp regarding performance, fixes, and new features. The most notable new feature is the support for custom linters defined by the user, it follows a similar logic of clj-kondo hooks, but with improved support for clojure-lsp in-memory data access, giving power for linters know about the project and not only a single file, with test support for linters. Check the missing-unit-test custom linter:
custom-linters

2025.05.27-13.56.57 - 2025.06.13-20.45.44

  • General

    • Consider .lsp/config.edn as part of project code, removing false positives of unused-public-var linter.

    • Consider full qualified symbols in edn files when checking for var references.

    • Improve clojure-lsp linter capabilities, migrating unused-public-var and different-aliases linters to be built-in linters. #2050

      • Migrate from clj-kondo custom-lint-fn but considering kondo settings to avoid breaking changes.
      • Considerably improve performance of unused-public-var and different-aliases linters.
    • Bump rewrite-clj to 1.2.50.

    • New feature: Add support for custom project linters. #2043 #2058

    • Publish to clojars com.github.clojure-lsp/clojure-lsp-test-helper to be able to test created custom linters.

    • Bump clj-kondo to 2025.04.08-20250526.195207-12.

    • Small performance improvements across clojure-lsp, especially on places with comparassions inside big loops.

    • Bump clj-depend to 0.11.1.

    • Provide analysis of unresolved namespaces, making features like definition, hover, references work.

    • Fix .lsp/config.edn file not found diagnostic when it doesn’t exist on project.

    • Fix custom linters source-paths analysis to consider all files in a source-path.

    • Fix crash on files with empty ignore comments.

    • Bump clj-kondo to 2025.06.05.

    • Fix analysis consistency for internal files.

    • Fix custom-linters not working for cases using clojure-lsp.custom-linters-api/find-nodes.

    • Improve clojure-lsp.custom-linters-api/dir-uris->file-uris to consider file-uris as well on input.

  • Editor

    • Add support for LSP feature textDocument/selectionRange. #1961
    • Fix outdated analysis for watched created/deleted files (git branch switchs for example). #2046
  • API/CLI

    • Replace tools.cli with babashka.cli. #2036

clojure-lsp-intellij

3.5.0

  • Add support for creating Clojure namespaces. #78

metrepl

0.3.1 - 0.4.1

  • Add compatibility with older opentelemetry versions.
  • Fix read config from classpath
  • Fix OpenTelemetry integration race condition corner case.
  • Replace event/first-op-requested with info/repl-ready adding more info about the project.
  • Add :first-time to :event/op-completed and :event/op-requested when op is load-file and first time processing it.
  • Bump opentelemetry to 1.51.0.

Michiel Borkent

2025 Annual Funding Report 3. Published July 1, 2025.

In this post I’ll give updates about open source I worked on during May and June 2025. To see previous OSS updates, go here.

Sponsors

I’d like to thank all the sponsors and contributors that make this work possible. Without you the below projects would not be as mature or wouldn’t exist or be maintained at all! So a sincere thank you to everyone who contributes to the sustainability of these projects.

gratitude

Current top tier sponsors:

Open the details section for more info about sponsoring.

Sponsor info

If you want to ensure that the projects I work on are sustainably maintained, you can sponsor this work in the following ways. Thank you!

Updates

Here are updates about the projects/libraries I’ve worked on in the last two months, 19 in total!

  • babashka: native, fast starting Clojure interpreter for scripting.

    • Bump edamame (support old-style #^ metadata)
    • Bump SCI: fix satisfies? for protocol extended to nil
    • Bump rewrite-clj to 1.2.50
    • 1.12.204 (2025-06-24)
    • Compatibility with clerk’s main branch
    • #1834: make taoensso/trove work in bb by exposing another timbre var
    • Bump timbre to 6.7.1
    • Protocol method should have :protocol meta
    • Add print-simple
    • Make bash install script work on Windows for GHA
    • Upgrade Jsoup to 1.21.1
    • 1.12.203 (2025-06-18)
    • Support with-redefs + intern (see SCI issue #973
    • #1832: support clojure.lang.Var/intern
    • Re-allow init as task name
    • 1.12.202 (2025-06-15)
    • Support clojure.lang.Var/{get,clone,reset}ThreadBindingFrame for JVM Clojure compatibility
    • #1741: fix taoensso.timbre/spy and include test
    • Add taoensso.timbre/set-ns-min-level! and taoensso.timbre/set-ns-min-level
    • 1.12.201 (2025-06-12)
    • #1825: Add Nextjournal Markdown as built-in Markdown library
    • Promesa compatibility (pending PR here)
    • Upgrade clojure to 1.12.1
    • #1818: wrong argument order in clojure.java.io/resource implementation
    • Add java.text.BreakIterator
    • Add classes for compatibility with promesa:
      • java.lang.Thread$Builder$OfPlatform
      • java.util.concurrent.ForkJoinPool
      • java.util.concurrent.ForkJoinPool$ForkJoinWorkerThreadFactory
      • java.util.concurrent.ForkJoinWorkerThread
      • java.util.concurrent.SynchronousQueue
    • Add taoensso.timbre/set-min-level!
    • Add taoensso.timbre/set-config!
    • Bump fs to 0.5.26
    • Bump jsoup to 1.20.1
    • Bump edamame to 1.4.30
    • Bump taoensso.timbre to 6.7.0
    • Bump pods: more graceful error handling when pod quits unexpectedly
    • #1815: Make install-script wget-compatible (@eval)
    • #1822: type should prioritize :type metadata
    • ns-name should work on symbols
    • :clojure.core/eval-file should affect *file* during eval
    • #1179: run :init in tasks only once
    • #1823: run :init in tasks before task specific requires
    • Fix resolve when *ns* is bound to symbol
    • Bump deps.clj to 1.12.1.1550
    • Bump http-client to 0.4.23
  • SCI: Configurable Clojure/Script interpreter suitable for scripting

    • 0.10.47 (2025-06-27)
    • Security issue: function recursion can be forced by returning internal keyword as return value
    • Fix #975: Protocol method should have :protocol var on metadata
    • Fix #971: fix satisfies? for protocol that is extended to nil
    • Fix #977: Can’t analyze sci.impl.analyzer with splint
    • 0.10.46 (2025-06-18)
    • Fix #957: sci.async/eval-string+ should return promise with :val nil for ns form rather than :val <Promise>
    • Fix #959: Java interop improvement: instance method invocation now leverages type hints
    • Bump edamame to 1.4.30
    • Give metadata :type key priority in type implementation
    • Fix #967: ns-name should work on symbols
    • Fix #969: ^:clojure.core/eval-file metadata should affect binding of *file* during evaluation
    • Sync sci.impl.Reflector with changes in clojure.lang.Reflector in clojure 1.12.1
    • Fix :static-methods option for class with different name in host
    • Fix #973: support with-redefs on core vars, e.g. intern. The fix for this issue entailed quite a big refactor of internals which removes “magic” injection of ctx in core vars that need it.
    • Add unchecked-set and unchecked-get for CLJS compatibility
  • clerk: Moldable Live Programming for Clojure

    • Make clerk compatible with babashka
  • quickblog: light-weight static blog engine for Clojure and babashka

    • 0.4.7 (2025-06-12)
    • Switch to Nextjournal Markdown for markdown rendering The minimum babashka version to be used with quickblog is now v1.12.201 since it comes with Nextjournal Markdown built-in.
    • Link to previous and next posts; see “Linking to previous and next posts” in the README (@jmglov)
    • Fix flaky caching tests (@jmglov)
    • Fix argument passing in test runner (@jmglov)
    • Add --date to api/new. (@jmglov)
    • Support Selmer template for new posts in api/new
    • Add ‘language-xxx’ to pre/code blocks
    • Fix README.md with working version in quickstart example
    • Fix #104: fix caching with respect to previews
    • Fix #104: document :preview option
  • edamame: configurable EDN and Clojure parser with location metadata and more

    • 1.4.31 (2025-06-25)
    • Fix #124: add :imports to parse-ns-form
    • Fix #125: Support #^:foo deprecated metadata reader macro (@NoahTheDuke)
    • Fix #127: expose continue value that indicates continue-ing parsing (@NoahTheDuke)
    • Fix #122: let :auto-resolve-ns affect syntax-quote
    • 1.4.30
    • #120: fix :auto-resolve-ns failing case
  • squint: CLJS syntax to JS compiler

    • #678: Implement random-uuid (@rafaeldelboni)
    • v0.8.149 (2025-06-19)
    • #671: Implement trampoline (@rafaeldelboni)
    • Fix #673: remove experimental atom as promise option since it causes unexpected behavior
    • Fix #672: alias may contain dots
    • v0.8.148 (2025-05-25)
    • Fix #669: munge refer-ed + renamed var
    • v0.8.147 (2025-05-09)
    • Fix #661: support throw in expression position
    • Fix #662: Fix extending protocol from other namespace to nil
    • Better solution for multiple expressions in return context in combination with pragmas
  • clj-kondo: static analyzer and linter for Clojure code that sparks joy.

    • #2560: NEW linter: :locking-suspicious-lock: report when locking is used on a single arg, interned value or local object
    • #2555: false positive with clojure.string/replace and partial as replacement fn
    • 2025.06.05
    • #2541: NEW linter: :discouraged-java-method. See docs
    • #2522: support :config-in-ns on :missing-protocol-method
    • #2524: support :redundant-ignore on :missing-protocol-method
    • #2536: false positive with format and whitespace flag after percent
    • #2535: false positive :missing-protocol-method when using alias in method
    • #2534: make :redundant-ignore aware of .cljc
    • #2527: add test for using ns-group + config-in-ns for :missing-protocol-method linter
    • #2218: use ReentrantLock to coordinate writes to cache directory within same process
    • #2533: report inline def under fn and defmethod
    • #2521: support :langs option in :discouraged-var to narrow to specific language
    • #2529: add :ns to &env in :macroexpand-hook macros when executing in CLJS
    • #2547: make redundant-fn-wrapper report only for all cljc branches
    • #2531: add :name data to :unresolved-namespace finding for clojure-lsp
  • sci.configs: A collection of ready to be used SCI configs.

  • scittle: Execute Clojure(Script) directly from browser script tags via SCI

  • nbb: Scripting in Clojure on Node.js using SCI

    • 1.3.204 (2025-05-15)
    • #389: fix regression caused by #387
    • 1.3.203 (2025-05-13)
    • #387: bump import-meta-resolve to fix deprecation warnings on Node 22+
    • 1.3.202 (2025-05-12)
    • Fix nbb nrepl server for Deno
    • 1.3.201 (2025-05-08)
    • Deno improvements for loading jsr: and npm: deps, including react in combination with reagent
    • #382: prefix all node imports with node:
  • quickdoc: Quick and minimal API doc generation for Clojure

    • v0.2.5 (2025-05-01)
    • Fix #32: fix anchor links to take into account var names that differ only by case
    • v0.2.4 (2025-05-01)
    • Revert source link in var title and move back to <sub>
    • Specify clojure 1.11 as the minimal Clojure version in deps.edn
    • Fix macro information
    • Fix #39: fix link when var is named multiple times in docstring
    • Upgrade clj-kondo to 2025.04.07
    • Add explicit org.babashka/cli dependency
  • Nextjournal Markdown

    • 0.7.186
    • Make library more GraalVM native-image friendly
    • 0.7.184
    • Consolidate utils in nextjournal.markdown.utils
    • 0.7.181
    • Hiccup JVM compatibility for fragments (see #34)
    • Support HTML blocks (:html-block) and inline HTML (:html-inline) (see #7)
    • Bump commonmark to 0.24.0
    • Bump markdown-it to 14.1.0
    • Render :code according to spec into <pre> and <code> block with language class (see #39)
    • No longer depend on applied-science/js-interop
    • Accept parsed result in ->hiccup function
    • Expose nextjournal.markdown.transform through main nextjournal.markdown namespace
    • Stabilize API and no longer mark library alpha
  • babashka.nrepl-client

    • Add :responses key with raw responses
  • speculative

    • Add spec for even?
  • http-client: babashka’s http-client

    • 0.4.23 (2025-06-06)
    • #75: override existing content type header in multipart request
    • Accept :request-method in addition to :request to align more with other clients
    • Accept :url in addition to :uri to align more with other clients
  • unused-deps: Find unused deps in a clojure project

    • This is a brand new project!
  • fs - File system utility library for Clojure

    • #147: fs/unzip should allow selective extraction of files (@sogaiu)
    • #145: fs/modified-since works only with ms precision but should support the precision of the filesystem
  • cherry: Experimental ClojureScript to ES6 module compiler

    • Fix cherry.embed which is used by malli
  • deps.clj: A faithful port of the clojure CLI bash script to Clojure

    • Released several versions catching up with the clojure CLI

Other projects

These are (some of the) other projects I’m involved with but little to no activity happened in the past month.

Click for more details - [CLI](https://github.com/babashka/cli): Turn Clojure functions into CLIs!
- [process](https://github.com/babashka/process): Clojure library for shelling out / spawning sub-processes
- [html](https://github.com/borkdude/html): Html generation library inspired by squint's html tag
- [instaparse-bb](https://github.com/babashka/instaparse-bb): Use instaparse from babashka
- [sql pods](https://github.com/babashka/babashka-sql-pods): babashka pods for SQL databases
- [rewrite-edn](https://github.com/borkdude/rewrite-edn): Utility lib on top of
- [rewrite-clj](https://github.com/clj-commons/rewrite-clj): Rewrite Clojure code and edn
- [pod-babashka-go-sqlite3](https://github.com/babashka/pod-babashka-go-sqlite3): A babashka pod for interacting with sqlite3
- [tools-deps-native](https://github.com/babashka/tools-deps-native) and [tools.bbuild](https://github.com/babashka/tools.bbuild): use tools.deps directly from babashka
- [http-server](https://github.com/babashka/http-server): serve static assets
- [bbin](https://github.com/babashka/bbin): Install any Babashka script or project with one comman
- [qualify-methods](https://github.com/borkdude/qualify-methods)
- Initial release of experimental tool to rewrite instance calls to use fully qualified methods (Clojure 1.12 only0
- [neil](https://github.com/babashka/neil): A CLI to add common aliases and features to deps.edn-based projects.
- [tools](https://github.com/borkdude/tools): a set of [bbin](https://github.com/babashka/bbin/) installable scripts
- [sci.nrepl](https://github.com/babashka/sci.nrepl): nREPL server for SCI projects that run in the browser
- [babashka.json](https://github.com/babashka/json): babashka JSON library/adapter
- [squint-macros](https://github.com/squint-cljs/squint-macros): a couple of macros that stand-in for [applied-science/js-interop](https://github.com/applied-science/js-interop) and [promesa](https://github.com/funcool/promesa) to make CLJS projects compatible with squint and/or cherry.
- [grasp](https://github.com/borkdude/grasp): Grep Clojure code using clojure.spec regexes
- [lein-clj-kondo](https://github.com/clj-kondo/lein-clj-kondo): a leiningen plugin for clj-kondo
- [http-kit](https://github.com/http-kit/http-kit): Simple, high-performance event-driven HTTP client+server for Clojure.
- [babashka.nrepl](https://github.com/babashka/babashka.nrepl): The nREPL server from babashka as a library, so it can be used from other SCI-based CLIs
- [jet](https://github.com/borkdude/jet): CLI to transform between JSON, EDN, YAML and Transit using Clojure
- [pod-babashka-fswatcher](https://github.com/babashka/pod-babashka-fswatcher): babashka filewatcher pod
- [lein2deps](https://github.com/borkdude/lein2deps): leiningen to deps.edn converter
- [cljs-showcase](https://github.com/borkdude/cljs-showcase): Showcase CLJS libs using SCI
- [babashka.book](https://github.com/babashka/book): Babashka manual
- [pod-babashka-buddy](https://github.com/babashka/pod-babashka-buddy): A pod around buddy core (Cryptographic Api for Clojure).
- [gh-release-artifact](https://github.com/borkdude/gh-release-artifact): Upload artifacts to Github releases idempotently
- [carve](https://github.com/borkdude/carve) - Remove unused Clojure vars
- [4ever-clojure](https://github.com/oxalorg/4ever-clojure) - Pure CLJS version of 4clojure, meant to run forever!
- [pod-babashka-lanterna](https://github.com/babashka/pod-babashka-lanterna): Interact with clojure-lanterna from babashka
- [joyride](https://github.com/BetterThanTomorrow/joyride): VSCode CLJS scripting and REPL (via [SCI](https://github.com/babashka/sci))
- [clj2el](https://borkdude.github.io/clj2el/): transpile Clojure to elisp
- [deflet](https://github.com/borkdude/deflet): make let-expressions REPL-friendly!
- [deps.add-lib](https://github.com/borkdude/deps.add-lib): Clojure 1.12's add-lib feature for leiningen and/or other environments without a specific version of the clojure CLI


Oleksandr Yakushev

2025 Annual Funding Report 3. Published July 5, 2025.

Hello friends! Here’s the May-June 2025 for my Clojurists Together work!. I’ve been focusing on CIDER during these two months, so all updates are related to it:


Peter Taoussanis

2025 Annual Funding Report 3. Published July 1, 2025.

A big thanks to Clojurists Together, Nubank, and other sponsors of my open source work! I realise that it’s a tough time for a lot of folks and businesses lately, and that sponsorships aren’t always easy 🙏

- Peter Taoussanis

Hello from sunny Berlin! 👋 🐝 🫶

Recent work

Introducing Trove

First up, a new micro library! It’s called Trove, and it’s basically a modern minimal (~100 loc) tools.logging that adds support for ClojureScript, structured logging and filtering.

Who’s this for?

Mostly library authors that want to do library-level logging without forcing a particular logging library on their users.

Authors could use tools.logging, but that doesn’t support ClojureScript, structured logging, or the kind of data-oriented filtering possible with structured logging.

Motivation

With Telemere now stable, I wanted to start updating libraries like Sente and Carmine to support structured logging.

Trove basically allows me to do this without forcing anyone to update if they’d rather stick with Timbre or use an alternative like μ/log.

Why structured logging?

Traditional logging is string oriented. Program state is captured at the logging callsite, then generally formatted into a message string or serialized into something like EDN/JSON before hitting the rest of the logging pipeline (filters → middleware → handlers).

Structured logging is data oriented. Program state is captured at the logging callsite, then retained as-is through the rest of the logging pipeline (filters → middleware → handlers).

The data-oriented approach means that:

  • No expensive formatting or serialization are needed.
  • Exact data types and (nested) data structures are losslessly retained throughout the logging pipeline.

The latter matters! It means that it’s easy and cheap to build a rich pipeline that can do data-oriented filtering, analytics, aggregations, transformations, and final handling (e.g. your DB or log aggregator might itself support rich types).

Clojure loves plain data and offers a rich set of data types, structures, and tools. Structured logging takes advantage of this. It’s essentially the extension of the data-first principle to the way we instrument our programs.

A big Sente update

Sente has a major new pre-release out.

There’s a lot of significant improvements in here. Some highlights include:

  • Added support for upcoming high-speed binary serialization
  • Added support for compression
  • A smaller dependency
  • Improved reliability under heavy load
  • Pluggable logging (via Trove)
  • Many other small fixes and improvements

Tufte v3 final

Tufte’s official v3 release is out.

There’s many big improvements, including many new features shared with Telemere, and new handlers for Telemere and Trove.

Other stuff

Other misc releases include:

Upcoming work

Next couple months I expect to focus on:

  • Getting Sente v1.21 final released, ideally with an official binary serialization mode
  • Getting Tempel v1 final released (sorry about the delays!)
  • Getting http-kit updated (lots of cool stuff pending there)

Cheers everyone! :-)

Permalink

Copyright © 2009, Planet Clojure. No rights reserved.
Planet Clojure is maintained by Baishamapayan Ghose.
Clojure and the Clojure logo are Copyright © 2008-2009, Rich Hickey.
Theme by Brajeshwar.