30+ Must-See GitHub Projects of This Week!

Exo: Exocompilation for productive programming of hardware accelerators

1. Exo: Exocompilation for productive programming of hardware accelerators

Check it out!

Psyllium, a Ruby Gem to make Fibers behave more like Threads

2. Psyllium, a Ruby Gem to make Fibers behave more like Threads

Check it out!

List of Useful Payloads and Bypass for Web Application Security and Pentest/CTF

3. List of Useful Payloads and Bypass for Web Application Security and Pentest/CTF

Check it out!

Neural โ€“ A DSL and Debugger for Simpler Neural Network Development

4. Neural โ€“ A DSL and Debugger for Simpler Neural Network Development

Check it out!

Ruby LLM

5. Ruby LLM

Check it out!

LLM-docs, software documentation intended for consumption by LLMs

6. LLM-docs, software documentation intended for consumption by LLMs

Check it out!

Deploy your stack's test data easily

7. Deploy your stack's test data easily

Check it out!

Detailed Feedback on Google's TS GenAI SDK

8. Detailed Feedback on Google's TS GenAI SDK

Check it out!

Sesame CSM: A Conversational Speech Generation Model

9. Sesame CSM: A Conversational Speech Generation Model

Check it out!

Guide to transform fragile AI agents into production-ready systems

10. Guide to transform fragile AI agents into production-ready systems

Check it out!

Sans UI library which supports iOS/Android/windows/macOS/Linux

11. Sans UI library which supports iOS/Android/windows/macOS/Linux

Check it out!

Build a Hyper-Simple Website for a Local Business and Charge $500+

Imagine this: You help a local business finally get online, transforming their visibility, and they happily pay you $500 or more for a website you built in just a few hours.You feel empowered.They feel grateful.Itโ€™s a win-win โ€” and you donโ€™t need to be an expert to make it happen.Iโ€™ve created a complete toolkit designed to remove every obstacle and hand you a simple, repeatable system to build websites, find clients, and get paid โ€” fast.Hereโ€™s what you get:๐Ÿ“˜ 1. The Local Digital Goldmine Guide (10 Pages of Pure Value) This step-by-step guide breaks down the entire process into simple, actionable steps: Why Local Businesses Need Simple Websites: Understand the massive opportunity in your local area. The No-Code Website Formula: Build a sleek, professional site in under 2 hours using free or low-cost tools. Finding & Pitching Clients Without Cold Calling: Use non-salesy strategies to attract clients, even if you hate selling. Pricing & Upselling for Recurring Income: Charge $500+ upfront, then stack passive income with hosting and maintenance upsells. Youโ€™ll finish this guide not just feeling motivated โ€” but knowing exactly what to do next.โœ… 2. Plug-and-Play Checklist (Stay Laser-Focused) Success is simple when you follow a proven process. This checklist is your roadmap: ๐Ÿ“ Pre-Launch Preparation: Research businesses, choose tools, and set up your payment system. ๐Ÿ” Client Outreach: Use personalized email scripts and follow-ups to land your first paying client. ๐Ÿ› ๏ธ Website Build: Follow a structured flow to build and launch your client's site. ๐Ÿค Client Management: Communicate like a pro, gather testimonials, and build lasting relationships. ๐Ÿ’ธ Pricing & Upsells: Lock in high-paying clients, then offer ongoing services for passive income. No overthinking. No confusion. Just tick the boxes, and watch your business grow.๐Ÿ”‘ 3. Handcrafted ChatGPT Prompts (Your AI-Powered Assistant) Why struggle to write client emails or site content when AI can do it for you? These prompts will save you hours: โœ๏ธ Website Content: Generate compelling headlines, service descriptions, and "About Us" sections. ๐Ÿ“ง Client Emails: Draft outreach, follow-ups, and pitch emails in seconds. ๐Ÿ“ˆ SEO & Optimization: Find the best local keywords, write meta descriptions, and boost site rankings. ๐ŸŽจ Design & Aesthetics: Get layout suggestions, color palette ideas, and font recommendations. ๐Ÿ’ฐ Pricing & Upsells: Brainstorm service packages, pricing tiers, and irresistible upsell offers. Youโ€™ll feel like you have a full team behind you โ€” even if youโ€™re a one-person business.๐Ÿ‘‰ This Isnโ€™t Just a Product โ€” Itโ€™s a Transformation Youโ€™re not just buying a bundle of files. Youโ€™re buying: ๐Ÿ”“ Clarity: Know exactly what to do, step by step. โšก Speed: Build and launch sites faster than you thought possible. ๐Ÿง  Confidence: Feel equipped to approach clients and charge what you're worth. ๐Ÿ“ˆ Freedom: Create a flexible, low-stress income stream from anywhere. Think about it: There are thousands of local businesses that desperately need a website.With this toolkit, you can be the person who delivers that solution โ€” and gets paid handsomely for it.Itโ€™s not a question of whether you can do this. The question is: How soon do you want to start?๐Ÿš€ One decision. One small investment. Infinite potential. Letโ€™s build something incredible.

favicon 0x7bshop.gumroad.com

Making extra income by selling websites has never been easierโ€”AI does most of the work for you!

No need to spend hours researching or figuring things out on your own. This step-by-step blueprint gives you everything you need:

  • โœ”๏ธ A complete guide that walks you through the process
  • โœ”๏ธ Detailed checklists so you donโ€™t miss a thing
  • โœ”๏ธ Pre-made ChatGPT prompts to make website creation effortless

Itโ€™s all laid out for youโ€”just follow the steps and start earning! ๐Ÿš€

Available on Gumroad - Instant Download

Golang to vlang source code translator

12. Golang to vlang source code translator

Check it out!

A solution to off-by-one errors in Racket based on XKCD

13. A solution to off-by-one errors in Racket based on XKCD

Check it out!

A healthcare platform that helps you develop high-quality compliant applications

14. A healthcare platform that helps you develop high-quality compliant applications

Check it out!

Scrapbook โ€“ addon that captures web pages to local device or back end server

15. Scrapbook โ€“ addon that captures web pages to local device or back end server

Check it out!

HashChain: Fast factor-based sublinear exact-match string search algorithm

16. HashChain: Fast factor-based sublinear exact-match string search algorithm

Check it out!

Owl: Optimized Workforce Learning for General Multi-Agent in Realworld Task Auto

17. Owl: Optimized Workforce Learning for General Multi-Agent in Realworld Task Auto

Check it out!

Desktop app for data science, Python notebook with code recipes and AI

18. Desktop app for data science, Python notebook with code recipes and AI

Check it out!

No Code: Best way to write secure and reliable applications

19. No Code: Best way to write secure and reliable applications

Check it out!

Coq-of-rust: Formal verification tool for Rust

20. Coq-of-rust: Formal verification tool for Rust

Check it out!

I-cant-believe-its-not-webusb: Hacking around lack of WebUSB support in Firefox

21. I-cant-believe-its-not-webusb: Hacking around lack of WebUSB support in Firefox

Check it out!

Cline: Autonomous Coding Agent for VS Code

22. Cline: Autonomous Coding Agent for VS Code

Check it out!

Build a Hyper-Simple Website for a Local Business and Charge $500+

Imagine this: You help a local business finally get online, transforming their visibility, and they happily pay you $500 or more for a website you built in just a few hours.You feel empowered.They feel grateful.Itโ€™s a win-win โ€” and you donโ€™t need to be an expert to make it happen.Iโ€™ve created a complete toolkit designed to remove every obstacle and hand you a simple, repeatable system to build websites, find clients, and get paid โ€” fast.Hereโ€™s what you get:๐Ÿ“˜ 1. The Local Digital Goldmine Guide (10 Pages of Pure Value) This step-by-step guide breaks down the entire process into simple, actionable steps: Why Local Businesses Need Simple Websites: Understand the massive opportunity in your local area. The No-Code Website Formula: Build a sleek, professional site in under 2 hours using free or low-cost tools. Finding & Pitching Clients Without Cold Calling: Use non-salesy strategies to attract clients, even if you hate selling. Pricing & Upselling for Recurring Income: Charge $500+ upfront, then stack passive income with hosting and maintenance upsells. Youโ€™ll finish this guide not just feeling motivated โ€” but knowing exactly what to do next.โœ… 2. Plug-and-Play Checklist (Stay Laser-Focused) Success is simple when you follow a proven process. This checklist is your roadmap: ๐Ÿ“ Pre-Launch Preparation: Research businesses, choose tools, and set up your payment system. ๐Ÿ” Client Outreach: Use personalized email scripts and follow-ups to land your first paying client. ๐Ÿ› ๏ธ Website Build: Follow a structured flow to build and launch your client's site. ๐Ÿค Client Management: Communicate like a pro, gather testimonials, and build lasting relationships. ๐Ÿ’ธ Pricing & Upsells: Lock in high-paying clients, then offer ongoing services for passive income. No overthinking. No confusion. Just tick the boxes, and watch your business grow.๐Ÿ”‘ 3. Handcrafted ChatGPT Prompts (Your AI-Powered Assistant) Why struggle to write client emails or site content when AI can do it for you? These prompts will save you hours: โœ๏ธ Website Content: Generate compelling headlines, service descriptions, and "About Us" sections. ๐Ÿ“ง Client Emails: Draft outreach, follow-ups, and pitch emails in seconds. ๐Ÿ“ˆ SEO & Optimization: Find the best local keywords, write meta descriptions, and boost site rankings. ๐ŸŽจ Design & Aesthetics: Get layout suggestions, color palette ideas, and font recommendations. ๐Ÿ’ฐ Pricing & Upsells: Brainstorm service packages, pricing tiers, and irresistible upsell offers. Youโ€™ll feel like you have a full team behind you โ€” even if youโ€™re a one-person business.๐Ÿ‘‰ This Isnโ€™t Just a Product โ€” Itโ€™s a Transformation Youโ€™re not just buying a bundle of files. Youโ€™re buying: ๐Ÿ”“ Clarity: Know exactly what to do, step by step. โšก Speed: Build and launch sites faster than you thought possible. ๐Ÿง  Confidence: Feel equipped to approach clients and charge what you're worth. ๐Ÿ“ˆ Freedom: Create a flexible, low-stress income stream from anywhere. Think about it: There are thousands of local businesses that desperately need a website.With this toolkit, you can be the person who delivers that solution โ€” and gets paid handsomely for it.Itโ€™s not a question of whether you can do this. The question is: How soon do you want to start?๐Ÿš€ One decision. One small investment. Infinite potential. Letโ€™s build something incredible.

favicon 0x7bshop.gumroad.com

Making extra income by selling websites has never been easierโ€”AI does most of the work for you!

No need to spend hours researching or figuring things out on your own. This step-by-step blueprint gives you everything you need:

  • โœ”๏ธ A complete guide that walks you through the process
  • โœ”๏ธ Detailed checklists so you donโ€™t miss a thing
  • โœ”๏ธ Pre-made ChatGPT prompts to make website creation effortless

Itโ€™s all laid out for youโ€”just follow the steps and start earning! ๐Ÿš€

Available on Gumroad - Instant Download

Gomcptest โ€“ A Go toolkit for testing LLM agents (MCP) with VertexAI

23. Gomcptest โ€“ A Go toolkit for testing LLM agents (MCP) with VertexAI

Check it out!

Automatically Enable Function Calling with Just TypeScript Objects

24. Automatically Enable Function Calling with Just TypeScript Objects

Check it out!

The default prefix used to be

25. The default prefix used to be "SQLite_". (2006)

Check it out!

ESLint plugin for vanilla-extract CSS

26. ESLint plugin for vanilla-extract CSS

Check it out!

Clojurellm-data: Clojure LLM โ€“ Dataset curation for fine tuning an LLM

27. Clojurellm-data: Clojure LLM โ€“ Dataset curation for fine tuning an LLM

Check it out!

Casual โ€“ re-imagine the Emacs UI using keyboard-driven menus

28. Casual โ€“ re-imagine the Emacs UI using keyboard-driven menus

Check it out!

Jupyter JEP: AI Representation for tools that interact with notebooks

29. Jupyter JEP: AI Representation for tools that interact with notebooks

Check it out!

Toy Operating System in Rust

30. Toy Operating System in Rust

Check it out!

The

31. The "No Clear Winner" Era of Federated Microblogging

Check it out!

A Python-based educational DSP playground

32. A Python-based educational DSP playground

Check it out!

ArkFlow โ€“ High-performance Rust stream processing engine

33. ArkFlow โ€“ High-performance Rust stream processing engine

Check it out!

CtrlSPEAK โ€“ Open-Source Speech-to-Text for macOS

34. CtrlSPEAK โ€“ Open-Source Speech-to-Text for macOS

Check it out!

๐ŸŽ Download Free Giveaway Products

We love sharing valuable resources with the community! Grab these free cheat sheets and level up your skills today. No strings attached โ€” just pure knowledge!

More Free Giveaway Products: ๐Ÿ”— Available Here

Build a Hyper-Simple Website for a Local Business and Charge $500+

