Holy Dev Newsletter February 2024

Welcome to the Holy Dev newsletter, which brings you gems I found on the web, updates from my blog, and a few scattered thoughts. You can get the next one into your mailbox if you subscribe.

What is happening

I keep a dialog with Thomas about Wolframite. We haven’t done much lately, but have planned the way towards v1. I have been primarily busy with Rama, finishing reading the docs and doing another round of learn-by-coding, as you can read in Hands on Rama, day 2: Rewrite CAS, finish basic C(R)UD. (You can catch the whole series here.) I went a little overboard and submitted a Rama talk to JavaZone, a big and popular conference here in Oslo. 🤞

I have also been digging into efficient and relevant text search, as I have been working with a suggestion API here at Ardoq. I’ve spent too much time with 📕 Introduction to Information Retrieval (free online, 2008), which is an excellent resource. I’ve learned about dictionaries, inverted indices (term → document), k-grams for wildcard queries, smart techniques for intersections of lists etc. I got me thinking about how limited I am with Postgres, and how could I leverage Rama’s ordered set for a better solution (until it gets 100% built-in support for rich text search). I’ve also learned finally what vector embeddings are, and wondered whether I could leverage those…​

Gems from the world wide web

👓 BrunoBonacci/where: Human readable predicate functions and `filter` best companion. [clojure, library, data, productivity]
Neat little library for more conscise creating and combining of predicates, e.g. for filtering maps. The invocation api is trivial: (where extractor comparator target). F.ex. [:country = "Italy"] extracts the value of :country and compares it with = against the target of "Italy". All comparators are nil-safe and the string comparators have a case-insensitive variant. See more examples.
Example: In Clojure: (->> xs (filter #(and (= (:country %) "Italy) (= (:country "USA")))). Where: (->> xs (filter (where [:or [:country = "Italy"] [:country = "USA"]])).

👓 George Fairbanks - IEEE Software - The Pragmatic Designer: Principle of Least Expressiveness [opinion, software eevelopment]
An inspiring article. P.L.E.: "When programming a component, the right computation model for the component is the least expressive model that results in a natural program [i.e. the solution is closer to the nature of the problem]." I.e. use the simplest construct suitable for the problem. Simplicity ordering: constant < variable < table < state machine < a Turing-complete programming language. E.g. use a lookup table if enough instead of an if-elses.
Why? It is clearer what the code can do (ie.e. paradoxically, using less expressive code expresses better the intent :)). It is more difficult to break its design integrity. "The idea is that by applying the PLE, we can write clearer code that better reveals how we think about the problem." "[± using] less expressive representation[...] stimulates a cascade of reasoning that shakes out an improved understanding." "we reveal our thoughts to readers in the least complicated way". While a Turing-complete language is rather vague, we want to use something more precise.

👓 Tailwind vs Semantic CSS [css, opinion, webdev]
This study compares two websites with identical design, one built with Tailwind vs the same site with semantic CSS. Tailwind needs significantly more coding because you are completely lacking the power of CSS: the way it cascades and the richness of the selectors. You are forced to wrap divs inside divs inside divs and fill the elements with Tailwind-specific class syntax. (Some 7-10x more - though it is not shared how much the external CSS of semantic css wieghts.)
Seems to be written by NueJS people, comparing their solution to Tailwind with NextJS, so it may not be 100% objective.

👓 Tidy First? in One Page [productivty, software development, opinion]
Kent Beck summarizes his upcoming booklet The Surprise Factory, which explains to executives why care about software design. Summary:

This book will help you, an executive in a software-based business, create a Surprise Factory, a team frequently delivering pleasant surprises. Sound unlikely? I hope you’ll stick with me. The missing ingredient is software design (aka "paying down technical debt" aka "refactoring"), but in the right proportions at the right times. If you only have a minute to read, here are the classic software design blunders: * Thou shalt not rewrite the whole system. * Thou shalt not pause delivery of features for an extended period to fix the design. * Thou shalt not delay the first feature to "get the design right". * Thou shalt not thoughtlessly demand the next feature immediately upon completion of the last. Why not, & what to do instead, are the topic of this briefing. Mixing software design into development isn’t one of those monochrome problems. Your team needs your judgement, perspective, & vision to balance today’s needs & tomorrow’s opportunities.

👓 Would you rather structurally avoid problems or detect them? [software development, criticism]
Great food for thought. Do we choose unnecessarily complex solutions and then pile on tools to make them manageable, instead of adopting simpler architectures? For example, is Rust with its (costly) memory safety truly an improvement over C, or does it only make the mess caused by bad design choices manageable, i.e. could we instead make simpler design choices that make Rust unnecessary? I.e. "[..] memory management needs Rust because of lousy architecture." The Rust borrow checker is a complex solution, which allows for complex code. Couldn't we rather write simpler code? Erlang's garbage collection is far simpler than JVM's because it made a simpler architecture choice of each process having its own, isolated heap.

👓 Introduction to Information Retrieval [book]
A free book (2008) by Stanford on information retrieval and text search. Good source to for a refresh of text search algorithms, indices, and data structures. F.ex. leading/trailing wildcard queries (use regular/reverse B-tree to get to terms, then an inverted index for term -> documents). Other interesting topics include Scoring, term weighting and the vector space model and Index compression.

👓 flex is a reactive signal library for Clojure(Script) [webdev, ClojureScript, library]
flex is a library for building computations using signals in Clojure(Script) - an approach made popular by SolidJS and others, recently. A "signal" is a reactive data holder, which updates when any of its dependencies change.

👓 JEP 453: Structured Concurrency (Preview) [java, standard, concurrency]
A proposal (started 4/2023) for a new JVM API for structured concurrency, which "treats groups of related tasks running in different threads as a single unit of work, thereby streamlining error handling and cancellation, improving reliability, and enhancing observability." Goals: Help eliminate common risks arising from cancellation and shutdown, such as thread leaks and cancellation delays. Improve the observability of concurrent code. Read its "Motivation" section for a concrete example where this helps.

👓 En uke med Tailwind CSS [webdev, css, criticism]
A short and sweet article (in Norwegian) about cons and pros of Tailwind CSS, something I have wondered about recently. The advantage seems to be that T. gives you higher-level, composable utilities, such as md:flex to set element to display: flex on medium+ display sizes. And it provides tools to ensure you end up only with the CSS you actually need. The key disadvantage is that you end up with tons of classes on all styled elements, and a lot of repetition. The proposed solution to that is to use instead reusable semantic classes such as button-primary - something that the Tailwind plugin DaisyUI offers.
Interestingly, Tailwind stands in a strong opposition to semantic CSS. The author derives that the there are different definition of "semantic" and you need to pick the right granularity - i.e. not the business domain (e.g. .shopping-cart), but the UI domain (.button-primary and other UI components). Good stuff!

👓 People over process [productivty, opinion, software development]
A long and thoughtful article about software development methodologies and productivity. The key thesis is that you can't achieve productivity by adopting a process, whether Scrum, XP, or anything else. The only thing that works is when leadership truly embraces continuous improvement, and enables you to arrive to a process that works for you, and iterate on it. In other words, it is about people.

👓 BQN: finally, an APL for your flying saucer [programming languages]
A modern successor to APL, the array programming language I learned about when dipping my toes in data science. Array-oriented programming is quite different from mainstream approaches, and thus worth studying. BQN has learned from two generations of predecessors, but also from languages outside of the array family. As the author writes, "I think BQN is a good choice for learning and enjoying array programming, scripting, prototyping, and number crunching at a single-CPU scale." Moreover, you can use some crazy characters in your code! Certainly something I'd like to do!
Side note: Array languages for Clojurians (thanks, Teodor!) essentially argues that Clojure can be shaped to do everything an array-oriented language can, though perhaps at times less efficiently.

👓 New Library: clj-reload [clojure, library, productivity]
A new alternative to tools.namespace (comparison), which tries to be smarter:

  • you can have per-ns unload-hook (instead of a single, global one)
  • it returns the unloaded and loaded namespaces as data
  • you can protect selected def(|n|type|record|protocol) from reloading, instead of just whole namespaces (^:clj-reload/keep meta). defonce is protected automatically. It has also the keep-methods multimethod.
  • it only reloads ns that have been loaded, and only if they changed, while t.ns. reloads everything

👓 JUXT Blog: URI Templates [webdev, standard, library]
A useful reminder about the largely unknown power and potential usefulness of URI templates. "URI Template supports more sophisticated features, such as embedding lists and maps ...".
Also, the mentioned Reap library "for decoding and encoding strings used by web protocols," seems useful. It uses parser combinators and basic "parsing expression grammers" to make translating the grammar from a standard trivial, and exposes rfc<num>.clj with high-level fns, such as compile-uri-template and make-uri / match-uri.

🎥 lambdaisland/launchpad, the ultimate dev env & repl launcher
Great walkthrough of lambdaisland/launchpad, the ultimate dev env & repl launcher. Highlights: integrates deps.edn and .env with local overrides for both, picks up changes to either automatically (i.e. adding a new dependency to the classpath of a running repl). Support for Portal, shadow-cljs, starting any other process (shadow build, a Docker stack, ...) or running commands before/after. Enable deps aliases at runtime. Custom watchers - run a fn when a file changes. Monorepo support. May run user/go after start. This tool looks very useful and I will certainly give it a try!

👓 Semantic Image Search with Clojure [ai, learning, clojure]
Are you also constantly hearing about vector embeddings, vector databases (e.g. usearch), and how they can be used e.g. to find an image matching a textual prompt? Adrian provides a great explanation.
See usearch.clj (vectordb wrapper), clip.clj (clj wrapper for a neural network model for computing embeddings -> OpenClip).

👓 High Level Overview of the Convex Decentralised Network from a Clojure Developer's perspective [web3, blockchain, clojure]
Convex, the "Decentralised Engine for Open Economic Systems," is a modern alternative to blockchains such as Ethereum for building decentralized apps (dApps), designed to handle the scale of Visa at much lower energy consumption that traditional blockchains. Moreover, it uses a Clojure-inspired Convex Lisp for its smart contracts a.k.a. actors. Instead of a traditional, linear blockchain, it combines a lattice-based structure with Conflict-Free Replicated Data Types (CRDTs) => more efficient data organization and access. Clojure lib. Interestingly, it is reportedly suitable for creating decentralized, real-time, multi-player games - something you'd never do on a blockchain. It could also be used to create an interactive dApp, such as a social network.
This article introduces Convex, its features and future, and compares its performance with Ethereum, though the authors note that the definition of a "transaction" varies widely, and Convex is geared towards more complex ones. From the conclusion: "Convex, with its lattice technology, integration with Clojure, and focus on functional programming, represents a significant step forward in the decentralized technology space. It offers developers not just a new technology but a new philosophy for building secure, efficient, and scalable decentralized applications."
I would absolutely look into Convex, were I ever to build a decentralized app. (Though I'd be wary of security and fundamental brokenness until proven otherwise, as I am with all blockchains. )

🎥 Jack Rusher's thought-inspiring talk Stop Writing Dead Programs (transcript here). Key message: Don't preserve practices that...
Jack Rusher's thought-inspiring talk Stop Writing Dead Programs (transcript here). Key message: Don't preserve practices that have no rational basis beyond being historical - which we do a lot. Such as 80 char width (from punch cards). Jack takes us on an excursion into the history of computing and across the landscape of programming languages (featuring Clojure, Erlang, Forth, Smalltalk, and many others).
Summary: "live" programs enable you to interact with, explore, debug, and change the code at run-time. "Dead" programs have the batch thinking / compile/run cycle. Also, we have many opportunities to discover more productive practices.
"Docker shouldn't exist. It exists only because everything else is so terribly complicated that they added another layer of complexity to make it work." 🤣
Jack talks about many things in programming languages that have nor really evolved much - seeing program is a static artifact, expecting it / optimizing for it to run from start to finish (vs. the long-lived servers we actually write). Few languages help with managing state over time from multiple sources. Program representation is still just primitive text, no leveraging our powerful visual cortex (pictures, tables, ...). The lack of interactive programming [..] the only kind of programming we should really be doing. A discussion of types (loves Haskell, hasn't really seen fewer bugs thanks to types).
Programming is actually a design discipline - you figure out what you're building as you build it. And all software is continuous change. (Which has consequences for types and any specs.) => debuggability is more important than correctness by construction. "I would say that actually most programming is debugging." Yet "dead coding languages" don't help much. In Common Lisp, Clojure I can attach to a running process, inspect anything, change anything [my words]. The Glamorous toolkit (for Smalltalk?) is built to give you great visibility into your running program.
Other topics

👓 Shoelace: A forward-thinking library of web components. [webdev, library]
An open-source library of framework agnostic Web Components, with localization, dark theme, React support. Components list.


Thank you for reading!

Tags: newsletter

Copyright © 2024 Jakub Holý
Powered by Cryogen
Theme by KingMob