Imagine this: You help a local business finally get online, transforming their visibility, and they happily pay you $500 or more for a website you built in just a few hours.You feel empowered.They feel grateful.Itโ€™s a win-win โ€” and you donโ€™t need to be an expert to make it happen.Iโ€™ve created a complete toolkit designed to remove every obstacle and hand you a simple, repeatable system to build websites, find clients, and get paid โ€” fast.Hereโ€™s what you get:๐Ÿ“˜ 1. The Local Digital Goldmine Guide (10 Pages of Pure Value) This step-by-step guide breaks down the entire process into simple, actionable steps: Why Local Businesses Need Simple Websites: Understand the massive opportunity in your local area. The No-Code Website Formula: Build a sleek, professional site in under 2 hours using free or low-cost tools. Finding & Pitching Clients Without Cold Calling: Use non-salesy strategies to attract clients, even if you hate selling. Pricing & Upselling for Recurring Income: Charge $500+ upfront, then stack passive income with hosting and maintenance upsells. Youโ€™ll finish this guide not just feeling motivated โ€” but knowing exactly what to do next.โœ… 2. Plug-and-Play Checklist (Stay Laser-Focused) Success is simple when you follow a proven process. This checklist is your roadmap: ๐Ÿ“ Pre-Launch Preparation: Research businesses, choose tools, and set up your payment system. ๐Ÿ” Client Outreach: Use personalized email scripts and follow-ups to land your first paying client. ๐Ÿ› ๏ธ Website Build: Follow a structured flow to build and launch your client's site. ๐Ÿค Client Management: Communicate like a pro, gather testimonials, and build lasting relationships. ๐Ÿ’ธ Pricing & Upsells: Lock in high-paying clients, then offer ongoing services for passive income. No overthinking. No confusion. Just tick the boxes, and watch your business grow.๐Ÿ”‘ 3. Handcrafted ChatGPT Prompts (Your AI-Powered Assistant) Why struggle to write client emails or site content when AI can do it for you? These prompts will save you hours: โœ๏ธ Website Content: Generate compelling headlines, service descriptions, and "About Us" sections. ๐Ÿ“ง Client Emails: Draft outreach, follow-ups, and pitch emails in seconds. ๐Ÿ“ˆ SEO & Optimization: Find the best local keywords, write meta descriptions, and boost site rankings. ๐ŸŽจ Design & Aesthetics: Get layout suggestions, color palette ideas, and font recommendations. ๐Ÿ’ฐ Pricing & Upsells: Brainstorm service packages, pricing tiers, and irresistible upsell offers. Youโ€™ll feel like you have a full team behind you โ€” even if youโ€™re a one-person business.๐Ÿ‘‰ This Isnโ€™t Just a Product โ€” Itโ€™s a Transformation Youโ€™re not just buying a bundle of files. Youโ€™re buying: ๐Ÿ”“ Clarity: Know exactly what to do, step by step. โšก Speed: Build and launch sites faster than you thought possible. ๐Ÿง  Confidence: Feel equipped to approach clients and charge what you're worth. ๐Ÿ“ˆ Freedom: Create a flexible, low-stress income stream from anywhere. Think about it: There are thousands of local businesses that desperately need a website.With this toolkit, you can be the person who delivers that solution โ€” and gets paid handsomely for it.Itโ€™s not a question of whether you can do this. The question is: How soon do you want to start?๐Ÿš€ One decision. One small investment. Infinite potential. Letโ€™s build something incredible.

favicon 0x7bshop.gumroad.com

Making extra income by selling websites has never been easierโ€”AI does most of the work for you!

No need to spend hours researching or figuring things out on your own. This step-by-step blueprint gives you everything you need:

  • โœ”๏ธ A complete guide that walks you through the process
  • โœ”๏ธ Detailed checklists so you donโ€™t miss a thing
  • โœ”๏ธ Pre-made ChatGPT prompts to make website creation effortless

Itโ€™s all laid out for youโ€”just follow the steps and start earning! ๐Ÿš€

Available on Gumroad - Instant Download

๐Ÿ’ฐ Earn Money with Our Affiliate Program

Want to make money promoting our products? Join our affiliate program and earn 40% commission on every sale! That means you can make anywhere between $8 to $40 per sale on average.

Join the Affiliate Program

Start sharing, start selling, and start earning! ๐Ÿš€

Permalink

Clojure Deref (Mar 14, 2025)

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

Libraries and Tools

New releases and tools this week:

  • core.async 1.8.718-beta2 - Facilities for async programming and communication in Clojure

  • honeysql 2.7.1295 - Turn Clojure data structures into SQL

  • clojure-plus 1.2.0 - A collection of utilities that improve Clojure experience

  • awesome-clojure-likes - Curated list of Clojure-like programming languages

  • contajners 1.0.7 - An idiomatic, data-driven, REPL friendly clojure client for OCI container engines

  • calva 2.0.487 - Clojure & ClojureScript Interactive Programming for VS Code

  • slim 0.3.0 - The slim way to build Clojure

  • squint 0.8.141 - Light-weight ClojureScript dialect

  • bling 0.5.2 - Rich text console printing for Clojure, ClojureScript, and Babashka

  • stripe-clojure 0.2.1 - Clojure SDK for the Stripe API

  • astro 2025-03-12 - Rich development workflow with Clojure support, using AstroNvim 4 and selected plugins

  • manifest-edn 0.1.1 - A small Clojure/Babashka library for hashing static assets

  • wet 0.3.0 - Liquid in Clojure(Script)

  • omega-red 2.2.0 - Idiomatic Redis client for Clojure

  • sketch 0.1.29 - Domain network modelling

  • c4k-keycloak 1.5.0 - k8s deployment for keycloak

  • next-jdbc 1.3.1002 - A modern low-level Clojure wrapper for JDBC-based access to databases

  • clojure-repl-intellij 2.4.0 - Free OpenSource IntelliJ plugin for Clojure REPL development

  • clojure-lsp 2025.03.07-17.42.36 - Clojure & ClojureScript Language Server (LSP) implementation

  • html 0.2.2 - Html generation library inspired by squintโ€™s html tag

  • lazytest 1.6.1 - A standalone BDD test framework for Clojure

  • flow-storm-debugger 4.2.1 - A debugger for Clojure and ClojureScript with some unique features

  • edamame 1.4.28 - Configurable EDN/Clojure parser with location metadata

  • sci 0.9.45 - Configurable Clojure/Script interpreter suitable for scripting and Clojure DSLs

  • clay 2-beta32 - A tiny Clojure tool for dynamic workflow of data visualization and literate programming

  • noj 2-beta10.1 - A clojure framework for data science

  • rv 0.0.6 - A Clojure library exploring the application of pure reasoning algorithms

  • babashka-sql-pods 0.1.4 - Babashka pods for SQL databases

  • cherry 0.4.24 - Experimental ClojureScript to ES6 module compiler

Permalink

How I Built a Google Docs Add-On in Three Hours (Using Vibe Engineering)

So, Iโ€™m writing a book with Steve Yegge (famous for his 20 years at Amazon and Google) on how developers can use GenAI to do amazing things. The working title is โ€œThe CHOP Handbook: The End Of Programming As We Know It, and Why It Will Be The Best Thing Ever For Developers.โ€

Weโ€™re considering new titles like โ€œChat and Vibe Engineering for Professionals,โ€ โ€œVibe Engineering In Production,โ€ or maybe even โ€œNo Vibe Coding When Iโ€™m On Callโ€ (thank you, Jessie Young!). Why the name change? Dr. Andrej Karpathy recently coined the term โ€œvibe coding,โ€ which has generated such a stir in the dev community.

Regardless of the title, our goal is to show developers how they can use GenAI to:

  • Build things faster.
  • Be more ambitious about things you can build.
  • Do it alone, rather than depending on a team.
  • Have more fun doing it.
  • And, explore vastly larger design spaces

In December, I marveled that I used Python to do statistical analysis in a Google Colab notebook, something I had previously done for decades only using SPSS. At the time, I thought that was a huge achievement in itself.ย 

However, last month, I did something that made that seem trivial by comparison: I wrote a Google Docs Add-On in three hours, using AI and โ€œvibe engineeringโ€ โ€”the term weโ€™re leaning into for the book.

Why โ€œvibe engineering,โ€ as opposed to โ€œvibe coding?โ€ Dr. Karpathy talks about vibe coding where you sort of turn your brain off. Weโ€™re describing something different โ€” you donโ€™t turn your brain off, and instead, you crank it way up, because with the help of AI, youโ€™re solving challenging problems you could never do alone. You are using AI as another engineering tool.

I built a Google Docs Add-On in a language I barely understand, TypeScript. Iโ€™ve spent hundreds of hours in JavaScript, and maybe 20 hours in TypeScript โ€” and Iโ€™ll be the first to admit that I barely understand most ES6 syntax.

The purpose of the Google Docs add-on was to explore the sibling of CHOP (Chat-Oriented Programming), which started jokingly calling CHOW, or Chat-Oriented Writing. (GitHub repo link at bottom of the post!)

Lessons:

  • You often donโ€™t need to understand all the code: This was the strange realization from my Python experience: โ€œI donโ€™t actually need to understand all of this code. For easy stuff, I visually inspect the results, and if the results look correct, weโ€™re good.โ€
  • But for some code, you definitely have to write tests. Luckily, Claude was great at it. (Honestly, I donโ€™t understand how the tests work.ย It uses some mocking technique I donโ€™t understand, using JavaScript prototype methods. I showed it to a longtime QA professional, and he instantly recognized it. All I cared about was that I could write tests that fail and pass.)
  • You need fast feedback.ย What use is fast code generation if you canโ€™t test it equally quicklyI This was a weak point for GAS. I couldnโ€™t figure out how to run automated tests inside of GAS, and I spent two hours making it easy to push changes and reload quickly. It was time very well spent. โ€“ Errors need to be easily accessible so you can quickly copy/paste into an LLM.

Short Aside: CHOP/CHOW Reminds Me of a DevOps Joke

Itโ€™s not just CHOP and CHOW.ย  Thereโ€™s a whole universe of activities in which you can use the same techniques for getting an AI to help you do your work.ย  Maybe we can express this pattern as:

  • Chat-Oriented *: (Chat-Oriented Everything)
  • Chat-Oriented (?<activity>+)$: the more precise regular expression (harhar)

Some of you probably realize the joke I stole there. Theo Schlossnagle had this wonderful line about DevOps. He pointed out that โ€œDevOps is often incomplete, misinterpreted, and too isolated. Itโ€™s not really DevOps anymore, but ratherโ€ฆโ€ โ€”and then he showed a bunch of regular expressions, which was hilarious.

  • DevOps: incomplete
  • Dev.*Ops: meaning โ€œDev Everything Opsโ€
  • (?<dept>+)Ops$: a more precise regex pattern that matches any department followed by โ€œOpsโ€

Get it? DevOps, DevSecOps, MarketingDevSecOps, MarketingDevSecOpsSales, etcโ€ฆ

Letโ€™s Skip JavaScript Editor Frameworksโ€”Letโ€™s Do a Google Docs Add-On!

Since last fall, I had been experimenting with JavaScript editor frameworks to find some way to better enable AI workflows to review and revise my text. I thought that Slate.js would be an amazing tool to create an easy editing environment. I had done some experiments, but nothing really seemed all that promising.

However, after talking to Steve on a Friday, we started running into a problem: I was getting better results using Claude to help review and revise text for the book than Steve was. In my mind, there is a very important meta observation here: CHOP and CHOW are orthogonal, but they involve the same techniques: context and prompts.

Months ago, in the CHOP domain, Steve noticed that I was getting pretty subpar results with AI-assisted codingโ€”during our pair programming session, he showed me how much better I could get at prompting. Sometimes by prompting less, sometimes more.

In the CHOW domain, a need started becoming evident. We needed a tool that could standardize the prompt generation process so that we could both get the same results when doing CHOW.

Thatโ€™s when the idea of using a Google Docs add-on came up. In his infectious and energetic way, Steve convinced me that building one was within my reach. Despite the fact that I knew absolutely nothing about them. I wasnโ€™t sure I could pull this off.

However, buoyed by my many successes in the last couple of months, I decided to give it a try. After all, Steve was here to help me, and the payoff could be huge. We were spending so much time in Google Docs working on the book manuscript.

Just in case you donโ€™t know what a Google Docs Add-On is, hereโ€™s a screenshot of one I use a lot for book projects.

  1. The Extensions menu item is where active Add-Ons reside
  2. The Sidebar that every Add-On renders โ€”ย thatโ€™s essentially your UI
  3. The Table of Contents Add-On I use, so it can be more like Microsoft Word auto-heading numbering
  4. My CHOW Add-On, waiting to be activated!

Remarkably, in just three hours, I was able to build a functioning Google Docs Add-On. I was really, really blown away by my ability to accomplish this โ€”ย I was bragging about it to almost everyone I talked with about it.

Here was the goal: Inside Google Docs, select text I wanted to review/revise/work with Claude.ย In my Add-On sidebar, click a button to have it generate a prompt.ย It would include the entire mansucript (spanning three different Google Doc files), include the task instructions, and the highlighted section being worked on. And generate a massive prompt that I could copy into Claude to review/critique/revise.

Once Again, Learn While Walking the Dog

Okay, so where do you start with a project like this when you donโ€™t know anything?

Again, I found myself walking a dog, and as Simon Willison taught us to do, I started using ChatGPT in voice mode. I spent almost 45 minutes in this conversation, exploring two main topics.

My first question was simple: I described what I wanted to build and asked, โ€œIs this even something that I could build as a Google Docs add-on?โ€ I learned that they are written in Google Apps Script, which I had used almost 12 or 13 years ago. I kept asking it to prove that this was possible, and it eventually convinced me that it could indeed be done.

Once I had gained a bit of understanding, the next question came into focus: โ€œOnce I build my add-on, how do I actually deploy and use it?โ€ ChatGPT explained that you can deploy it to the marketplace, or you can deploy it in test mode for your own documents. (It also described all the ways you can build and add on: You can build it as part of a document, or you build it so that you can use it in any document. I screwed this up a couple of times.)

In the next 20 minutes, I then asked about how one could write code not in the in-browser Google Apps Script IDE environment but on my laptop. I was surprised to learn that there is a tool called CLASP that does exactly that.

At that point, I had heard enough. I was convinced that I could build this.ย Letโ€™s go!

Google Apps Script: My Second Encounter

Google Docs add-ons are written in Google Apps Script (GAS from here on). I used GAS once for one project about 12 years ago to build an email autoresponder, which still runs today. It was my first experience with writing JavaScript. You go to https://script.google.com/home/, and youโ€™ll see an in-browser IDE. For a long time, GAS only executed an ancient version of JavaScript (ES3!!!), but now it supports ES6 (sort of).

(As much as I will complain about GAS, it is really quite remarkable. According to this article, โ€œApps Script is just as popular inside Google as it is among external users and developers. In fact, there are more than 70,000 weekly active scripts written by thousands of Googlers.โ€ You can use it to add custom functions to Google Sheets and Google Docs. It enables access to the APIs of most Google properties. Itโ€™s like Visual Basic, but for Google Apps. Keep reading for caveats.)

To get started, I once asked ChatGPT to explain how to use CLASP to create a GAS project, synchronize files, and push my files.ย I studied the GitHub repo, reading the README and their fantastic tutorials.

I manage to get it working, so itโ€™s time to code!

The Revelation of Augment Code

For this project, I wanted to try out Augment Code, which was absolutely awesome.ย In some ways, it is similar to using Claude Code. You donโ€™t do a lot of highlighting of text.ย You just type what you want, and it suggests modifications to your code. To my surprise, it handled multi-file editing seamlesslyโ€”it just seems to know what files need to be changed (just like Claude Code). And it applies the code suggestions as fast (or faster) than Cursor.

This was a month ago, but the prompts I remember typing sounded like:

  • Create a button that brings up a dialog box. (I then learned that `ui.alert` can have a Yes/No buttonโ€”who knew?)
  • Get the selection that is currently highlighted in the Google Docs editor.
  • Make a new button called โ€œReload Sidebarโ€. Make a button to put something in the clipboard.

In less than an hour, I managed to get a very simple Google Docs add-on working, with a simple sidebar and a counter. It was comprised of an HTML file, which renders the sidebar, and a .gs file, which is the JavaScript that was transpiled by CLASP using TypeScript.

I couldnโ€™t believe I was actually starting to build something that looked like a real Google Docs add-on!

My next prompt was: โ€œMake a new button called โ€˜Load Documentโ€™ and store it in a global variable.โ€

Then the sledding got pretty rough.

Google Apps Script API Documentation Leaves A Lot To Be Desired

I actually bought a book on GAS seven years ago: โ€œGoing GAS: From VBA to Google Apps Script.โ€ When I found myself sufficiently bewildered by how Google Docs add-ons worked, I found myself reading it again, but I got a little impatient. I started hacking around and eventually realized two things.

First, my mental model of how the Google Docs add-ons work was completely wrong. There are actually two pieces to it:

1. The front end is the HTML file, and you can run JavaScript code that you put there inside of <script> tags.
2. The back end is the GS file.

To my surprise, there is no shared memory between the two. Communication occurs via remote procedure call, triggered by using the Google.script.run API. And Iโ€™m pretty sure that the front end can only run ES3, while the back end runs something different in those .GS filesโ€” something like, but not exactly JavaScript. But Clasp allows you to write it in TypeScript/ES6l and transpiles it into those .GS files.

Where was that documented? I have no idea. (I was having lots of trouble finding documentation for things I was trying to do in GAS.ย And I think because there is so little documentation, Claude kept hallucinating APIs that didnโ€™t exist. More on that later.)

But that actually paled in comparison to a much bigger problem.

Fixing The Horrible 15-Second, Six-Step Feedback Loop

The issue was that reloading modified GAS code required so many steps. I would make a change on my laptop, save the file, and push it to GAS using CLASP. And then I have to go back to my Google Docs instance, click the Extensions menu bar, and click the โ€œReload Sidebarโ€ menu item.

This was really intolerable. Iโ€™m accustomed to hot code reloading in Clojure and ClojureScript, which is typical in many JavaScript environmentsโ€”you save your file and see the changes immediately.

This situation felt like the oppositeโ€”it was probably 15 seconds to get feedback on my work, and so much clicking! When your AI is giving you bad API calls that donโ€™t exist, itโ€™s really interolable that it takes so work to determine whether your code works or not.

I spent about two hours across two days trying to get as close to a hot reload as possible. Fortunately, CLASP has a watch mode that can monitor for local file changes, which can trigger the TypeScript transpilation and push into GAS.

I eventually got it down to clicking a โ€œReload Sidebarโ€ button, which was so much better than before, which required multiple clicks inside a menu item.

Now that I got a reasonable developer experience going, it was time to tackle the API documentation problem.

OpenAI Deep Research To The Rescue

I hit a brick wall because I managed to get the Google Docs add-on working for one document, but I couldnโ€™t figure out how to deploy it so that I could use it in any Google Docs environment. I searched for help and couldnโ€™t find anything.

I was at a total standstill.ย Fortunately, OpenAI Deep Research came to the rescue.ย The prompt:

  • I have written a Google Apps Script Workspace Add-On.ย I have a test deployment add-on deployed: deployment ID is xxxx.ย How do I use it in any Google Doc?

It came back after 10 minutes and gave me almost 10 pages of instructions on what to do โ€”ย hereโ€™s the ChatGPT session doc, in case this helps someone in the future. After some hacking around, I finally got it working. (Note my use of OpenAI Deep Research to find the solution, and o3-mini-high to use that knowledge to solve my problems.)

The biggest problem was that I just couldnโ€™t find the Add-On icon! Who knew that it would show up as an icon on the right sidebar? In the screenshot at the top of the post, my icon is #4. (Where was that documented?ย )

Oh, I also had to use Google Deep Research and o1-preview or o3-high to figure out how to create a working โ€œReload Sidebarโ€ button! (Every other attempt with any other model closed the sidebar and terminated the program before the reload finished.)

(When I told Steve this, he quipped, โ€œI complained about GAS not getting any love back in my platform rant in 2011. Itโ€™s been 13+ years, and those pancake heads at Google still havenโ€™t figured out that, yes, platforms really do matter. Iโ€™m sure GAS wants to do more but gets zero resourcing.โ€ย Haha.ย You always learn a bunch hanging out with Steve. ๐Ÿ˜†)

But once I got the add-on running inside my Google Doc, things started really cooking. Looking through the Git repo, my prompts must have looked like this:

  • Create function to convert the Google Doc data to Markdown.
  • Write tests for the above that could run inside Google Apps Script in the in-browser IDE. (This wasnโ€™t great, but some tests that are inconvenient to run are better than no tests at all,ย because I couldnโ€™t understand the giant JavaScript function, with lots of regular expression.)
  • Concatenate multiple Google Docs together (because our manuscript spans multiple Google Docs).
  • Add word count to the sidebar.
  • Create a dropdown box in the sidebar to enable multiple prompts. (Wow! I barely know how dropdown boxes work!)
  • Build tests for assembling the different types of prompts.
  • Help me clean up the mess I made in the sidebar.html file, because I had <script> tags scattered all over the place.
  • Introduce some global variables to manage persistent state.
  • Remove global variables and use the Google Apps Script API for key-value pairs.

Oh, another thing I did to improve my developer experience was putting my log messages in the sidebar! Google Docs add-on logging can be cumbersomeโ€”the front end canโ€™t log to the browser console (console.log doesnโ€™t work), and the back end logs only show up in the in-browser GAS IDE.

The Success Moment

I started this project on a Saturday morning. Late on Sunday night, I texted Steve that I was going to do my first roundtrip using CHOW. That meant I would use my CHOW tool to highlight some text, click a button, which would take the Google Docs files and the selection to generate a prompt in JSON. It would then copy it to the clipboard, so I could paste it into Claude.

In Claude, I could critique the section, review and revise it, and then copy it back into our manuscript in Google Docs.

Hereโ€™s the screenshot I took of the Add-On, and a video of me using the tool.

The first minute shows the add-on in action; the second minute demonstrates augment code making quick usability changes while pushing it into Google Apps Script. In the third minute, I showcase how quickly the โ€œApplyโ€ operations occur, and in the eighth minute, I illustrate how CHOW evaluates a section, critiques it, and uses Claude to implement changes.

Another challenge I encountered with the Google Docs add-on was obtaining logging information. I utilized the sidebar to display logs, and I was thrilled when Steve successfully installed the Google Docs add-on. Having others use your extension without publishing it in the marketplace can be quite cumbersome, but he managed to clone the repository and deploy it in his environment, which was great.

I was elated that Steve successfully installed the Google Docs add-on so he could use it himself. (He gave up trying to follow the instructions, and instead cloned my repo, and deploy it in his Google environment.)

Reinforced Lessons

As I reflected on what I had accomplished with this Google Docs add-on project, I realized something important about how AI is changing the way I learn and build.

You often donโ€™t need to understand all the code: This was the strange realization from my Python experience: โ€œI donโ€™t actually need to understand all of this code. For easy stuff, I visually inspect the results, and if the results look correct, weโ€™re good.โ€

But for some code, you definitely have to write tests. Luckily, Claude was great at it. (Honestly, I donโ€™t understand how the tests work.ย It uses some mocking technique I donโ€™t understand. I showed it to a longtime QA professional, and he instantly recognized it. All I cared about was that I could write tests that fail and pass.)

You need fast feedback.ย What use is fast code generation if you canโ€™t test it equally quicklyI This was a weak point for GAS. I couldnโ€™t figure out how to run automated tests inside of GAS, and I spent two hours making it easy to push changes and reload quickly. It was time very well spent.

Errors need to be easily accessible so you can quickly copy/paste into an LLM: Unfortunately, GAS is not great in this regard; because errors are buried inside the in-browser GAS IDE, and console messages canโ€™t go to the browser console. (My solution was to copy them into the add-on DOM)

An Embarrassing Moment Exposing My Lack Of Understanding Of My Code

An embarrassing moment occurred when Steve questioned a specific portion of the code, Google.script.run, asking why it was called twice. He made fun of me, suggesting I didnโ€™t catch the LLM doing something stupid.

I promised to fix it. But I realized the next day that it wasnโ€™t slow due to the multiple calls; the multiple calls were actually required because of callbacks. In fact, it was being called three times: once for the script call, once on success, and once on failure.

Thereโ€™s a lesson here, but what it is exactly eludes me right now.

(Moreover, my attempt to use async/await to fix the โ€œcallback hellโ€ problem didnโ€™t work.ย As I mentioned, I think itโ€™s because the JavaScript in the HTML appears must be the ES3 version of JavaScript, which dates back to the early 2010s.)

Vibe Engineering Is Amazing

For me, this represents an entirely new way of programming and writing. I was able to build a program in a language and ecosystem I barely knew. Iโ€™m using this tool nearly every day, and I couldnโ€™t have done it without AI. I was able to:

  • Build things faster.
  • Be more ambitious about the things I can build.
  • Do it alone rather than depending on a team.
  • Have more fun doing it.
  • And, explore vastly larger design spaces.

Oh, and hereโ€™s the link to the repo: https://github.com/realgenekim/chow-addon

Huzzah!

Weโ€™re Looking For GenAI/Dev Experience Reports

The Enterprise Technology Leadership Summit in Las Vegas in September will be featuring amazing experience reports โ€”ย one of the focus areas is how technology leaders are using GenAI to make developers more productive! Register or submit a talk proposal here!

The post How I Built a Google Docs Add-On in Three Hours (Using Vibe Engineering) appeared first on IT Revolution.

Permalink

Repetition

Sometimes, Clojure seems to miss operations that feel like they should be in clojure.core. I'm clearly not the only one who has felt this way, as evidenced by James Reeves's medley library. These missing operations are typically simple to implement, but after rewriting them for the nth time I feel like putting them in my own library. Or maybe contributing to James's.

Today's question was how to identify duplicates in a seq. For instance, say we have a sequence of numbers with a duplicate:

(def a-seq [0 1 2 3 4 2 6])

Step 1

My first thought was that every element in the sequence needs to be compared to every other item. Indeed, if the first element is not equal to any of the sequence that follows it, then that element need not be considered again. How might that look?

I'll start with a different seq where the first element does appear again:

(def seq0 [0 1 2 3 4 2 0])

We want to avoid indexing and loops whenever possible in Clojure, as these are conceptually low-level, and can be a source of errors. Instead, we want to look in clojure.core for applicable functions whenever possible.

As with any Lisp, the 2 main functions to consider for processing seqs are map and reduce. The map operation will do something for every element in a seq, returning a new seq, while reduce will derive a single value from processing each element in a seq. In this case, we're looking for a repeated value:

(reduce (fn [result element] (or result (= (first a-seq) element))) nil (rest a-seq))
true

OK, this found the value, but which value was it?

(reduce (fn [result element] (or result (and (= (first a-seq) element) element))) (rest a-seq))
0

This could then be applied to each subsequence, of the seq, but... wow, is it clunky. The reducing function is doing a lot here, comparing if the result is true yet, and if not, then comparing the provided first element to the element being presented by reduce, returning that element if needed.

We could clean it up a little by putting reduced over the return value so that it breaks as soon as the first match is found, and using a singleton set for the equality test:

(reduce (fn [result element] (or result (and (#{(first seq0)} element) (reduced element)))) (rest seq0))

But clojure.core already has a function that does all of this, called some. The documentation even tells us that we can use a set as an "equality" test when searching a seq:

(some #{(first a-seq)} (rest a-seq))

Step 2

But now we want to do this for every sub-seq, dropping off the head each time. One way might be to use drop to remove the start of the seq, counting up:

(map #(drop % a-seq) (range (count a-seq)))
((0 1 2 3 4 2 6) (1 2 3 4 2 6) (2 3 4 2 6) (3 4 2 6) (4 2 6) (2 6) (6))

But counting the sequence, or even using map-indexed (which does the counting for you) is still getting very involved in the mechanics of processing a sequence. Is there a higher-level construct?

One mechanism is using the rest operation over and over. This might be done using reduce in some way, but fortunately the iterate function does exactly this. Except it returns an infinite series, so we just need to take the correct amount. Let's go past the end by 5 to see why we need to keep it constrained:

(take (+ 5 (count a-seq)) (iterate rest a-seq))
([0 1 2 3 4 2 6] (1 2 3 4 2 6) (2 3 4 2 6) (3 4 2 6) (4 2 6) (2 6) (6) () () () () ())

This looks good, but it's still counting the length of the seq, so we want to keep looking for a better approach.

The map function is more flexible than simply applying an operation to transform each element of a seq. It can also be used to apply an operation to a group of elements across multiple seqs. The best part about this is that it will terminate as soon as the first of the seqs finishes. So let's try using map between the original seq and the iteration. We can just return the 2 values as pairs to see what we are working with:

(map (fn [a b] [a b]) a-seq (iterate rest a-seq))

This is almost exactly what we want as the arguments for the some operation above. Each operation of the map could execute rest on its second argument, but if we start the iteration one step along, then can get the same effect without needing the extra rest every time:

(map (fn [a b] [a b]) a-seq (rest (iterate rest a-seq)))
([0 (1 2 3 4 2 6)] [1 (2 3 4 2 6)] [2 (3 4 2 6)] [3 (4 2 6)] [4 (2 6)] [2 (6)] [6 ()])

These pairs are exactly what we needed for the some operation, so let's look at that:

(map (fn [a b] (some #{a} b)) a-seq (rest (iterate rest a-seq)))
(nil nil 2 nil nil nil nil)

Step 3

This is almost done. We need to skip to the first non-nil value, which we already know we can do using some. However, some needs a function to apply whereas we don't need to do anything to the value, just use it as-is. So we can use identity here:

(some identity (map (fn [a b] (some #{a} b)) a-seq (iterate rest (rest a-seq))))
2

To look more Clojure-ey we would usually run steps like this through a threading macro, and the anonymous function can be abbreviated as well. Of course, we want this to be a function, so we can replace the use of the a-seq value with a function argument, which I'll just call s for "seq":

(defn any=
 [s]
 (->> s
      rest
      (iterate rest)
      (map #(some #{%1} %2) s)
      (some identity)))

Step 4

After all of the above, I was thinking I had something relatively nice, and maybe I should write about the process of getting to it. Hence this post. I actually included extra steps along the way, just to spell things out, though I didn't actually go through most of those steps.

While thinking of extra steps and alternatives that I could write about, I thought about comparing to Clojure's distinct function which can remove duplicates:

(distinct a-seq)

This is similar to what I needed, but instead of telling me when it found a duplicate, it skips the duplicates instead. A similarly written function would also work, but this function is a transducer, and these are probably more work than should be necessary for something relatively simple.

I'm not saying that a transducer would be difficult. They follow a straightforward template:

(defn duplicates
 ([]
  (fn [rf]
    (let [seen (volatile! #{})]
      (fn
        ([] (rf))
        ([result] (rf result))
        ([result input]
         (if (contains? @seen input)
           (rf result input)
           (do (vswap! seen conj input)
               result)))))))
 ([coll] (sequence (duplicates) coll)))

This is actually a little more flexible since it can find all of the duplicates, rather than just the first one:

(duplicates [1 2 3 4 2 4 5 1])
(2 4 1)

The transducer approach also has the benefit of only processing the seq s single time, and using memory to recall what it has seen. So instead of O(n^2) complexity, it becomes O(n.log(n)) complexity (since the hashmap insertions and lookups are logarithmic - though in Clojure they are effectively O(1)).

However, all I needed was a function that could report the first duplicate in a seq. Maybe there was some way to use distinct for that.

Let's try using map again, this time against the original seq and it's distinct version. We'll emit pairs to see what is happening:

(map (fn [a b] [a b]) a-seq (distinct a-seq))
([1 1] [2 2] [3 3] [4 4] [2 5])

It terminated at the end of the distinct version, but we can see that just at the end the two sequences differed. This will happen if there is a duplicate in the first sequence that didn't appear in the second. So we can output that one when we get to it:

(map (fn [a b] (when (not= a b) a)) a-seq (distinct a-seq))
(nil nil nil nil nil 2)

This will keep emitting values after the first duplicate is found, since the two sequences are now misaligned. Consequently we only want to return the first non-nil value from the seq:

(some identity (map (fn [a b] (when (not= a b) a)) a-seq (distinct a-seq)))
2

Giving a new version for finding the first duplicate:

(defn any=
 [s]
 (->> s
      distinct
      (map #(when (not= %1 %2) %1) s)
      (some identity)))

Wrap Up

This is a little shorter, has better complexity, and makes better use of clojure.core. I'd probably use this one instead of my first attempt.

This post also reminded me to check my assumptions. This is not the first time I've tried to explain why one approach was not as effective as another, only to show that it was better. I need to remember that.

Permalink

Migrating to LazyTest

I've been using the Expectations testing library since early 2019 -- over six years. I love the expressiveness of it, compared to clojure.test, and it exists because "Classic Expectations" was not compatible with clojure.test tooling. At work, our tests use a mixture of clojure.test and Expectations, but in my open source projects, I've mostly stuck with clojure.test for familiarity's sake for contributors.Being compatible with clojure.test comes at a price, though. Expectations uses macros to produce clojure.test-compatible code under the hood, so it is limited by the same reporting as clojure.test and the same potential problems with tooling that tries to override parts of clojure.test's behavior -- namely that multiple tools do not play well together, so I've had to avoid "improving" the expressiveness or reporting in ways that would break compatibility with that tooling.

Permalink

Bad data models lead to code complexity

Our last Apropos was with Alex Engelberg. Check it out. Our next episode is with Sean Corfield on March 11 (Paula had to cancel). Please watch us live so you can ask questions.

Have you seen Grokking Simplicity, my book for beginners to functional programming? Please check it out or recommend it to a friend. You can also get it from Manning. Use coupon code TSSIMPLICITY for 50% off.


Bad data models lead to code complexity

There I was, staring at a complex, nested if statement with hard-to-follow logic in each condition. Whatโ€™s worse, similar logic was repeated in several places in the codebase. That was when I realized how much complexity a bad data model can cause.

An example of a bad domain model

The domain was simple. Iโ€™ll describe a similar one that is an easier example so I donโ€™t have to explain the whole domain. Imagine a content management system for a magazine. Articles go through several stages as they are prepared for publication:

  • Drafting

  • Editing

  • Approval

  • Publication

We modeled it with several Maybe<DateTime> type. As we finished a stage, we added the time. If the time didnโ€™t exist, it meant that we had not finished that stage yet.

type Document = {
  drafted?  : Date;
  edited?   : Date;
  approved? : Date;
  published?: Date;
};

This worked for a while, but eventually we found problems.

The first problem was that our logic was complicated and duplicated. If we wanted to know if something was in the approval stage, we had logic like this:

if(document.drafted && document.edited && !document.approved)

The second problem was that as we increased the number of documents in the database, with many releases to our software (often with bugs), we eventually got data that had impossible states. For instance, it was edited without being drafted:

{
  drafted: null,
  edited: new Date("2010-01-02")
}

How could this happen? Well, despite our checks, something slipped through. Maybe it was a bug in a prior release. Maybe it was user error. Whatever the case, it existed, and our code needed to make sense of it. That meant more if statements.

This is a bad data model. I mean that in an objective way. We can actually measure how bad it is. We can count the number of states in the domain and the number of states in the model, and see how far off they are from each other. Here is the timeline of a document workflow:

The horizontal lines represent events that divide the timeline into sections. Each section is a distinct state that the document can be in. We see clearly that there are five states a document can be in in this domain.

Our model is different. It contains for optional values, where the presence of the value is meaningful. The date is also meaningful, but in a different way. Iโ€™m going to ignore it for this analysis, even though it also could present difficulties such as if a document is published before it is drafted. Weโ€™ll just look at the presence or absence of the value.

In that situation, there are four values with two options each (present/absent), which is 2^4 = 16. So our data can represent 16 states. That means that 11 of our states are redundant, ambiguous, or invalid, because we only needed 5.

That sucks. Those unneeded states caused a lot of code complexity because our code had to map them to the domain concept. It had to deal with the ambiguity of the situation. That was the second problem I talked about (finding impossible data in the database). We could have seen this coming with some simple math.

The first problem was still there. Remember, the first problem was about the complex, duplicated logic we found everywhere. It wasnโ€™t really because there were ambiguous states. It was more that what we stored didnโ€™t correspond well to what we needed.

Remember, we were storing the time that we crossed each of the horizontal lines. But what we wanted to know was which green box we were in. We had to write compound conditionals to convert the lines into boxes.

Adapting the existing data model to a better one

Luckily, we can adapt this model and solve both problems with in one stroke. Remember, domain modeling is about mapping. So letโ€™s create a function that maps the data model we have to the data model we wish we had:

function documentStatus(document) {
  if(document.published) return "published";
  if(document.approved) return "ready";
  if(document.edited) return "approval";
  if(document.drafted) return "editing";
  else return "drafting";
}

We did double-check to make sure that this does correctly map the ambiguous states to the correct states. Luckily, it was simple. We could imagine it being much more complex than this. For instance, we might have a check somewhere in the code that a document with a published date but no content is actually in the drafting stage. I have a hunch that this is where the really hairy complexity comes fromโ€”different behavior in different places in the codebase because of inconsistent logic. Either way, having a single place that defines the mapping to our desired model is a good thing. The biggest trouble with it is making sure to use it consistently.

We could then use the human-readable string representing the status in place of a bunch of booleans anded together:

if(documentStatus(document) === "approval")

instead of:

if(document.drafted && document.edited && !document.approved)

Here are the things we achieved:

  • Centralized the logic for determining the status

  • Eliminated inconsistencies in status logic across the codebase

  • Made status checks more human-readable

  • Eliminated complex conditional logic

Iโ€™d say it was a major win.

Is this a major win? Or a waste of time?

Even still, I have encountered people who think this is a waste of time. To put a fine point on it, this change was both small (in the number of lines of code it saved) and extensive (in the number of lines of code and the number of files it touched). Further, it was risky. Changing so many lines of working code could introduce bugs we couldnโ€™t anticipate. In short, itโ€™s high risk and it doesnโ€™t eliminate much complexity.

When I ask them about where code complexity comes from, they say itโ€™s from other sourcesโ€”mainly business logic, user requirements, and system interactions. Bad domain models do introduce complexity, but itโ€™s a rounding error compared to those sources.

My response is always the same: Those sources of complexity can be modeled, too. Nesting if statements is one simple tool we have to address a corner case. Iโ€™ve done it myself thousands of times. Itโ€™s extremely useful when I donโ€™t have the time to fully understand what is happening in the codebase. Surgically adding an if where Iโ€™m certain it will be useful is a versatile tool.

But nesting ifs is a tool that breeds more complexity. The next time I or someone else needs to handle a new case, it becomes that much harder to understand, so that much more likely that weโ€™ll add yet another if.

Modeling is the only way to rein in complexity

Look, Iโ€™m getting deep into the weeds here, and I need to bring this thread back to my intended point: Thereโ€™s no way to clean up those ifs without understanding the domain. All a domain model is is an understanding of the domain expressed in code. When you express it in code, its an understand that is available to anyone who cares to read it. Domain modeling is software design. Trying to eliminate complexity without modeling is a foolโ€™s errand. Domain modeling is a necessary skill for designing software. So we might as well get good at it.

When we talk about modeling business logic, often people hear โ€œoversimplifyingโ€. That is not what Iโ€™m talking about at all. Domain modeling means embracing the complexity, but identifying the structure of it, then writing that structure down in useful code. Maybe Iโ€™m preaching to the choir, so Iโ€™ll just leave it at that. If youโ€™ve got an understanding, you can write it down. I also hope to show that there are objective measures of data model quality in my upcoming book. Modeling is a skill, and you can get better at it.

Conclusion

Code complexity is a major cost of software development. Bad data models add significantly to code complexityโ€”when models donโ€™t correspond to the domain, we require extra code to handle the redundant and missing cases. Cleaning up the data model can significantly contribute to reducing complexity. Even if you think that data models arenโ€™t a major source of complexity, the sources of complexity can also be modeled. Chances are, the current models of things like business logic, user requirements, and system interactions are poor models. Theyโ€™re adding way more complexity than they actually need. You can clean those up, too.

I hope to bring this awareness of poor models to the industry. Currently weโ€™re stuck thinking about code in isolation instead of how it relates to the domain. Iโ€™d like to help fix that, which is why itโ€™s a major theme of my book. I donโ€™t want domain modeling to be just another abstract skill. Instead, itโ€™s fundamental to software design. The more I research it, the more I see that itโ€™s the real key to explaining a lot of software design advice.

Do you have any interesting stories involving domain modeling? How do you apply domain modeling in your work?

Permalink

Software Engineer at Scarlet

Software Engineer at Scarlet

gbp60000 - gbp110000

Our mission is to hasten the transition to universally accessible healthcare. We deliver on this mission by enabling innovators to bring cutting-edge software and AI to the healthcare market safely and quickly. We're regulated by the UK Government and European Commission to do so.

Our certification process is optimised for software and AI, facilitating a more efficient time to market, and the frequent releases needed to build great software. This ensures patients safely get the most up-to-date versions of life-changing technology.

Come help us bring the next generation of healthcare to the people who need it.

Our challenges

Product and engineering challenges go hand in hand at Scarlet. We know our mission can only be accomplished if we:

  • Build products and services that our customers love.
  • Streamline and accelerate complex regulatory processes without sacrificing quality.
  • Ensure that we always conduct excellent safety assessments of our customersโ€™ products.
  • Continuously ship great functionality at a high cadence - and have fun while doing it.
  • Build and maintain a tech stack that embraces the complex domain in which we work.

Our engineering problems are plenty and we have chosen Clojure as the tool to solve them.

The team

The team is everything at Scarlet and we aspire to shape and nurture a team where every team member:

  • Really cares about our customers.
  • Works cross-functionally with engineers, product managers, designers, regulatory experts, and other stakeholders.
  • Collaborates on solving hard, important, real-world problems.
  • Helps bring out the best in each other and support each otherโ€™s growth.
  • Brings a diverse set of experiences, backgrounds, and perspectives.
  • Feels that what we do day-to-day is deeply meaningful.

The engineering team today consists of: Ferdinand, Ivรกn, Jacob, Lukas and Niclas. We all have our fair share of experience working with startups, open source and various problem spaces. We wish to expand the team with ~3 more team members that can balance our strengths and weaknesses and help Scarlet build fantastic products.

Weโ€™re looking for ambitious teammates who have at least a few years of experience, have an insatiable hunger to learn, and want to do the most important work of their career!

How we work

Our ways of working are guided by a desire to perform at the highest level and do great work.

  • Flexible working: Remote-first with no fixed hours or vacation tracking.
  • Low/no scheduled meetings: Keep meetings to a minimumโ€”no daily stand-ups or agile ceremonies.
  • Asynchronous collaboration: Have rich async discussions and flexible 1:1s as needed.
  • High trust and autonomy: Everyone solves problems; we are responsible for our choices and communicating them with our teammates.
  • Getting together: We meet a minimum of twice a year for a week at our offices in London.

About you

If this sounds exciting to you, we believe Scarlet may be a great fit and would love to hear from you!

We believe that the potential for a great fit is even higher if you have one or more of the following:

  • Professional Clojure experience.
  • Professional full-stack web development experience.
  • Previous experience in the health tech / regulatory space
  • Endless curiosity and are always driven to understand why things are the way they are.
  • Superb written and verbal communication
  • Live within +/- 2h of the UKโ€™s timezone

The interview process

Though the order may change, the interview steps are:

  1. Intro call with Niclas - 45 mins
  2. Technical knowledge call with an engineer - 60 mins
  3. Culture fit call with a product manager - 60 mins
  4. Technical workshop with Niclas - 90 mins
    • A pair programming session or a presentation of something youโ€™ve built, the choice is yours
  5. Culture fit calls with our co-founders Jamie and James - 30 mins each
  6. Referencing & offer

We want your experience with Scarlet to be a good one and we do our utmost to ensure that you feel welcomed throughout the interview process.

Permalink

Annually-Funded Developers' Update: Jan./Feb. 2025

Hello Fellow Clojurists! This is the first report from the 5 developers receivng Annual Funding in 2025. (Highlights listed on the list below - but the reports include much more).

Dragan Duric: Apple M Engine Neanderthal
Eric Dallo: lsp-intellij, repl-intellij. lsp, lsp4clj
Michiel Borkent: clj-kondo, squint, babashka, fs, SCI, and moreโ€ฆ
Peter Taoussanis: Truss v2, Telemere v1 RC3
Oleksandr Yakushev: CIDER, clj-async-profiler and Flamebin, clj-memory-meter

Dragan Duric

2025 Annual Funding Report 1. Published February 27, 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 January and February, I released the first version of Neanderhal that can run on Mac/Apple M!

My grant proposal: Hereโ€™s what Iโ€™ve proposed when applying for the CT grant.
I propose 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

First, I set to the task of tidying up the existing versions of Uncomplicate libraries (Neanderthal, Deep Diamond, etc.) to bring them up with the latest versions of native libraries, cuda, etc., and to fix some outstanding issues/bugs that might complicate work on Apple M support. After that, it was time for the main task, the beginnings of Apple M support.

The plan was to buy an Apple M2/M3, but in the meantime the nice Clojurians from Prague donated a used (but fantastically beefed up) MacBook Pro Max M1, so this was covered quickly!

I explored OpenBLAS as the first choice (the other is Apple Accelerate), as it can also work on Linux and Windows, and could be immediately beneficial to all users and easier to start with (I didnโ€™t need to switch to Apple yet).

I implemented the OpenBLAS engine for the part of functionality that was supported by JavaCPPโ€™s openblas preset. A lot of critical functionality was not present there (although some of it was there in the openblas itself), so I jumped at the opportunity to learn some JavaCPP preset building, and improved JavaCPPโ€™s OpenBLAS. After a bit of experimentation and lot of waiting on the compiler and github tools, this is now contributed upstream.

Next, I returned to the pleasant part of work - programming in Clojure - and completed the first Neanderthal engine that runs in Apple M Macs. This covers the core and linalg namespace, because this is what OpenBLAS covers. The rest of Neanderthal functionality is waiting for me to explore Apple Accelerate, and to create engine based on that.

I managed to release Neanderthal 0.53.2, which enables Clojurians to use this on their Macs, just in time for this report.

I hope theyโ€™ll have immediate benefits, and have fun doing some high performance hacking on their Macs.

Many thanks for CT for sponsoring this work!


Eric Dallo

2025 Annual Funding Report 1. Published February 27, 2025.

In the first two months of sponsorship I could work on so many things related to IDE development which made me really glad of this sponsorship! :heart:

I spend most of the time improving the Clojure development on IntelliJ, improving both clojure-lsp-intellij and clojure-repl-intellij plugins releasing 2 major extremally important versions. The IntelliJ Clojure development using those plugins are way better and mature, please test and give feedback!

clojure-lsp-intellij

The 3.0.0 major version was a refactor on most of the plugin to use lsp4ij, an OSS plugin for IntelliJ which makes easier to code and use LSP features, so this integration removed tons of code from this plugin that I needed to implement manually (and some had some bugs) and added support for lots of missing features that I didnโ€™t even plan to add. The lsp4ij plugin is used by multiple languages already which makes this plugin more resilient and stable. Check it out the call hieararchy feature on the image.
call-hierarchy dallo feb 2025

Kudos to @angelozerr for the help during lsp4ij integration on their side.

3.0.0 - 3.1.0

  • Fix brace matcher to insert closing brace for some missing cases.
  • Add imcompatible tag with Cursive and Clojure-Kit plugins.
  • Integrate with lsp4ij, a LSP client plugin, removing lots of logics from this plugin and fixing multiple bugs and issues. Fixes #63, #61, #59, #57, #53, #36, #21, #9, #5
  • Drop support for older intellijs, supporting 2023.3 onwards.
  • Bump clj4intellij to 0.6.3.
  • Fix code lens references not working when more than a project is opened. #67
  • Fix Settings page exception when more than a project was opened and closed.
  • Fix comment form complain about missing paren.
  • Improve server installation fixing concurrency bugs + using lsp4ij install API.

clojure-repl-intellij

This 2.0.0 major release included multiple fixes and new features like the new _Inlay hint eval result + _REPL syntax coloring.

clojure-repl-intellij dallo feb 2025

Kudos to @afucher for the help on some of those features.

2.0.0 - 2.2.0

  • Add icons of REPL commands to REPL window (clear and entry history navigation). #99
  • Drop support of older IntelliJ versions (2021/2022). Now requires minimum IntelliJ 2023.3 (Build 233)
  • Fix namespace-not-found error handling. Now shows a message to the user. #107
  • Add eval inlay hint support. #106
  • Add action to interrupt evals on the REPL session (shift alt R + shift alt S). #104
  • Add color settings page for customization of some tokens.
  • Add default name for RunConfigurations instead of save as Unnamed. #123
  • Add REPL syntax highlight. #18
  • Fix eval defun at cursor action error. #121
  • Create view error on test error. #128
  • Block backspace on repl input.

clojure-lsp

clojure-lsp is the base for all Clojure language handle logic and analysis, so lots of fixes and improvements are made all the time.

2025.01.22-23.28.23 - 2025.02.07-16.11.24

  • General

    • Bump clj-kondo to 2025.01.16
    • Bump lsp4clj to 1.11.0.
    • Add semantic version sorting in completion lib versions. #1913
    • Fix internal error in range formatting. #1931
    • Drop support for jdk 8. #1942
  • Editor

    • Change simple keyword completion to return all known keywords. #1920
    • Return textEdit to CompletionItems to fix completion in Zed #1933
    • Restrict linked edits to namespace aliases only, and fix a few related issues #1947
    • Add :hover :hide-signature-call? settings option to disable showing the surrounding call. #1954, @NoahTheDuke
    • Revert #1933, which caused a regression on completion adding extra text.
    • Fix fetching libs exception causing progress notification to be stuck. #1958
  • API/CLI

    • Add :project-and-shallow-analysis type to dump command
    • Add :diagnostics to dump command output (successor of :findings)

lsp4clj

lsp4lj is the base of clojure-lsp, itโ€™s the layer that has all the LSP communication layer, making easy to build LSP clients/servers of any language in Clojure.

v1.11.0

  • Add a :response-executor option to control on which thread responses to server-initiated requests are run, defaulting to Promesaโ€™s :default executor, i.e. ForkJoinPool/commonPool.
  • Fix work done progress notification to allow nullable message.

clj4intellij

clj4intellij is a lib that makes possible to code IntelliJ plugins in Clojure, itโ€™s used by both clojure-lsp-intellij and clojure-repl-intellij plugins.

0.6.0 - 0.6.3

  • Add unregister-action! and improve register-action!
  • Add clj-kondo hook for proxy+.

Michiel Borkent

2025 Annual Funding Report 1. Published February 28, 2025.

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. Top sponsors:

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!

If youโ€™re used to sponsoring through some other means which isnโ€™t listed above, please get in touch. Thank you! On to the projects that Iโ€™ve been working on!

As Iโ€™m writing this Iโ€™m still recovering from a flu that has kept me bedridden for a good few days, but Iโ€™m starting to feel better now. Here are updates about the projects/libraries Iโ€™ve worked on in the last two months.
As Iโ€™m writing this Iโ€™m still recovering from a flu that has kept me bedridden for a good few days, but Iโ€™m starting to feel better now.

Here are updates about the projects/libraries Iโ€™ve worked on in the last two months.

  • clj-kondo: static analyzer and linter for Clojure code that sparks joy.
    • Unreleased:
    • #2493: reduce image size of native image
    • 2025.02.20:
    • #2473: New linter: :unknown-ns-options will warn on malformed (ns) calls. The linter is {:level :warning} by default. (@Noahtheduke)
    • #2475: add :do-template linter to check args & values counts (@imrekoszo)
    • #2465: fix :discouraged-var linter for fixed arities
    • #2277: prefer an array class symbol over (Class/forName โ€ฆ) in defprotocol and extend-type
    • #2466: fix false positive with tagged literal in macroexpand hook
    • #2463: using :min-clj-kondo-version results in incorrect warning (@imrekoszo)
    • #2464: :min-clj-kondo-version warning/error should have a location in config.edn (@imrekoszo)
    • #2472 hooks api/resolve should return nil for unresolved symbols and locals
    • #2472: add api/env to determine if symbol is local
    • #2482: Upgrade to Oracle GraalVM 23
    • #2483: add api/quote-node and api/quote-node? to hooks API (@camsaul)
    • #2490: restore unofficial support for ignore hints via metadata
  • squint: CLJS syntax to JS compiler
    • Fix #609: make remove return a transducer when no collection is provided
    • Fix #611: Implement the set? function (@jonasseglare)
    • Fix #613: Optimize aset
    • Fix #626: Implement take-last
    • Fix #615: (zero? โ€œ0โ€) should return false
    • Fix #617: deftype field name munging problem
    • Fix #618: Named multi-arity fn args donโ€™t get munged (@grayrest)
    • Fix #622: operator precendence issue with | and if
    • Add clojure.string functions lower-case, upper-case, capitalize (@plexus)
    • Fix #605: merge command line โ€“paths with squint.edn config properly
    • Fix #607: make mapcat return a transducer if no collections are provided (@jonasseglare)
  • babashka: native, fast starting Clojure interpreter for scripting.
    • Experimenting upgrading to new beta core.async, work in is a branch ready to be merged
    • #1785: Allow subclasses of Throwable to have instance methods invoked (@bobisageek)
    • #1791: interop problem on Jsoup form element
    • #1793: Bump rewrite-clj to 1.1.49 (fixes parsing of foo// among other things)
    • Bump deps.clj
    • Bump fs
  • fs - File system utility library for Clojure
    • v0.5.24 (2025-01-09)
    • #135: additional fix for preserving protocol when calling fs/path on multiple arguments (@Sohalt)
  • SCI: Configurable Clojure/Script interpreter suitable for scripting
    • Records should have keys present and set to nil
  • deps.clj: A faithful port of the clojure CLI bash script to Clojure
    • Catch up with several new versions of clojure CLI
    • Fix #132: copy install tools.edn to config dir when install version is newer, similar to clojure CLI bash script
    • Adds support for XDG_DATA_HOME environment variable according to XDG Base Directory Specification
  • sql pods: babashka pods for SQL databases
  • edamame: Configurable EDN/Clojure parser with location metadata
    • Fix #115: add location to exception for invalid keyword
  • rewrite-edn: Utility lib on top of rewrite-clj with common operations to update EDN while preserving whitespace and comments
    • #40: assoc/update now handles map keys that have no indent at all (@lread)
    • #43: bump rewrite-clj to 1.1.49 (@lread)
    • #40: assoc/update now handles map keys that have no indent at all (@lread)
    • #40: assoc/update now aligns indent to comment if thatโ€™s all that is in the map (@lread)
    • #40: update now indents new entries in same way as assoc (@lread)

Other projects Iโ€™ve been working on this month. See Click for More Details.


Peter Taoussanis

2025 Annual Funding Report 1. Published February 27, 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

Hi everyone! ๐Ÿ‘‹

Iโ€™ve been focused recently on getting Telemere v1 over the finish line. Thatโ€™s been a lot of ongoing work, in part because Iโ€™m trying to establish patterns that can be easily shared between a suite of complementary libs (Telemere, Tufte, and Truss).

Iโ€™ve also been considering long-term plans for how to better modularize Encore to help reduce dependency and build sizes where relevant. More on that later in the year.

Truss v2 - an opinionated micro toolkit for Clj/s errors

Iโ€™ve recently released Truss v2, which has enlarged the scope of the library from just assertion utils to a general error toolkit for Clojure and ClojureScript.

v2 includes:

  • A ground-up rewrite of the existing assertion utils to improve performance, reduce build size, and improve integration with Telemere.
  • A new set of small but high-value error utils moved from Encore as part of the ongoing modularization effort.
  • A new contextual exception type thatโ€™s already used by Encore and Telemere, and will be used by all of my other libraries in future.
  • Updated docs and a new Slack channel.
    Truss v2 basically packages together a minimal set of mature tools and patterns that Iโ€™ve used over many years to help tame Clojureโ€™s often unruly errors. Itโ€™s simple stuff, but practical - and it helps.
    For more info see the README and docstrings.

Telemere v1 RC3 - structured logging and telemetry for Clj/s

I was hoping to release Telemere v1 final this month, but decided that a third release candidate was warranted.

The latest improvements are focused on harmonizing relevant terminology, concepts, and API between Telemere, Truss, and the forthcoming v3 of Tufte.

Big thanks to everyone thatโ€™s been helping test and give feedback! v1 final really should (finally) be available next month ๐Ÿ™

Upcoming work

The next major release planned is Tufte v3. Iโ€™ve already got an early draft prepared, but thereโ€™s polish needed - and new docs.

v3 improves performance and significantly improves interop with Telemere, offering what I believe to be a pretty compelling combination for Clojure/Script observability and monitoring.

For plans after that, you can see my 2025 roadmap), which as usual Iโ€™ll keep updated along the way ๐Ÿ‘

Cheers everyone! :-)


Oleksandr Yakushev

2025 Annual Funding Report 1. Published February 28, 2025.

Hello friends! Hereโ€™s a short summary of my January-February Clojurists Together 2025 sponsorship work.

CIDER

clj-async-profiler and Flamebin

clj-memory-meter

Misc projects

  • New release: virgil 0.3.2 - bugfixes.
  • New release: clj-java-decompiler 0.3.7 - bugfixes.

Permalink

Call for Proposals. Feb. 2025 Member Survey

Greetings folks!

Clojurists Together is pleased to announce that we are opening our Q2 2025 funding round for Clojure Open Source Projects. Applications will be accepted through the 17th of March 2025 (midnight Pacific Time). We are looking forward to reviewing your proposals! More information and the application can be found here.

We will be awarding up to $33,000 USD for a total of 5-7 projects. The $2k funding tier is for experimental projects or smaller proposals, whereas the $9k tier is for those that are more established. Projects generally run 3 months, however, the $9K projects can run between 3 and 12 months as needed. We expect projects to start on April 1, 2025.

A BIG THANKS to all our members for your continued support. We also want to encourage you to reach out to your colleagues and companies to join Clojurists Together so that we can fund EVEN MORE great projects throughout the year.

We surveyed our members again in February to find out what types of initiatives they would like us to focus on for this round of funding. Their responses are summarized below. In particular, it was great to see members' feedback relating to how often they used or referred to developers' work we have funded. Also noted that several of you plan to attend Clojure/Conj, Clojure Berlin MeetUps, SciNoj Light, ClojureX, and ReClojure. CLojurists Together will be one of the sponsors of ReClojure in London this May.

Here is more from our February Members' survey.

Usage Feb 2025

platforms in projects feb 25

improve clojure feb 25

improve clojurescript feb 25

What areas of the Clojure and ClojureScript ecosystem need support?

โ€ข โ€œBeginnersโ€ onboarding. I think we need to make a concerted effort to attract new people (not only people without programming experience) and make the initial stages of โ€œgetting into Clojure(Script)โ€ much more guided. Even if that initially means โ€œopinionatedโ€. Once a person has a minimum level of confidence, she can always explore other โ€œopinionated optionsโ€. Otherwise, we risk fragmenting the efforts, and making the learners confused (which can led them to rejecting the language).
โ€ข We need to gain more hype and visibility in the wider software development community
โ€ข Outreach to new developers
โ€ข Learning experience for people who are not senior developers. Build tooling and getting started material for newcomers
โ€ข Data Science - can I make an R-quality chart without Python?
โ€ข Shadow - linting and bus-factor - Shadow is fiendishly complex judging by the rate of questions on Slack, and there is just one expert.
โ€ข ClojureDart
โ€ข New use cases, data analysis, clojure in academia, beginner resources, interop
โ€ข JS Ecosystem interop. How to use stuff like shadcn, tailwind etc. These things should not be so different from how people do it in JS.
โ€ข Scientific Clojure and Clojure for data processing
โ€ข DX tools.
โ€ข Build tooling the introduction of deps has made the ecosystem more difficult to get into
โ€ข The developer experience - it is already good, but it could be so much better. Better, more usable error messages, stack traces and better / easier to use profiling tools and tools for structuring builds
โ€ข The core is stagnant, especially the spec debacle.

What areas of the Clojure and ClojureScript ecosystem are strong?

โ€ข Just a great community
โ€ข Clojure-JVM language maintenance
โ€ข Web development, data processing
โ€ข The language. The tooling is becoming stronger, but more to go I think.
โ€ข Developer Experience Tools for experienced devs
โ€ข Very excited about all the work around the data science scene ie noj, clerk
โ€ข Clojure async, scripting, and web use cases are well covered
โ€ข Core language
โ€ข Build tooling
โ€ข The backwards compatibility and stability of libraries
โ€ข Linting
โ€ข Core libraries for config, web apps, routing, database interaction are all well served
โ€ข In general everything just works and is very stable, canโ€™t wish for a better ecosystem to build on.

Are there any particular libraries, tools, or projects that are important to you that you would like to see supported?

โ€ข Jank (3) Jank seems increasingly important to use all the C++ AI related libs and models.
โ€ข Malli (3)
โ€ข shadow-cljs (3)
โ€ข Everything borkdude is doing (3)
โ€ข Babashka (2)
โ€ข Calva (2)
โ€ข CIDER (2)
โ€ข ClojureDart (2)
โ€ข Clojure LSP (2)
โ€ข Datalevin (2)
โ€ข Reitit (2)
โ€ข SciCloj (2)
โ€ข BB, carmine, Cheshire, clang, clay, clj-kondo, Clojars, clojupyter, clojure.jdbc, Cloverage, Conjure, Datahike, datomic-related, dtype-next, Duct (for deps.edn), Elasticsearch (spandex), fastmath, FlowStorm, Fulcro, girouette, http-kit, honeysql, HugSQL, kindly, Neanderthal, next-jdbc, nippy, noj, parinfer, Pathom, Portal, python-clj, Reagent, Shadow, snitch. NBBAero, squint/cherry. tableplot. tech.ml.dataset. A continuation of oz. Statistical libraries (say brms), uix

โ€ข I would LOVE to see some additional testing and cleanup work on Bifurcanโ€“Iโ€™ve been using it a ton recently and itโ€™s a big step up from the standard Clojure data structures

What would you like to be different in the Clojure community in the next 12 months?

โ€ข That AIโ€™s get better at writing/reviewing Clojure code.
โ€ข I would love to see wider adoption of cljs on node.
โ€ข More cljc portable libraries usable in the JVM, browser, and Dart
โ€ข A plan for growth into new use cases, fields, and user personas
โ€ข Good integrations of Clojure IDEs with LLMs
โ€ข User-friendly developer tools: easy bootstrap, easy frameworks, a starting point to move fast from the start
โ€ข Moldable development
โ€ข Clojure data science tools with paths to reach parity with R and python
โ€ข I would love an online course for free
โ€ข Better support and tutorials around datalog databases beyond datomic
โ€ข Some kind of consensus as to how to create โ€œquickstartโ€ applications for various usecases that will make the Clojure ecosystem easier for new users to approach and for existing Clojurians to โ€œsellโ€ the stack into the teams that they work with. Ruby had / has Rails, Elixir has Phoenix, Node had / has Express, Python had / has Flask - Clojure needs a toolchain to create the same skeletons without departing from the many libraries approach that makes Clojure eventually far more versatile, down the road.
โ€ข Show the broader dev community that choosing Clojure for new projects is viable.
โ€ข We need to gain more hype and visibility in the wider software development community.
โ€ข More outreach, articles and so on to new developers.
โ€ข Improve adoption for non-Clojure users.
โ€ข It would be great to think of how we can grow the Clojure community so that it will be sustainable in the long run. What are some ways we can reach new audiences and bring more people into the community?
โ€ข Iโ€™d like it to be easier for newcomers to Clojure to get started.

Permalink

Clojure Deref (Mar 6, 2025)

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

Libraries and Tools

New releases and tools this week:

  • brew-install 1.12.0.1530 - Clojure CLI

  • docker-nrepl.el - Easily connect CIDER to nREPL servers running in Docker containers

  • action 0.0.9 - Controlled, data oriented state manipulation - Clojure/Script/Dart

  • baldr 1.0.9 - Positive reporting for clojure.test, works for Clojure, ClojureScript, Babashka, and nbb

  • flow-storm-cljs-compiler-plugin 1.0.0-beta2 - A FlowStorm plugin to debug the ClojureScript compiler

  • rpub 0.1.0 - A free open-source CMS written in Clojure

  • babashka 1.12.197 - Native, fast starting Clojure interpreter for scripting

  • squint 0.8.138 - Light-weight ClojureScript dialect

  • clojure-cli-config 2025-03-01 - User aliases and Clojure CLI configuration for deps.edn based projects

  • elin 2025.3.1-alpha - A Clojure development environment for Vim/Neovim and more!, primarily written in Babashka

  • rv 0.0.5 - A Clojure library exploring the application of pure reasoning algorithms

  • replicant 2025.03.02 - A data-driven rendering library for Clojure(Script) that renders hiccup to DOM or to strings

  • clojure-ts-mode 0.2.3 - The next generation Clojure major mode for Emacs, powered by TreeSitter

  • flow-storm-async-flow-plugin 1.0.0-beta3 - A FlowStorm plugin to debug core.async.flow graphs

  • clay 2-beta31.1 - A tiny Clojure tool for dynamic workflow of data visualization and literate programming

  • noj 2-beta9.1 - A clojure framework for data science

  • deps.clj 1.12.0.1530 - A faithful port of the clojure CLI bash script to Clojure

Permalink

Clojure vs. Other Functional Programming Languages: A Quick Comparison

Functional programming has gained momentum in recent years, with several languages
leading the charge. Among them, Clojure stands out for its Lisp-based syntax, dynamic
typing, and seamless JVM interoperability. But how does it compare to other popular
functional programming languages like Haskell, Scala, and Elixir? Letโ€™s dive in.

Clojure vs. Haskell

FeatureClojureHaskell
TypingDynamicStatic (Strongly typed)
SyntaxLisp-based, minimalisticMathematical, more structured
PerformanceGenerally slowerHigh-performance with optimizations
Concurrencycore.async, STMSTM, parallelism
Learning CurveMediumSteep

Key Differences

โ€ข Haskell enforces strict functional purity with a strong static type system, while Clojure offers more flexibility with dynamic typing.

โ€ข Haskellโ€™s laziness can lead to better performance in some cases, whereas Clojureโ€™s JVM integration makes it more practical for real-world applications.

โ€ข Clojure is widely used in web applications and data processing, while Haskell finds its niche in academic and high-assurance systems.

Clojure vs. Scala

FeatureClojureScala
ParadigmPurely functionalHybrid (FP + OOP)
TypingDynamicStatic
PerformanceSlower than ScalaFaster due to optimizations
InteroperabilityExcellent Java integrationSeamless Java interoperability
Use CasesWeb dev, data processingEnterprise applications, big data

Key Differences

โ€ข Scala combines functional and object-oriented paradigms, making it more familiar to Java developers, while Clojure embraces a minimalist, Lisp-style approach.

โ€ข Clojureโ€™s simplicity leads to better code maintainability, but Scalaโ€™s type safety helps catch errors at compile time.

โ€ข Scala is often preferred in large-scale enterprise applications, while Clojure shines in smaller, agile teams focusing on simplicity and expressiveness.

Clojure vs. Elixir

FeatureClojureElixir
Concurrency ModelJVM-based (core.async, STM)BEAM-based (Actor model)
SyntaxLisp-basedRuby-like
PerformanceModerateHigh (optimized for concurrency)
Best ForGeneral-purpose FPDistributed, fault-tolerant systems

Key Differences

โ€ข Elixir runs on the BEAM VM, making it ideal for building distributed and fault-tolerant systems like messaging apps and real-time applications.

โ€ข Clojureโ€™s JVM integration gives it a broader use case across web development, data science, and AI applications.

โ€ข Elixir has a more approachable syntax, especially for developers coming from Ruby, while Clojureโ€™s Lisp-like syntax can be more challenging for newcomers.

Final Thoughts

Clojure, Haskell, Scala, and Elixir each bring unique strengths to the table. If you prioritize simplicity and JVM interoperability, Clojure is a solid choice. Haskell is great for pure functional programming enthusiasts, Scala is ideal for enterprise-level applications, and Elixir is the go-to for highly concurrent, distributed systems. The best choice ultimately depends on your project requirements and personal preferences.

The post Clojure vs. Other Functional Programming Languages: A Quick Comparison appeared first on Flexiana.

Permalink

The sources of software complexity

Our last Apropos was with Alex Engelberg. Check it out. Our next episode is with Paula Gearon on March 11. Please watch us live so you can ask questions.

Have you seen Grokking Simplicity, my book for beginners to functional programming? Please check it out or recommend it to a friend. You can also get it from Manning. Use coupon code TSSIMPLICITY for 50% off.


The sources of software complexity

In No Silver Bullet, Fred Brooks defines the line of discussion around software complexity. He identifies two broad classes of difficulty in software: essential and accidental. The essential is irreducible. You cannot get rid of it because it is inherently part of the task of writing software. The accidental is anything that could in principle be eliminated and you still get working software.

He also identified complexity as an essential difficulty in software. Brooks writes: As software grows in size, it โ€œis not merely a repetition of the same elements in larger sizes, it is necessarily an increase in the number of different elements. In most cases, the elements interact with each other in some nonlinear fashion, and the complexity of the whole increases much more than linearly.โ€

Brooks also defines two similar conjectures which I call the strong and weak conjecture. The strong conjecture is that less than 90% of time is spent on accidental difficulties. Because of this, we will never see an order of magnitude improvement in software development speed.

The weak conjecture is that there wonโ€™t be a single improvement (a silver bullet) that alone accounts for an order of magnitude improvement in software development speed within a decade.

Itโ€™s always puzzled me why he made the weak conjecture. Itโ€™s weaker in two ways. First, thereโ€™s the deadline of one decade. If essence is less than 10%, it shouldnโ€™t matter how long you give it. Itโ€™s in principle impossible to improve by an order of magnitude. Second, he limits it to a single improvement acting alone. This one allows him to weasel out of arguments about two improvements acting in concert, which he does repeatedly in the follow-up essay, โ€˜No Silver Bulletโ€™ Refired.

I believe Brooks was wrong in the strong conjecture. He suffered from a lack of imagination about what we could do with the increases in computer power available to us. We turned a lot of what he called essential difficulties into non-problems. Dan Luu has a pretty insightful essay on how techniques like version control and software testing have done wonders for turning the essential into accidental.

As for the weak conjecture, I think itโ€™s arbitrary and worthless. Who cares how you count improvements, and why a decade?

Check out two podcast episodes I did about the essay: 1 and 2.

In Out of the Tar Pit, Ben Mosely and Fred Marks argue that complexity is the major problem in software development. They redefine essential complexity as that due to the domain (instead of that inherent in software development). Accidental complexity is complexity due to something else, such as from bad language design or poor programming practices.

They identify two major sources of complexity: state and control. State leads to complexity because software systems can get into a bad state. And control leads to complexity because of the ordering of execution. They then try to show how using functional programming and the relational model can eliminate most accidental complexity. Itโ€™s a very good read that demonstrates a lot of first-principles thinking.

However, the division between essential and accidental is fraught with ambiguity. If you can eliminate it, itโ€™s obviously accidental. But if you donโ€™t know how to eliminate it, you canโ€™t really tell if you havenโ€™t found a way to eliminate it yet, or if it is truly essential. Itโ€™s a helpful concept, but not that helpful.

I much prefer to clearly delineate the sources of complexity. Here is an example list of sources of complexity:

  • Domain complexity (ex: rocket science is just hard)

  • Business complexity (ex: the business wants to bill clients based on usage)

  • Architectural/platform complexity (ex: we have to deal with client/server issues on the web)

  • Language complexity (ex: we need the visitor pattern in Java)

  • Code complexity (ex: we introduced an unnecessary indirection)

If we identify a certain problem in our code as coming from the design of the Java language, we can deal with it in two ways. One, we can avoid it by changing languages. If we canโ€™t do that, then we can isolate it. We build indirection to route around the bad design. We add linters to help us steer clear of the bad design. We increase the testing to make sure we arenโ€™t bitten by the complexity.

Itโ€™s not necessary to come up with an exhaustive, indisputable list of sources. Instead, it is important to know that one can identify the source of complexity to the granularity you find useful. We can ask โ€œWhy is the browser always getting out of sync with data on the server? Oh, itโ€™s because weโ€™re on the web and that brings with it some complexities.โ€ After identifying it, we can then isolate it. Add to the list if you need more resolution into your problem.

Complexity is real. And itโ€™s a killer. We should be constantly on the lookout for how to avoid it. And when we canโ€™t avoid it, we should isolate it. Having a list of sources of complexity can help you understand it better. Iโ€™ve presented the list I use. Next week I want to talk about why poor data modeling is a major source of complexity.

What sources do you typically use to classify complexity?

Permalink

How Multiply went from Datomic to XTDB to Rama

โ€œWith databases, the conversation always started with โ€˜what are we able to do?โ€™. I rarely find myself asking what Rama is able to support, and rather โ€˜how?โ€™. The requirements of the application dictate how we utilise the platform, not the other way around. Rama as a tool allows us to think product first, while still delivering highly optimised and scalable features for specific use cases, something that would not have been possible without a much larger team.โ€

William Robinson, engineer at Multiply

Multiply rebuilt their entire backend from scratch in the past year using Rama. Their prior implementation was built with XTDB, and their prototype before that used Datomic.

Multiply is an AI-powered platform for collaboration and co-creation. Expert AI agents plan, communicate with other agents, leverage tools, analyze files, conduct online research, and report back when the work is done. Hereโ€™s a short video introducing the platform.

A few aspects of the application are demanding of the backend. The application is highly interactive, so low-latency reactivity on pretty much anything that changes on the backend is critical. Users create complex workflows that execute asynchronously, with the state of these workflows needing to be managed in a fault-tolerant way. Thereโ€™s also many features which are fully synchronous with user interaction, with a wide variety of indexing and query requirements.

Datomic was appealing for the first version for its immutable data model and expressive Datalog-based query language, attractive and intuitive concepts for most Clojure programmers. They soon switched to XTDB, as even though XTDB is similar to Datomic, its more flexible ways of representing data helped reduce their codebase size.

The problems that led Multiply to switch to Rama were the same ones typically faced with databases. Almost every database, not just Datomic and XTDB but also Postgres, MongoDB, Redis, Cassandra, etc., supports a small set of data models. Most databases support just one. A data model dictates how a database indexes data and the database API is oriented around whatโ€™s possible with that indexing strategy.

The first problem Multiply had was understandability. Squeezing everything into a fixed data model was unnatural, and they had to build lots of โ€œsupport infrastructureโ€ to deal with the impedance mismatches:

โ€œI came to the realisation that we didnโ€™t understand the system weโ€™d built. We understood each part in isolation, but we didnโ€™t have the tools to โ€œseeโ€ the system (through the lens of our domain model and business logic) operating.โ€

Henrik Eneroth, CPO of Multiply

โ€œIt often felt like we were working against the database, and that our efforts went towards designing our product around it.โ€

William Robinson

The lack of understandability made everything take longer, whether fixing bugs or developing new features. This severely impacted their iteration speed, which is particularly harmful for a startup.

The second problem was the difficulty of achieving acceptable performance and fault-tolerance, also common problems when using a database that can only index a limited number of ways:

โ€œWe ran into bottlenecks for running deep live queries. While we found acceptable levels of latency on write, we were unable to get fault tolerance across multiple nodes. We spent many long months on trying to get it to work. In the end, it was a pricey, tiring and failed effort.โ€

William Robinson

Multiply understood the potential of Rama to fundamentally solve these problems as soon as it was announced. Rama being a platform for both storage and computation, with an indexing model based on data structures that supports infinite data models, could let them mold their infrastructure to fit their product rather than continue to contort their product to fit their infrastructure:

โ€œRama seemed like it would remove a lot of the aforementioned support infrastructure that weโ€™d built around XTDB. This was a huge bet of course, but it paid off on removing a ton of the custom plumbing we needed.โ€

Henrik Eneroth

Indexed datastores in Rama are called PStates (โ€œpartitioned stateโ€). Multiply created many PStates to support their application with a wide variety of shapes, many of which are very different than how a database could represent data. PStates are distributed, durable, and replicated, making them suitable for any use case that a database could be used.

Letโ€™s take a look at some of the PStates powering Multiply. The following definitions are used in the PState schemas:

1
2
3
4
5
(def Sub String)
(def AccountID String)
(def Email String)
(def OrgID String)
(def GenID Long)

One of their simplest PStates tracks the account ID for each login subject (e.g. โ€œgoogle-oauth2/12345โ€ is a login subject using google-oauth2 with user ID 12345 for authentication):

1
(declare-pstate account-mb $$subs {Sub AccountID})

This PState is called $$subs (PState names always begin with $$ ) and is equivalent to a distributed key/value database. Its schema is a map from Sub to AccountId .

Another PState tracks information for every registered email:

1
2
3
4
5
6
7
(declare-pstate
ย  account-mb
ย  $$emails
ย  {Email (fixed-keys-schema
ย  ย  ย  ย  ย  ย {:id ย  ย  ย  AccountID
ย  ย  ย  ย  ย  ย  :forename String
ย  ย  ย  ย  ย  ย  :surname ย String})})

This is a โ€œmap of mapsโ€ schema, where the inner maps have a fixed set of keys each with their own schema. This is equivalent to a distributed document database.

Then there are many PStates which index data in ways that no database can. These PStates are finely tuned to exactly whatโ€™s optimal for Multiplyโ€™s use cases. For example, this PState stores LLM responses as tokens are received:

1
2
3
4
5
6
(declare-pstate
ย  gen-s
ย  $$streams
ย  {OrgID (map-schema GenID
ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย (vector-schema Object {:subindex? true})
ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย {:subindex? true})})

Every organization can have an arbitrary number of LLM requests, and each LLM request can receive an arbitrary number of tokens. The inner map-schema tracks all the LLM requests for an organization, and the inner vector-schema is the list of response tokens for that LLM request. This PState uses the :subindex? flag, which tells Rama that value should have its elements indexed individually instead of the whole value being serialized as a whole. This lets those nested data structures be of huge size while still being able to read and write to them quickly.

This PState can handle many queries in less than one millisecond: get the number of LLM requests for an org, get a range of LLM requests for an org, get the number of tokens streamed in for a particular LLM request, get a range of tokens for a particular LLM request, and many more. Because Rama has a generic way for doing queries of any complexity on PStates of any structure (paths), all these queries are tiny amounts of code to implement.

This PState is highly specific to Multiplyโ€™s use cases. Multiply was able to structure all their PStates to match their domain model exactly, including making use of the freedom that Rama gives to use any types they want. Values within PStates can be primitive types, Clojure defrecordโ€™s, or anything else. They donโ€™t need any adapters for their datastore to manage the mapping from their domain model to the datastore, as is typical with databases (e.g. ORMs).

Finally, since Rama applications are based on event sourcing, with all data coming in on distributed logs called โ€œdepotsโ€, Multiply always has the flexibility to recompute PStates from scratch if they need to aggregate or index it in new ways in the future.

Multiply had this to say about the newfound power Rama gave them:

"Rama put us back in control, by writing code that can express what would previously require us to deploy an entirely new service that we probably would not fully understand. Its abstractions allow us to tap into the power of a small team, by expressing architecture through code, and to focus on business logic instead.

Itโ€™s trivial to write custom PStates to support certain features or views, contrary to having convoluted queries or transformations. โ€˜This could just be a PStateโ€™ is something we exclaim quite a bit. Itโ€™s also nice to not have to clutter existing PStates."

William Robinson

All the intricacies of their application could be expressed naturally with Ramaโ€™s dataflow API for reacting to events as they come in. Whereas before business logic was spread across a web server and a custom background worker, with Rama they were able to express all their logic โ€“ย both synchronous and asynchronous โ€“ย in a unified system. Besides how much this simplified thinking about the system, it eliminated all the work to orchestrate the deployment of multiple systems for storage and computation.

Rama also enabled them to build things that were not possible with XTDB and would have been huge efforts without Rama:

โ€œThe LLM task runner would not have been possible with XTDB. Without Rama, weโ€™d need more than one database. Thereโ€™s a great deal of work we havenโ€™t had to do for the capabilities that we now have with Rama. Itโ€™s easy to have the cake and eat it too with Rama.โ€

Henrik Eneroth

Multiplyโ€™s implementation is a single module with:

They run their application on five nodes with a replication factor of two, deployed to Fly.io performance-4x machines.

The depots separate incoming events according to the entities they affect. For example, one depot is for changes to accounts, another depot is for changes to organizations, another depot is for changes to collaborative spaces, and so on.

Their frontend uses Rama proxies heavily for reactive queries, which is the basis of how they enable their application to be so interactive.

Their Rama deployment required minimal DevOps work:

The DevOps and backup/restore experience with Rama is truly next level. Compared to my experience in running other data systems, the complexity of setting up and running Rama in production is much lower. And due to the power and flexibility of the programming model plus the tremendous horizontal scalability, you probably donโ€™t need to run any other database, which simplifies things even more.

Oskar Boรซthius Lissheim, CTO of Multiply

Deployment is handled completely by Ramaโ€™s built-in and fault-tolerant facilities to launch, update, and scale modules. Rama has comprehensive monitoring built-in, which Multiply augmented with basic node telemetry to track things like disk space and CPU load. Ramaโ€™s integrated backups feature made it trivial to keep all state continuously backed up to S3.

Rama multiplied Multiplyโ€™s capabilities and productivity. Rama attracted Multiply not for its scalability, but for its ability to fundamentally reduce complexity. That their application will scale to any read/write load in the future is a big bonus.

On March 18th, Rama will be free for production use up to two nodes and will be downloadable from our website. In the meantime, you can learn Rama by reading the tutorial and playing with the current public build.

Permalink

Radar Trends to Watch: March 2025

Anthropicโ€™s announcement of Claude 3.7 Sonnet notwithstanding, the breakneck pace of major AI announcements seemed to slow down through February. That gave us some time to look at some other topics. Two important posts about programming appeared: Salvatore Sanfilippoโ€™s โ€œWe Are Destroying Softwareโ€ and Rob Pikeโ€™s slide deck โ€œOn Bloat.โ€ Theyโ€™re unsurprisingly similar. Neither mentions AI; both address the question of why our hardware is getting faster and faster but our applications arenโ€™t. Weโ€™ve also noted the return of Pebble, the first smart watch, and an AI-driven table lamp from Apple Research that looks like it came from Pixarโ€™s logo. Fun, perhaps, but donโ€™t look for it in Apple Stores.

Artificial Intelligence

  • Anthropic has released Claude 3.7 Sonnet, the companyโ€™s first reasoning model. Itโ€™s a โ€œhybrid modelโ€; you can tell it whether you want to enable its reasoning capability. You can also control its thinking โ€œbudgetโ€ by limiting the number of tokens it generates for the reasoning process.
  • The Computer Agent Arena is a platform for crowdsourced agent testing. It allows anyone to run an agent using two different AI models, observe what the agent is doing, and rate the results. Results are summarized on a leaderboard; right now, Claude 3.5 Sonnet is at the top.
  • Google is developing a โ€œco-scientistโ€ that suggests hypotheses for scientists to investigate. The hypotheses are based on the scientistโ€™s goals, ideas, and past research. The companyโ€™s looking for researchers to help with testing.
  • GitHub has upgraded agent mode for Copilot. It will now iterate on buggy code until it delivers correct results, and can add new subtasks to the original if theyโ€™re needed to accomplish the userโ€™s goal.
  • Open-R1 is a new project that intends to create a fully open reproduction of DeepSeek R1. In addition to code and weights, this project will release all tools and synthetic data used to train the model.
  • Moshi is a new conversational (speech-to-speech) language model that is constantly listening and can handle interjections like โ€œuh huhโ€ without getting confused.
  • Codename Goose is a new open source framework for developing agentic AI applications. It uses Anthropicโ€™s Model Context Protocol for communicating with systems that have data, and can discover new data sources on the fly.
  • The University of Surrey will be building a language model for sign language. One focus will be translating between spoken language and sign language. The goal is to ensure that the deaf community isnโ€™t left behind by the explosion of AI tools.
  • Galileo is an agentic toolset for detecting when an AI model is hallucinating. Itโ€™s particularly important for agentic systems, where an error by one agent leads to misbehavior by others downstream.
  • A group of researchers released s1, a 32B reasoning model with near state-of-the-art performance. s1 cost only $6 to train. A very small set of training data (only 1,000 reasoning samples) proved sufficient when the model was forced to take extra time for reasoning.
  • Some researchers published How to Scale Your Model, a book on how to scale large language models. The book is apparently internal documentation from Google DeepMind.
  • OpenAI has released o3-mini, a small and cost-efficient language model based on its (still unreleased) o3 reasoning model.
  • Anthropic has deployed its Constitutional Classifier for adversarial testing by the public. The classifier is a system that protects Claude models from jailbreaks and attempts to get Claude to answer questions that arenโ€™t allowed. Early results look very good.
  • The lesson to learn from DeepSeek R1 is that, given a good foundation model, itโ€™s less difficult than many thought to develop a reasoning model. In the coming months, expect many open alternatives.
  • OpenAI has introduced DeepResearch, an application based on its o3 model that claims the ability to synthesize large amounts of information and perform multistep research tasks.
  • Sam Altman has acknowledged that OpenAI is on the โ€œwrong side of historyโ€ as far as open source AI but also said that addressing the issues was not a high priority.
  • Alibaba has launched Qwen2.5-Max, another large language model with performance on the same level as GPT-4 and Claude 3.5 Sonnet. It can be accessed through Qwen Chat or Alibabaโ€™s cloud.
  • Transformer Lab is a tool for experimenting with, training, fine-tuning, and programming LLM models locally. Itโ€™s still installing, but it looks like Ollama on steroids.
  • smolGPT is โ€œa minimal PyTorch implementation for training your own small LLM from scratch.โ€
  • Yes, Microsoft is complaining that DeepSeek used OpenAI to generate synthetic training data. Those objections didnโ€™t stop it from making DeepSeek available on Azure.
  • Two composers collaborated with Googleโ€™s Gemini to create The Twin Paradox, a work for a classical symphony orchestra.
  • Alibaba has released two โ€œcheckpointsโ€ to its models, Qwen2.5-7B-Instruct-1M and Qwen2.5-14B-Instruct-1M. These models have large 1M-token context windows. Alibaba has also open-sourced its inference framework, which the company claims is three to seven times faster.
  • TinyZero reproduces DeepSeekโ€™s R1 Zero, a reasoning model with 3B parameters. Training TinyZero cost under US$30. You could download TinyZero, but you could also make your own for less than the cost of an evening out. Do we need expensive models?

Programming

  • Tanagram is promising a toolset for helping developers understand and work with complex codebases. So far, there are only demos, but it sounds interesting.
  • Harper Reed describes his workflow for programming with AI. Developing a workflow is essential to using AI effectively, and Harper has given the most thorough description weโ€™ve seen.
  • Like Linux, Ruby on Rails can run in the browser. This hack uses WebAssembly.
  • Linux booting inside a PDF in Chrome. PDF implementations support JavaScript; C can be compiled into a subset of JavaScript (asm.js), which means that a RISC-V emulator can be compiled to JavaScript and run in a PDF in the browser, which then runs Linux. An amazing hack.
  • OCR4all provides free and open source optical character recognition software. Should you need it.
  • Why does software run no faster than it did 20 or 30 years ago, despite much faster computers? Rob Pike has some thoughts on controlling bloat.
  • As the name implies, Architectural Decision Records (ADRs) capture a decision about software architecture and the reason for the decision. All too frequently, this information isnโ€™t captured. It is likely to become more important in the era of AI-assisted software development.
  • Jank is a new general purpose programming language. Itโ€™s a dialect of Clojure that incorporates ideas from many other languages, including C++ and Rust, and is built on top of the LLVM.
  • Hereโ€™s a set of patterns for building real-time features into applications.
  • Salvatore โ€œantirezโ€ Sanfilippoโ€™s post, โ€œWe Are Destroying Software,โ€ is a must-read. (It says nothing about AI.) It starts โ€œWe are destroying software by no longer taking complexity into account.โ€
  • Script is a Go library that makes it possible to do shell-like programming in Go. Its biggest contribution is the ability to create pipes; it also has Go functions that are similar to grep, find, head, tail, and other common shell commands.

Security

  • Threat actors aligned with Russia are targeting Signal, the secure messaging application, with phishing attacks that link usersโ€™ accounts to hostile devices. One group sends QR codes that look legitimate but link to a device under their control; another impersonates an application used by Ukraineโ€™s military. The best protection is to update to the latest version of Signal.
  • Two new vulnerabilities in OpenSSH have been found. One exposes OpenSSH servers to man-in-the-middle attacks; the other can lead to denial-of-service attacks. An update has been released; install it.
  • DarkMind is a new attack against reasoning language models. Itโ€™s possible to build custom applications (like those in the GPT Store) with โ€œhidden triggersโ€ that modify the reasoning process.
  • A new kind of supply chain attack involves obtaining abandoned AWS S3 buckets that still hold libraries that are frequently downloaded. The new owner can insert malware into the libraries; the original owner, who abandoned the bucket, canโ€™t patch the corrupted libraries.
  • Security is blocking AI adoption, particularly in heavily regulated industries. Thatโ€™s understandable; many of the questions we ask of secure systems canโ€™t be adequately answered for AI.
  • Microsoftโ€™s AI Red Team has published Lessons from Red Teaming 100 Generative AI Products. Itโ€™s essential reading for anyone interested in building a secure AI system.
  • AI is being used to submit fake feature requests and bug reports on open source projects. Many of these may be inadvertent, but regardless of cause, itโ€™s generating problems for software maintainers.
  • Linux has a number of tools for detecting rootkits and other malware. Chkrootkit and LMD (Linux Malware Detect) are worth your attention.
  • Time Bandit is a new jailbreak for the GPT models. The attack causes the model to lose track of past, present, and future. Essentially, you ask GPT how someone in the past would do something that can only be done in the present. Itโ€™s unclear whether this attack works on other models.
  • When the price of bitcoin goes up, so does the frequency of cryptojacking: hijacking computers to form crypto-mining botnets. Itโ€™s claimed that for every dollar of crypto thatโ€™s mined, the victim incurs $53 in cloud costs.
  • A new backdoor to VPNs has been discovered in the wild, giving attackers access to corporate networks. These backdoors stay dormant until they are triggered by a specially constructed โ€œmagic packet,โ€ making them difficult to detect.

Web

  • As more people ask AI for product recommendations, marketers will need to optimize product perception by language models. Does LLMO replace SEO? Optimizing for an LLM may be the next generation of SEO.
  • This article tells you how to opt out of Gemini features in Gmail and other Google Workspace applications. Itโ€™s possible to disable Gemini selectively. Unfortunately, it requires you to have access to the administratorโ€™s console.
  • JavaScriptโ€™s Temporal object is starting to appear in browsers! Temporal is a replacement for the inadequate Date object. It allows programmers to work effectively with dates and times.
  • Marginalia is an open source search engine that prioritizes noncommercial resorts.

Quantum Computing

  • Microsoft has created a topological qubit on a new quantum chip. While its chip currently has only 8 qubits, Microsoft claims it can scale to millions of qubits. Putting this many qubits on a chip would go a long way to solving the problem of moving quantum data between chips.
  • Canadian startup Xanadu has built a quantum computer using photonics. It currently has 12 qubits, but the company believes it can scale to larger systems.

Robotics

Gadgets

  • Pebble returns? Remember the crowdfunded Pebble smartwatch that was available long before Appleโ€™s Watch? Itโ€™s coming backโ€”maybe. And it will be hackable.
  • Something we all need: An engineering team at Apple developed an AI-driven table lamp. Not available in an Apple Store near you.

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.