diff --git a/docs/2024/05/14/clojure-structured-concurrency-and-scoped-values.html b/docs/2024/05/14/clojure-structured-concurrency-and-scoped-values.html new file mode 100644 index 00000000..1c22dbeb --- /dev/null +++ b/docs/2024/05/14/clojure-structured-concurrency-and-scoped-values.html @@ -0,0 +1,227 @@ + +Clojure: structured concurrency and scoped values

Clojure: structured concurrency and scoped values


In this post we'll explore some useful tools for making working with virtual threads easier. Structured concurrency helps eliminate common problems like thread leaks and cancellation delays. Scoped values let you extend parent thread based context to child threads so you can treat a group of threads as a single unit of work with the same shared context.

Enable preview

Structured concurrency and scoped values are available in java 21 as preview features, so we'll need to enable preview:

{:paths ["src"]
+ :deps {org.clojure/clojure {:mvn/version "1.12.0-alpha11"}}
+ :aliases
+{:dev {:jvm-opts ["--enable-preview"]}}}
+

Example code

We'll be implementing our own version of pmap as it has a clear thread hierarchy which is exactly the sort of place both structured concurrency and scoped values are useful:

(ns server.core
+  (:refer-clojure :exclude [pmap])
+  (:import
+   (java.util.concurrent
+     ExecutorService
+     Executors
+     Callable)))
+
+(defonce executor
+  (Executors/newVirtualThreadPerTaskExecutor))
+
+(defn pmap [f coll]
+  (->> (mapv (fn [x] (ExecutorService/.submit executor
+                       ;; More than one matching method found: submit
+                       ;; So we need to type hint Callable
+                       ^Callable (fn [] (f x))))
+         coll)
+    (mapv deref)))
+

Let's run this code and make one of the tasks cause an exception:

(pmap (fn [x]
+        (let [result (inc x)]
+          (Thread/sleep 50) ;; simulate some io
+          (print (str "complete " result "\n"))
+          result))
+  [1 2 "3" 4 5 6])
+
+=> Error printing return value (ClassCastException)
+at clojure.lang.Numbers/inc (Numbers.java:139).
+class java.lang.String cannot be cast to class
+java.lang.Number (java.lang.String and java.lang.Number
+are in module java.base of loader 'bootstrap')
+
+complete 7
+complete 2
+complete 5
+complete 4
+complete 6
+

Despite one of the tasks causing an exception all the other tasks keep running and complete. This might not be the behaviour we want, particularly if we require all tasks to succeed.

This is where structured concurrency comes in.

Simplify concurrent programming by introducing an API for structured concurrency. Structured concurrency 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.

  • JEP 462

Structured Task Scope

First let's import the classes we will need from StructuredTaskScope:

(ns server.core
+  (:refer-clojure :exclude [pmap])
+  (:import
+   (java.util.concurrent
++    StructuredTaskScope
++    StructuredTaskScope$Subtask
++    StructuredTaskScope$ShutdownOnFailure
++    StructuredTaskScope$ShutdownOnSuccess
+     ExecutorService
+     Executors
+     Callable)))
+

When dealing with concurrent subtasks it is common to use short-circuiting patterns to avoid doing unnecessary work. Currently, StructuredTaskScope provides two shutdown policies ShutdownOnFailure and ShutdownOnSuccess. These policies shut down the scope when the first subtask fails or succeeds, respectively.

We're going to explore the ShutdownOnFailure shutdown policy first.

Let's redefine our pmap function:

(defn pmap [f coll]
+  (with-open [scope (StructuredTaskScope$ShutdownOnFailure/new)]
+    (let [r (mapv (fn [x]
+                    (StructuredTaskScope/.fork scope
+                      (fn [] (f x))))
+              coll)]
+      ;; join subtasks and propagate errors
+      (.. scope join throwIfFailed)
+      ;; fork returns a Subtask/Supplier not a future
+      (mapv StructuredTaskScope$Subtask/.get r))))
+

Then run this new version with one task causing an exception:

(pmap (fn [x]
+        (let [result (inc x)]
+          (Thread/sleep 50)
+          (print (str "complete " result "\n"))
+          result))
+  [1 2 "3" 4 5 6])
+  
+=> Error printing return value (ClassCastException)
+at clojure.lang.Numbers/inc (Numbers.java:139).
+class java.lang.String cannot be cast to class
+java.lang.Number (java.lang.String and java.lang.Number
+are in module java.base of loader 'bootstrap')
+

As you can see the other threads are shutdown before they run/complete. Note: this depends on execution order and task completion time. Some threads might complete before the exception occurs.

Next lets look at the ShutdownOnSuccess shutdown policy. This policy works well in a situation where you only care about one of the results. For example reaching out to three data providers that provide the same data (for redundancy).

We are going to implement a function called alts that will take the first completed task from a sequence of tasks being executed in parallel. Only failing if all tasks fail.

(defn alts [f coll]
+  (with-open [scope (StructuredTaskScope$ShutdownOnSuccess/new)]
+    (run! (fn [x]
+            (StructuredTaskScope/.fork scope (fn [] (f x))))
+      coll)
+    ;; Throws if none of the subtasks completed successfully
+    (.. scope join result)))
+

Let's run alts and make one of the tasks cause an exception:

(alts (fn [x]
+        (let [result (inc x)]
+          (Thread/sleep 100)
+          (print (str "complete " result "\n"))
+          result))
+  [1 2 "3" 4 5 6])
+  
+=> 
+complete 2
+complete 4
+
+2
+

We can see two of the tasks manage to complete, the rest are shutdown and only one result is returned.

Structured concurrency is a really nice addition to Java. It's great for automatically handling thread cancellation which can help keep latency down and avoid thread leaks in the case of error.

That being said it's not a natural fit for all use cases. Sometimes you do want unstructured concurrency, like in my previous post on Clojure: managing throughput with virtual threads where upmap produces tasks in one thread and consumes their results in another.

Something I haven't covered but plan on covering in a future post is that StructuredTaskScope can be extended to implement your own shutdown policies.

Dynamic var binding conveyance

Before we get on to scoped values lets explore Clojure's existing mechanism for thread bound state: dynamic vars. Dynamic vars implement a nice feature called binding conveyance which means thread context gets passed to futures and agents spawned by the parent thread. However, because StructuredTaskScope returns StructuredTaskScope$Subtask/Supplier and not a future we don't get binding conveyance automatically:

(def ^:dynamic *inc-amount* nil)
+
+(binding [*inc-amount* 3]
+  (pmap (fn [x]
+          (let [result (+ x *inc-amount*)]
+            (Thread/sleep 50)
+            (print (str "complete " result "\n"))
+            result))
+    [1 2 3 4 5 6]))
+    
+=> Execution error (NullPointerException) 
+at server.core/eval3782$fn (REPL:6).
+Cannot invoke "Object.getClass()" because "x" is null
+

The task threads do not inherit the value of the *inc-aomunt* binding so we get an error. Thankfully, this is easy to fix with the bound-fn* function. A higher order function that transfers the current bindings to the new thread:

(binding [*inc-amount* 3]
+  (pmap
++   (bound-fn*
+      (fn [x]
+        (let [result (+ x *inc-amount*)]
+          (Thread/sleep 50)
+          (print (str "complete " result "\n"))
+          result)))
+    [1 2 3 4 5 6]))
+    
+=> complete 9
+complete 6
+complete 7
+complete 5
+complete 4
+complete 8
+
+[4 5 6 7 8 9]
+

Binding conveyance now works as we would expect.

Scoped Values

This brings us to scoped values. These are similar to Clojure's dynamic vars and Java's thread-local variables but designed for use with virtual threads.

Scoped values, values that may be safely and efficiently shared to methods without using method parameters. They are preferred to thread-local variables, especially when using large numbers of virtual threads. This is a preview API.

  • JEP 446

With the following stated goals:

Goals

  • Ease of use — Provide a programming model to share data both within a thread >and with child threads, so as to simplify reasoning about data flow.

  • Comprehensibility — Make the lifetime of shared data visible from the syntactic >structure of code.

  • Robustness — Ensure that data shared by a caller can be retrieved only by legitimate callees.

  • Performance — Allow shared data to be immutable so as to allow sharing by a large number of threads, and to enable runtime optimizations.

First let's import the classes we will need from ScopedValue:

(ns server.core
+  (:refer-clojure :exclude [pmap])
++ (java.lang ScopedValue)
+  (:import
+   (java.util.concurrent
+     StructuredTaskScope
+     StructuredTaskScope$Subtask
+     StructuredTaskScope$ShutdownOnFailure
+     StructuredTaskScope$ShutdownOnSuccess
+     ExecutorService
+     Executors
+     Callable)))
+

Scoped values have conveyance built in as this is the behaviour that makes the most sense with hierarchical tasks:

Subtasks forked in a scope inherit ScopedValue bindings (JEP 446). If a scope's owner reads a value from a bound ScopedValue then each subtask will read the same value.

  • JEP 462

Let's see how to use a single scoped value:

(def scoped-inc-amount (ScopedValue/newInstance))
+
+(ScopedValue/getWhere scoped-inc-amount 3
+  (delay 
+    (pmap (fn [x]
+            (let [result (+ x (ScopedValue/.get scoped-inc-amount))]
+              (Thread/sleep 50)
+              (print (str "complete " result "\n"))
+              result))
+      [1 2 3 4 5 6])))
+
+=> complete 4
+complete 6
+complete 9
+complete 8
+complete 5
+complete 7
+
+[4 5 6 7 8 9]
+

It's worth pointing out the use of delay to satisfy the Supplier interface. This is a recent and welcome addition in Clojure 1.12 (see CLJ-2792). Effectively it avoids us having to reify Supplier:

(ScopedValue/getWhere scoped-inc-amount 3
+  (reify Supplier
+    (get [_]
+      (pmap (fn [x]
+              (let [result (+ x (ScopedValue/.get scoped-inc-amount))]
+                (Thread/sleep 50)
+                (print (str "complete " result "\n"))
+                result))
+        [1 2 3 4 5 6]))))
+

Now let's see how we set multiple scoped values:

(def scoped-dec-amount (ScopedValue/newInstance))
+
+(.. (ScopedValue/where scoped-inc-amount 3)
+  (ScopedValue/where scoped-dec-amount -2)
+  (ScopedValue/get
+    (delay
+      (pmap (fn [x]
+              (let [result (+ x
+                             (ScopedValue/.get scoped-inc-amount)
+                             (ScopedValue/.get scoped-dec-amount))]
+                (Thread/sleep 50)
+                (print (str "complete " result "\n"))
+                result))
+        [1 2 3 4 5 6]))))
+        
+=> complete 4
+complete 2
+complete 3
+complete 5
+complete 7
+complete 6
+
+[2 3 4 5 6 7]
+

Finally, let's make this more ergonomic by writing a convenience macro for scoped values called scoped-binding that mirrors Clojure's binding macro:

(defmacro scoped-binding [bindings & body]
+  (assert (vector? bindings)
+    "a vector for its binding")
+  (assert (even? (count bindings))
+    "an even number of forms in binding vector")
+  `(.. ~@(->> (partition 2 bindings)
+           (map (fn [[k v]]
+                  (assert (-> k resolve deref type (= ScopedValue))
+                    (str k " is not a ScopedValue"))
+                  `(ScopedValue/where ~k ~v))))
+     (ScopedValue/get (delay ~@body))))
+

And see if it works:

(scoped-binding [scoped-inc-amount  3
+                 scoped-dec-amount -2]
+  (pmap (fn [x]
+          (let [result (+ x
+                         (ScopedValue/.get scoped-inc-amount)
+                         (ScopedValue/.get scoped-dec-amount))]
+            (Thread/sleep 50)
+            (print (str "complete " result "\n"))
+            result))
+    [1 2 3 4 5 6]))
+
+=> complete 4
+complete 6
+complete 9
+complete 8
+complete 5
+complete 7
+
+[4 5 6 7 8 9]
+

Great, we now have all the tools for using scoped values in Clojure.

Yet again we've seen how Clojure's seamless and constantly improving integration with Java makes exploring the latest Java features effortless, and thanks to macros we can even improve on the Java experience.

The full example project can be found here.

Further Reading:

\ No newline at end of file diff --git a/docs/2024/05/14/clojure-structured-concurrency-and-scoped-variables.html b/docs/2024/05/14/clojure-structured-concurrency-and-scoped-variables.html new file mode 100644 index 00000000..51dba928 --- /dev/null +++ b/docs/2024/05/14/clojure-structured-concurrency-and-scoped-variables.html @@ -0,0 +1,227 @@ + +Clojure: structured concurrency and scoped variables

Clojure: structured concurrency and scoped variables


In this post we'll explore some useful tools for making working with virtual threads easier. Structured concurrency helps eliminate common problems like thread leaks and cancellation delays. Scoped variables lets you extend parent thread based context to child threads so you can treat a group of threads as a single unit of work with the same shared context.

Enable preview

Structured concurrency and scoped variables are available in java 21 as preview features, so we'll need to enable preview:

{:paths ["src"]
+ :deps {org.clojure/clojure {:mvn/version "1.12.0-alpha11"}}
+ :aliases
+{:dev {:jvm-opts ["--enable-preview"]}}}
+

Example code

We'll be implementing our own of pmap as it has a clear thread hierarchy though which is exactly the sort of place both structured concurrency and scoped variables are useful:

(ns server.core
+  (:refer-clojure :exclude [pmap])
+  (:import
+   (java.util.concurrent
+     ExecutorService
+     Executors
+     Callable)))
+
+(defonce executor
+  (Executors/newVirtualThreadPerTaskExecutor))
+
+(defn pmap [f coll]
+  (->> (mapv (fn [x] (ExecutorService/.submit executor
+                       ;; More than one matching method found: submit
+                       ;; So we need to type hint Callable
+                       ^Callable (fn [] (f x))))
+         coll)
+    (mapv deref)))
+

Let's run this code and make one of the tasks cause an exception:

(pmap (fn [x]
+        (let [result (inc x)]
+          (Thread/sleep 50) ;; simulate some io
+          (print (str "complete " result "\n"))
+          result))
+  [1 2 "3" 4 5 6])
+
+=> Error printing return value (ClassCastException)
+at clojure.lang.Numbers/inc (Numbers.java:139).
+class java.lang.String cannot be cast to class
+java.lang.Number (java.lang.String and java.lang.Number
+are in module java.base of loader 'bootstrap')
+
+complete 7
+complete 2
+complete 5
+complete 4
+complete 6
+

Despite one of the tasks causing an exception all the other tasks keep running and complete. This might not be the behaviour we want, particularly if we require all tasks to succeed.

This is where structured concurrency scopes come in.

Simplify concurrent programming by introducing an API for structured concurrency. Structured concurrency 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.

  • JEP 462

Structured Task Scope

First let's import the classes we will need from StructuredTaskScope:

(ns server.core
+  (:refer-clojure :exclude [pmap])
+  (:import
+   (java.util.concurrent
++    StructuredTaskScope
++    StructuredTaskScope$Subtask
++    StructuredTaskScope$ShutdownOnFailure
++    StructuredTaskScope$ShutdownOnSuccess
+     ExecutorService
+     Executors
+     Callable)))
+

When dealing with concurrent subtasks it is common to use short-circuiting patterns to avoid doing unnecessary work. Currently, StructuredTaskScope provides two shutdown policies ShutdownOnFailure and ShutdownOnSuccess. These policies shut down the scope when the first subtask fails or succeeds, respectively.

We're going to explore the ShutdownOnFailure shutdown policy first.

Let's redefine our pmap function:

(defn pmap [f coll]
+  (with-open [scope (StructuredTaskScope$ShutdownOnFailure/new)]
+    (let [r (mapv (fn [x]
+                    (StructuredTaskScope/.fork scope
+                      (fn [] (f x))))
+              coll)]
+      ;; join subtasks and propagate errors
+      (.. scope join throwIfFailed)
+      ;; fork returns a Subtask/Supplier not a future
+      (mapv StructuredTaskScope$Subtask/.get r))))
+

Then run this new version with one task causing an exception:

(pmap (fn [x]
+        (let [result (inc x)]
+          (Thread/sleep 50)
+          (print (str "complete " result "\n"))
+          result))
+  [1 2 "3" 4 5 6])
+  
+=> Error printing return value (ClassCastException)
+at clojure.lang.Numbers/inc (Numbers.java:139).
+class java.lang.String cannot be cast to class
+java.lang.Number (java.lang.String and java.lang.Number
+are in module java.base of loader 'bootstrap')
+

As you can see the other threads are shutdown before they run/complete. Note: this depends on execution order and task completion time. Some threads might complete before the exception occurs.

Next lets look at the ShutdownOnSuccess shutdown policy. This policy works well in a situation where you only care about one of the results. For example reaching out to three data providers that provide the same data (for redundancy).

We are going to implement a function called alts that will take the first completed task from a sequence of tasks being executed in parallel. Only failing if all tasks fail.

(defn alts [f coll]
+  (with-open [scope (StructuredTaskScope$ShutdownOnSuccess/new)]
+    (run! (fn [x]
+            (StructuredTaskScope/.fork scope (fn [] (f x))))
+      coll)
+    ;; Throws if none of the subtasks completed successfully
+    (.. scope join result)))
+

Let's run alts and make one of the tasks cause an exception:

(alts (fn [x]
+        (let [result (inc x)]
+          (Thread/sleep 100)
+          (print (str "complete " result "\n"))
+          result))
+  [1 2 "3" 4 5 6])
+  
+=> 
+complete 2
+complete 4
+
+2
+

We can see two of the tasks manage to complete, the rest are shutdown and only one result is returned.

Structured concurrency is a really nice addition to Java. It's great for automatically handling thread cancellation which can help keep latency down and avoid thread leaks in the case of error.

That being said it's not a natural fit for all use cases. Sometimes you do want unstructured concurrency, like in my previous post on Clojure: managing throughput with virtual threads where upmap produces tasks in one thread and consumes their results in another.

Something I haven't covered is that StructuredTaskScope can be extended to implement your own shutdown policies. However, I'll cover that in a future post as it's a fair bit more involved.

Dynamic var binding conveyance

Before we get on to scoped variables lets explore Clojure's existing mechanism for thread bound state: dynamic vars. Dynamic vars implement a nice feature called binding conveyance which means thread context gets passed to futures and agents spawned by the parent thread. However, because StructuredTaskScope returns StructuredTaskScope$Subtask/Supplier and not a future we don't get binding conveyance automatically:

(def ^:dynamic *inc-amount* nil)
+
+(binding [*inc-amount* 3]
+  (pmap (fn [x]
+          (let [result (+ x *inc-amount*)]
+            (Thread/sleep 50)
+            (print (str "complete " result "\n"))
+            result))
+    [1 2 3 4 5 6]))
+    
+=> Execution error (NullPointerException) 
+at server.core/eval3782$fn (REPL:6).
+Cannot invoke "Object.getClass()" because "x" is null
+

The task threads do not inherit the value of the *inc-aomunt* binding so we get an error. Thankfully, this is easy to fix with the bound-fn* function. A higher order function that transfers the current bindings to the new thread:

(binding [*inc-amount* 3]
+  (pmap
++   (bound-fn*
+      (fn [x]
+        (let [result (+ x *inc-amount*)]
+          (Thread/sleep 50)
+          (print (str "complete " result "\n"))
+          result)))
+    [1 2 3 4 5 6]))
+    
+=> complete 9
+complete 6
+complete 7
+complete 5
+complete 4
+complete 8
+
+[4 5 6 7 8 9]
+

Working as expected.

Scoped Values

This brings us to scoped values. These are similar to Clojure's dynamic vars and Java's thread-local variables but designed for use with virtual threads.

Scoped values, values that may be safely and efficiently shared to methods
without using method parameters. They are preferred to thread-local variables, especially when using large numbers of virtual threads. This is a preview API.

  • JEP 446

With the following stated goals:

Goals

  • Ease of use — Provide a programming model to share data both within a thread >and with child threads, so as to simplify reasoning about data flow.

  • Comprehensibility — Make the lifetime of shared data visible from the syntactic >structure of code.

  • Robustness — Ensure that data shared by a caller can be retrieved only by >legitimate callees.

  • Performance — Allow shared data to be immutable so as to allow sharing by a large number of threads, and to enable runtime optimizations.

First let's import the classes we will need from ScopedValue:

(ns server.core
+  (:refer-clojure :exclude [pmap])
++ (java.lang ScopedValue)
+  (:import
+   (java.util.concurrent
+     StructuredTaskScope
+     StructuredTaskScope$Subtask
+     StructuredTaskScope$ShutdownOnFailure
+     StructuredTaskScope$ShutdownOnSuccess
+     ExecutorService
+     Executors
+     Callable)))
+

Scoped values have conveyance built in as this is the behaviour that makes the most sense with hierarchical tasks:

Subtasks forked in a scope inherit ScopedValue bindings (JEP 446). If a scope's owner reads a value from a bound ScopedValue then each subtask will read the same value.

  • JEP 462

Let's see how to use a single scoped value:

(def scoped-inc-amount (ScopedValue/newInstance))
+
+(ScopedValue/getWhere scoped-inc-amount 3
+  (delay 
+    (pmap (fn [x]
+            (let [result (+ x (ScopedValue/.get scoped-inc-amount))]
+              (Thread/sleep 50)
+              (print (str "complete " result "\n"))
+              result))
+      [1 2 3 4 5 6])))
+
+=> complete 4
+complete 6
+complete 9
+complete 8
+complete 5
+complete 7
+
+[4 5 6 7 8 9]
+

It's worth pointing out the use of delay using delay to satisfy the Supplier interface is a recent and welcome addition in Clojure 1.12 (see CLJ-2792). It avoids us having to reify Supplier:

(ScopedValue/getWhere scoped-inc-amount 3
+  (reify Supplier
+    (get [_]
+      (pmap (fn [x]
+              (let [result (+ x (ScopedValue/.get scoped-inc-amount))]
+                (Thread/sleep 50)
+                (print (str "complete " result "\n"))
+                result))
+        [1 2 3 4 5 6]))))
+

Now let's see how we set multiple scoped values:

(def scoped-dec-amount (ScopedValue/newInstance))
+
+(.. (ScopedValue/where scoped-inc-amount 3)
+  (ScopedValue/where scoped-dec-amount -2)
+  (ScopedValue/get
+    (delay
+      (pmap (fn [x]
+              (let [result (+ x
+                             (ScopedValue/.get scoped-inc-amount)
+                             (ScopedValue/.get scoped-dec-amount))]
+                (Thread/sleep 50)
+                (print (str "complete " result "\n"))
+                result))
+        [1 2 3 4 5 6]))))
+        
+=> complete 4
+complete 2
+complete 3
+complete 5
+complete 7
+complete 6
+
+[2 3 4 5 6 7]
+

Finally, let's make this more ergonomic by writing a convenience macro for scoped values called scoped-binding that mirrors Clojure's binding macro:

(defmacro scoped-binding [bindings & body]
+  (assert (vector? bindings)
+    "a vector for its binding")
+  (assert (even? (count bindings))
+    "an even number of forms in binding vector")
+  `(.. ~@(->> (partition 2 bindings)
+           (map (fn [[k v]]
+                  (assert (-> k resolve deref type (= ScopedValue))
+                    (str k " is not a ScopedValue"))
+                  `(ScopedValue/where ~k ~v))))
+     (ScopedValue/get (delay ~@body))))
+

And see if it works:

(scoped-binding [scoped-inc-amount  3
+                 scoped-dec-amount -2]
+  (pmap (fn [x]
+          (let [result (+ x
+                         (ScopedValue/.get scoped-inc-amount)
+                         (ScopedValue/.get scoped-dec-amount))]
+            (Thread/sleep 50)
+            (print (str "complete " result "\n"))
+            result))
+    [1 2 3 4 5 6]))
+
+=> complete 4
+complete 6
+complete 9
+complete 8
+complete 5
+complete 7
+
+[4 5 6 7 8 9]
+

Great we now have all the tools for using scoped values in Clojure. That being said should you use scoped values in Clojure? In places you were already using thread-local variables then scoped values have improved performance and security. But, if you are already using Clojure's dynamic vars then I don't see an obvious benefit to switching to scoped variables other than not having to remember to use bound-fn* when you want to carry over bound dynamic vars to virtual threads.

Yet again we've seen how Clojure's seamless and constantly improving integration with Java makes exploring the latest Java features effortless, and thanks to macros we can even improve on the Java experience.

The full example project can be found here.

Further Reading:

\ No newline at end of file diff --git a/docs/feed.xml b/docs/feed.xml index 68a895b0..a1da8e41 100644 --- a/docs/feed.xml +++ b/docs/feed.xml @@ -1 +1 @@ -anders murphyA blog mostly about Clojure programming/Clojure: managing throughput with virtual threadsMon, 6 May 2024 00:00:00 GMT/2024/05/06/clojure-managing-throughput-with-virtual-threads.html/2024/05/06/clojure-managing-throughput-with-virtual-threads.htmlClojure: CI with Github Actions and PostgresSat, 6 Apr 2024 00:00:00 GMT/2024/04/06/clojure-ci-with-github-actions-and-postgres.html/2024/04/06/clojure-ci-with-github-actions-and-postgres.htmlClojure: pruning HTML with clojure.walkMon, 1 Apr 2024 00:00:00 GMT/2024/04/01/clojure-pruning-html-with-clojure-walk.html/2024/04/01/clojure-pruning-html-with-clojure-walk.htmlEmacs: streaming radio with emmsSun, 31 Mar 2024 00:00:00 GMT/2024/03/31/emacs-streaming-radio-with-emms.html/2024/03/31/emacs-streaming-radio-with-emms.htmlClojure: the REPL makes contributing to open source easyMon, 4 Mar 2024 00:00:00 GMT/2024/03/04/clojure-the-repl-makes-contributing-to-open-source-easy.html/2024/03/04/clojure-the-repl-makes-contributing-to-open-source-easy.htmlClojure: clj-kondo datalog lintingWed, 3 Jan 2024 00:00:00 GMT/2024/01/03/clojure-clj-kondo-datalog-linting.html/2024/01/03/clojure-clj-kondo-datalog-linting.htmlClojure: virtual threads with ring and jettySat, 16 Sep 2023 00:00:00 GMT/2023/09/16/clojure-virtual-threads-with-ring-and-jetty.html/2023/09/16/clojure-virtual-threads-with-ring-and-jetty.htmlClojure: virtual threads with ring and http-kitFri, 15 Sep 2023 00:00:00 GMT/2023/09/15/clojure-virtual-threads-with-ring-and-http-kit.html/2023/09/15/clojure-virtual-threads-with-ring-and-http-kit.htmlFennel: making PICO-8 gamesFri, 8 Sep 2023 00:00:00 GMT/2023/09/08/fennel-making-pico-8-games.html/2023/09/08/fennel-making-pico-8-games.htmlClojure: SQLite application defined SQL functions with JDBCSun, 16 Jul 2023 00:00:00 GMT/2023/07/16/clojure-sqlite-application-defined-sql-functions-with-jdbc.html/2023/07/16/clojure-sqlite-application-defined-sql-functions-with-jdbc.htmlSQLite: building from source on macOSSun, 9 Jul 2023 00:00:00 GMT/2023/07/09/sqlite-building-from-source-on-macos.html/2023/07/09/sqlite-building-from-source-on-macos.htmlClojure: sending emails with postal and Gmail SMTPTue, 14 Jun 2022 00:00:00 GMT/2022/06/14/clojure-sending-emails-with-postal-and-gmail-smtp.html/2022/06/14/clojure-sending-emails-with-postal-and-gmail-smtp.htmlClojure: pretty print stringsSun, 22 May 2022 00:00:00 GMT/2022/05/22/clojure-pretty-print-strings.html/2022/05/22/clojure-pretty-print-strings.htmlClojure: extend honeysql to support postgres 'alter column' and 'add constraint'Mon, 2 May 2022 00:00:00 GMT/2022/05/02/clojure-extend-honeysql-to-support-postgres-alter-column-and-add-constraint.html/2022/05/02/clojure-extend-honeysql-to-support-postgres-alter-column-and-add-constraint.htmlClojure: removing namespace from keywords in response middlewareSun, 27 Mar 2022 00:00:00 GMT/2022/03/27/clojure-removing-namespace-from-keywords-in-response-middleware.html/2022/03/27/clojure-removing-namespace-from-keywords-in-response-middleware.htmlClojure: making missing environment variables fail at compile timeSun, 20 Mar 2022 00:00:00 GMT/2022/03/20/clojure-making-missing-environment-variables-fail-at-compile-time.html/2022/03/20/clojure-making-missing-environment-variables-fail-at-compile-time.htmlHeroku: buildpack for Clojure toolsSat, 19 Mar 2022 00:00:00 GMT/2022/03/19/heroku-buildpack-for-clojure-tools.html/2022/03/19/heroku-buildpack-for-clojure-tools.htmlClojure: compiling java source with tools.buildSun, 12 Dec 2021 00:00:00 GMT/2021/12/12/clojure-compiling-java-source-with-tools-build.html/2021/12/12/clojure-compiling-java-source-with-tools-build.htmlClojure: check if instant happened today at timezoneSat, 4 Dec 2021 00:00:00 GMT/2021/12/04/clojure-check-if-instant-happened-today-at-timezone.html/2021/12/04/clojure-check-if-instant-happened-today-at-timezone.htmlEmacs: the joy of reducing workflow friction with elispTue, 30 Nov 2021 00:00:00 GMT/2021/11/30/emacs-the-joy-of-reducing-workflow-friction-with-elisp.html/2021/11/30/emacs-the-joy-of-reducing-workflow-friction-with-elisp.htmlEmacs: building from source on macOSSun, 14 Nov 2021 00:00:00 GMT/2021/11/14/emacs-building-from-source-on-macos.html/2021/11/14/emacs-building-from-source-on-macos.htmlClojure: code highlights for this websiteThu, 11 Nov 2021 00:00:00 GMT/2021/11/11/clojure-code-highlights-for-this-website.html/2021/11/11/clojure-code-highlights-for-this-website.htmlClojure: website link checkerSun, 31 Oct 2021 00:00:00 GMT/2021/10/31/clojure-website-link-checker.html/2021/10/31/clojure-website-link-checker.htmlClojure: map-occurrenceSat, 30 Oct 2021 00:00:00 GMT/2021/10/30/clojure-map-occurrence.html/2021/10/30/clojure-map-occurrence.htmlClojure: ensuring multimethods are requiredSun, 24 Oct 2021 00:00:00 GMT/2021/10/24/clojure-ensuring-multimethods-are-required.html/2021/10/24/clojure-ensuring-multimethods-are-required.htmlClojure: destructive macrosSun, 18 Jul 2021 00:00:00 GMT/2021/07/18/clojure-destructive-macros.html/2021/07/18/clojure-destructive-macros.htmlClojure: crawling Hacker News with re-seqSat, 17 Jul 2021 00:00:00 GMT/2021/07/17/clojure-crawling-hackernews-with-re-seq.html/2021/07/17/clojure-crawling-hackernews-with-re-seq.htmlClojure: cond-merge revisitedWed, 30 Dec 2020 00:00:00 GMT/2020/12/30/clojure-cond-merge-revisited.html/2020/12/30/clojure-cond-merge-revisited.htmlClojure: adding dissoc-in to our cond-merge macroTue, 29 Dec 2020 00:00:00 GMT/2020/12/29/clojure-adding-dissoc-in-to-the-cond-merge-macro.html/2020/12/29/clojure-adding-dissoc-in-to-the-cond-merge-macro.htmlClojure: cond->, deep-merge, remove-nils and the shape of dataSun, 27 Dec 2020 00:00:00 GMT/2020/12/27/clojure-cond-deep-merge-remove-nils-and-the-shape-of-data.html/2020/12/27/clojure-cond-deep-merge-remove-nils-and-the-shape-of-data.htmlClojure: string similaritySun, 13 Dec 2020 00:00:00 GMT/2020/12/13/clojure-string-similarity.html/2020/12/13/clojure-string-similarity.htmlClojure: adding compile time errors with macrosWed, 11 Nov 2020 00:00:00 GMT/2020/11/11/clojure-adding-compile-time-errors-with-macros.html/2020/11/11/clojure-adding-compile-time-errors-with-macros.htmlClojure: previous, current and nextSun, 11 Oct 2020 00:00:00 GMT/2020/10/11/clojure-previous-current-and-next.html/2020/10/11/clojure-previous-current-and-next.htmlClojure: jdbc using any and all as alternatives to inSun, 6 Sep 2020 00:00:00 GMT/2020/09/06/clojure-jdbc-using-any-and-all-as-an-alternative-to-in.html/2020/09/06/clojure-jdbc-using-any-and-all-as-an-alternative-to-in.htmlEmacs: setting up Apheleia to use Prettier and ZprintThu, 20 Aug 2020 00:00:00 GMT/2020/08/20/emacs-setting-up-apheleia-to-use-zprint.html/2020/08/20/emacs-setting-up-apheleia-to-use-zprint.htmlHomebrew: write your own brew formulaTue, 18 Aug 2020 00:00:00 GMT/2020/08/18/homebrew-write-your-own-brew-formula.html/2020/08/18/homebrew-write-your-own-brew-formula.htmlClojure: code formatting pre-commit hook with zprintSun, 16 Aug 2020 00:00:00 GMT/2020/08/16/clojure-code-formatting-pre-commit-hook-with-zprint.html/2020/08/16/clojure-code-formatting-pre-commit-hook-with-zprint.htmlClojure: java interop with beanFri, 27 Mar 2020 00:00:00 GMT/2020/03/27/clojure-java-interop-with-bean.html/2020/03/27/clojure-java-interop-with-bean.htmlClojure: code as dataSun, 1 Mar 2020 00:00:00 GMT/2020/03/01/clojure-code-as-data.html/2020/03/01/clojure-code-as-data.htmlClojure: persistent rate limitingSat, 8 Feb 2020 00:00:00 GMT/2020/02/08/clojure-persistent-rate-limiting.html/2020/02/08/clojure-persistent-rate-limiting.htmlClojure: permutationsThu, 19 Dec 2019 00:00:00 GMT/2019/12/19/clojure-permutations.html/2019/12/19/clojure-permutations.htmlRuby: functional programmingSat, 7 Dec 2019 00:00:00 GMT/2019/12/07/ruby-functional-programming.html/2019/12/07/ruby-functional-programming.htmlClojure: flattening key pathsSat, 30 Nov 2019 00:00:00 GMT/2019/11/30/clojure-flattening-key-paths.html/2019/11/30/clojure-flattening-key-paths.htmlClojure: manipulating HTML and XML with zippersSun, 17 Nov 2019 00:00:00 GMT/2019/11/17/clojure-manipulating-html-and-xml-with-zippers.html/2019/11/17/clojure-manipulating-html-and-xml-with-zippers.htmlClojure: sorting tuplesSun, 27 Oct 2019 00:00:00 GMT/2019/10/27/clojure-sorting-tuples.html/2019/10/27/clojure-sorting-tuples.htmlClojure: generating HTML and XMLSun, 8 Sep 2019 00:00:00 GMT/2019/09/08/clojure-generating-html-and-xml.html/2019/09/08/clojure-generating-html-and-xml.htmlClojure: using java.time with clojure.java.jdbcSat, 3 Aug 2019 00:00:00 GMT/2019/08/03/clojure-using-java-time-with-jdbc.html/2019/08/03/clojure-using-java-time-with-jdbc.htmlClojure: connection pooling with hikari-cpSun, 14 Jul 2019 00:00:00 GMT/2019/07/14/clojure-connection-pooling-with-hikari-cp.html/2019/07/14/clojure-connection-pooling-with-hikari-cp.htmlClojure: emoji in stringsTue, 2 Jul 2019 00:00:00 GMT/2019/07/02/clojure-emoji-in-strings.html/2019/07/02/clojure-emoji-in-strings.htmlClojure: a debug macro for threading macros using tap>Tue, 4 Jun 2019 00:00:00 GMT/2019/06/04/clojure-a-debug-macro-for-threading-macros-using-tap.html/2019/06/04/clojure-a-debug-macro-for-threading-macros-using-tap.htmlClojure: intro to tap> and accessing private varsSat, 1 Jun 2019 00:00:00 GMT/2019/06/01/clojure-intro-to-tap-and-accessing-private-vars.html/2019/06/01/clojure-intro-to-tap-and-accessing-private-vars.htmlClojure: sorting a sequence based on another sequenceSat, 25 May 2019 00:00:00 GMT/2019/05/25/clojure-sorting-a-sequence-based-on-another-sequence.html/2019/05/25/clojure-sorting-a-sequence-based-on-another-sequence.htmlClojure: personalising textSat, 18 May 2019 00:00:00 GMT/2019/05/18/clojure-personalising-text.html/2019/05/18/clojure-personalising-text.htmlClojure: case conversion and boundariesSat, 4 May 2019 00:00:00 GMT/2019/05/04/clojure-case-conversion-and-boundaries.html/2019/05/04/clojure-case-conversion-and-boundaries.htmlClojure: contains? and someFri, 5 Apr 2019 00:00:00 GMT/2019/04/05/clojure-contains-and-some.html/2019/04/05/clojure-contains-and-some.htmlClojure: sortingSat, 9 Mar 2019 00:00:00 GMT/2019/03/09/clojure-sorting.html/2019/03/09/clojure-sorting.htmlLisp-1 vs Lisp-2Fri, 8 Mar 2019 00:00:00 GMT/2019/03/08/lisp-1-vs-lisp-2.html/2019/03/08/lisp-1-vs-lisp-2.htmlClojure: merging maps by key (join)Sat, 16 Feb 2019 00:00:00 GMT/2019/02/16/clojure-merging-maps-by-key.html/2019/02/16/clojure-merging-maps-by-key.htmlClojure: string interpolationTue, 15 Jan 2019 00:00:00 GMT/2019/01/15/clojure-string-interpolation.html/2019/01/15/clojure-string-interpolation.htmlClojure: sending emails with SendGridSun, 6 Jan 2019 00:00:00 GMT/2019/01/06/clojure-sending-emails-with-sendgrid.html/2019/01/06/clojure-sending-emails-with-sendgrid.htmlClojure: validating phone numbersSat, 24 Nov 2018 00:00:00 GMT/2018/11/24/clojure-validating-phone-numbers.html/2018/11/24/clojure-validating-phone-numbers.htmlClojure: juxt and separateSun, 18 Nov 2018 00:00:00 GMT/2018/11/18/clojure-juxt-and-separate.html/2018/11/18/clojure-juxt-and-separate.htmlClojure: map-values and map-keysSat, 10 Nov 2018 00:00:00 GMT/2018/11/10/clojure-map-values-and-keys.html/2018/11/10/clojure-map-values-and-keys.htmlDesert island code: compose and pipeThu, 4 Jan 2018 00:00:00 GMT/2018/01/04/desert-island-code-compose-and-pipe.html/2018/01/04/desert-island-code-compose-and-pipe.htmlDesert island code: curryTue, 2 Jan 2018 00:00:00 GMT/2018/01/02/desert-island-code-curry.html/2018/01/02/desert-island-code-curry.htmlDesert island code: reduce map and filterThu, 28 Dec 2017 00:00:00 GMT/2017/12/28/desert-island-code-reduce-map-and-filter.html/2017/12/28/desert-island-code-reduce-map-and-filter.htmlManaging obfuscation with annotationsSat, 8 Oct 2016 00:00:00 GMT/2016/10/08/managing-obfuscation-with-annotations.html/2016/10/08/managing-obfuscation-with-annotations.htmlUsing Proguard instead of multidexThu, 19 May 2016 00:00:00 GMT/2016/05/19/using-proguard-instead-of-multidex.html/2016/05/19/using-proguard-instead-of-multidex.htmlSigning your appTue, 17 May 2016 00:00:00 GMT/2016/05/17/signing-your-app.html/2016/05/17/signing-your-app.htmlIntroduction to Kotlin on AndroidTue, 6 Oct 2015 00:00:00 GMT/2015/10/06/introduction-to-kotlin-on-android.html/2015/10/06/introduction-to-kotlin-on-android.htmlSetting up RetrolambdaWed, 16 Sep 2015 00:00:00 GMT/2015/09/16/setting-up-retrolambda.html/2015/09/16/setting-up-retrolambda.htmlEnabling multidex on AndroidThu, 10 Sep 2015 00:00:00 GMT/2015/09/10/enabling-multidex-on-android.html/2015/09/10/enabling-multidex-on-android.htmlBinding Android views with Butter KnifeWed, 2 Sep 2015 00:00:00 GMT/2015/09/02/binding-android-views-with-butter-knife.html/2015/09/02/binding-android-views-with-butter-knife.htmlAdvantages of an Android free zoneThu, 27 Aug 2015 00:00:00 GMT/2015/08/27/advantages-of-an-android-free-zone.html/2015/08/27/advantages-of-an-android-free-zone.html \ No newline at end of file +anders murphyA blog mostly about Clojure programming/Clojure: structured concurrency and scoped valuesTue, 14 May 2024 00:00:00 GMT/2024/05/14/clojure-structured-concurrency-and-scoped-values.html/2024/05/14/clojure-structured-concurrency-and-scoped-values.htmlClojure: managing throughput with virtual threadsMon, 6 May 2024 00:00:00 GMT/2024/05/06/clojure-managing-throughput-with-virtual-threads.html/2024/05/06/clojure-managing-throughput-with-virtual-threads.htmlClojure: CI with Github Actions and PostgresSat, 6 Apr 2024 00:00:00 GMT/2024/04/06/clojure-ci-with-github-actions-and-postgres.html/2024/04/06/clojure-ci-with-github-actions-and-postgres.htmlClojure: pruning HTML with clojure.walkMon, 1 Apr 2024 00:00:00 GMT/2024/04/01/clojure-pruning-html-with-clojure-walk.html/2024/04/01/clojure-pruning-html-with-clojure-walk.htmlEmacs: streaming radio with emmsSun, 31 Mar 2024 00:00:00 GMT/2024/03/31/emacs-streaming-radio-with-emms.html/2024/03/31/emacs-streaming-radio-with-emms.htmlClojure: the REPL makes contributing to open source easyMon, 4 Mar 2024 00:00:00 GMT/2024/03/04/clojure-the-repl-makes-contributing-to-open-source-easy.html/2024/03/04/clojure-the-repl-makes-contributing-to-open-source-easy.htmlClojure: clj-kondo datalog lintingWed, 3 Jan 2024 00:00:00 GMT/2024/01/03/clojure-clj-kondo-datalog-linting.html/2024/01/03/clojure-clj-kondo-datalog-linting.htmlClojure: virtual threads with ring and jettySat, 16 Sep 2023 00:00:00 GMT/2023/09/16/clojure-virtual-threads-with-ring-and-jetty.html/2023/09/16/clojure-virtual-threads-with-ring-and-jetty.htmlClojure: virtual threads with ring and http-kitFri, 15 Sep 2023 00:00:00 GMT/2023/09/15/clojure-virtual-threads-with-ring-and-http-kit.html/2023/09/15/clojure-virtual-threads-with-ring-and-http-kit.htmlFennel: making PICO-8 gamesFri, 8 Sep 2023 00:00:00 GMT/2023/09/08/fennel-making-pico-8-games.html/2023/09/08/fennel-making-pico-8-games.htmlClojure: SQLite application defined SQL functions with JDBCSun, 16 Jul 2023 00:00:00 GMT/2023/07/16/clojure-sqlite-application-defined-sql-functions-with-jdbc.html/2023/07/16/clojure-sqlite-application-defined-sql-functions-with-jdbc.htmlSQLite: building from source on macOSSun, 9 Jul 2023 00:00:00 GMT/2023/07/09/sqlite-building-from-source-on-macos.html/2023/07/09/sqlite-building-from-source-on-macos.htmlClojure: sending emails with postal and Gmail SMTPTue, 14 Jun 2022 00:00:00 GMT/2022/06/14/clojure-sending-emails-with-postal-and-gmail-smtp.html/2022/06/14/clojure-sending-emails-with-postal-and-gmail-smtp.htmlClojure: pretty print stringsSun, 22 May 2022 00:00:00 GMT/2022/05/22/clojure-pretty-print-strings.html/2022/05/22/clojure-pretty-print-strings.htmlClojure: extend honeysql to support postgres 'alter column' and 'add constraint'Mon, 2 May 2022 00:00:00 GMT/2022/05/02/clojure-extend-honeysql-to-support-postgres-alter-column-and-add-constraint.html/2022/05/02/clojure-extend-honeysql-to-support-postgres-alter-column-and-add-constraint.htmlClojure: removing namespace from keywords in response middlewareSun, 27 Mar 2022 00:00:00 GMT/2022/03/27/clojure-removing-namespace-from-keywords-in-response-middleware.html/2022/03/27/clojure-removing-namespace-from-keywords-in-response-middleware.htmlClojure: making missing environment variables fail at compile timeSun, 20 Mar 2022 00:00:00 GMT/2022/03/20/clojure-making-missing-environment-variables-fail-at-compile-time.html/2022/03/20/clojure-making-missing-environment-variables-fail-at-compile-time.htmlHeroku: buildpack for Clojure toolsSat, 19 Mar 2022 00:00:00 GMT/2022/03/19/heroku-buildpack-for-clojure-tools.html/2022/03/19/heroku-buildpack-for-clojure-tools.htmlClojure: compiling java source with tools.buildSun, 12 Dec 2021 00:00:00 GMT/2021/12/12/clojure-compiling-java-source-with-tools-build.html/2021/12/12/clojure-compiling-java-source-with-tools-build.htmlClojure: check if instant happened today at timezoneSat, 4 Dec 2021 00:00:00 GMT/2021/12/04/clojure-check-if-instant-happened-today-at-timezone.html/2021/12/04/clojure-check-if-instant-happened-today-at-timezone.htmlEmacs: the joy of reducing workflow friction with elispTue, 30 Nov 2021 00:00:00 GMT/2021/11/30/emacs-the-joy-of-reducing-workflow-friction-with-elisp.html/2021/11/30/emacs-the-joy-of-reducing-workflow-friction-with-elisp.htmlEmacs: building from source on macOSSun, 14 Nov 2021 00:00:00 GMT/2021/11/14/emacs-building-from-source-on-macos.html/2021/11/14/emacs-building-from-source-on-macos.htmlClojure: code highlights for this websiteThu, 11 Nov 2021 00:00:00 GMT/2021/11/11/clojure-code-highlights-for-this-website.html/2021/11/11/clojure-code-highlights-for-this-website.htmlClojure: website link checkerSun, 31 Oct 2021 00:00:00 GMT/2021/10/31/clojure-website-link-checker.html/2021/10/31/clojure-website-link-checker.htmlClojure: map-occurrenceSat, 30 Oct 2021 00:00:00 GMT/2021/10/30/clojure-map-occurrence.html/2021/10/30/clojure-map-occurrence.htmlClojure: ensuring multimethods are requiredSun, 24 Oct 2021 00:00:00 GMT/2021/10/24/clojure-ensuring-multimethods-are-required.html/2021/10/24/clojure-ensuring-multimethods-are-required.htmlClojure: destructive macrosSun, 18 Jul 2021 00:00:00 GMT/2021/07/18/clojure-destructive-macros.html/2021/07/18/clojure-destructive-macros.htmlClojure: crawling Hacker News with re-seqSat, 17 Jul 2021 00:00:00 GMT/2021/07/17/clojure-crawling-hackernews-with-re-seq.html/2021/07/17/clojure-crawling-hackernews-with-re-seq.htmlClojure: cond-merge revisitedWed, 30 Dec 2020 00:00:00 GMT/2020/12/30/clojure-cond-merge-revisited.html/2020/12/30/clojure-cond-merge-revisited.htmlClojure: adding dissoc-in to our cond-merge macroTue, 29 Dec 2020 00:00:00 GMT/2020/12/29/clojure-adding-dissoc-in-to-the-cond-merge-macro.html/2020/12/29/clojure-adding-dissoc-in-to-the-cond-merge-macro.htmlClojure: cond->, deep-merge, remove-nils and the shape of dataSun, 27 Dec 2020 00:00:00 GMT/2020/12/27/clojure-cond-deep-merge-remove-nils-and-the-shape-of-data.html/2020/12/27/clojure-cond-deep-merge-remove-nils-and-the-shape-of-data.htmlClojure: string similaritySun, 13 Dec 2020 00:00:00 GMT/2020/12/13/clojure-string-similarity.html/2020/12/13/clojure-string-similarity.htmlClojure: adding compile time errors with macrosWed, 11 Nov 2020 00:00:00 GMT/2020/11/11/clojure-adding-compile-time-errors-with-macros.html/2020/11/11/clojure-adding-compile-time-errors-with-macros.htmlClojure: previous, current and nextSun, 11 Oct 2020 00:00:00 GMT/2020/10/11/clojure-previous-current-and-next.html/2020/10/11/clojure-previous-current-and-next.htmlClojure: jdbc using any and all as alternatives to inSun, 6 Sep 2020 00:00:00 GMT/2020/09/06/clojure-jdbc-using-any-and-all-as-an-alternative-to-in.html/2020/09/06/clojure-jdbc-using-any-and-all-as-an-alternative-to-in.htmlEmacs: setting up Apheleia to use Prettier and ZprintThu, 20 Aug 2020 00:00:00 GMT/2020/08/20/emacs-setting-up-apheleia-to-use-zprint.html/2020/08/20/emacs-setting-up-apheleia-to-use-zprint.htmlHomebrew: write your own brew formulaTue, 18 Aug 2020 00:00:00 GMT/2020/08/18/homebrew-write-your-own-brew-formula.html/2020/08/18/homebrew-write-your-own-brew-formula.htmlClojure: code formatting pre-commit hook with zprintSun, 16 Aug 2020 00:00:00 GMT/2020/08/16/clojure-code-formatting-pre-commit-hook-with-zprint.html/2020/08/16/clojure-code-formatting-pre-commit-hook-with-zprint.htmlClojure: java interop with beanFri, 27 Mar 2020 00:00:00 GMT/2020/03/27/clojure-java-interop-with-bean.html/2020/03/27/clojure-java-interop-with-bean.htmlClojure: code as dataSun, 1 Mar 2020 00:00:00 GMT/2020/03/01/clojure-code-as-data.html/2020/03/01/clojure-code-as-data.htmlClojure: persistent rate limitingSat, 8 Feb 2020 00:00:00 GMT/2020/02/08/clojure-persistent-rate-limiting.html/2020/02/08/clojure-persistent-rate-limiting.htmlClojure: permutationsThu, 19 Dec 2019 00:00:00 GMT/2019/12/19/clojure-permutations.html/2019/12/19/clojure-permutations.htmlRuby: functional programmingSat, 7 Dec 2019 00:00:00 GMT/2019/12/07/ruby-functional-programming.html/2019/12/07/ruby-functional-programming.htmlClojure: flattening key pathsSat, 30 Nov 2019 00:00:00 GMT/2019/11/30/clojure-flattening-key-paths.html/2019/11/30/clojure-flattening-key-paths.htmlClojure: manipulating HTML and XML with zippersSun, 17 Nov 2019 00:00:00 GMT/2019/11/17/clojure-manipulating-html-and-xml-with-zippers.html/2019/11/17/clojure-manipulating-html-and-xml-with-zippers.htmlClojure: sorting tuplesSun, 27 Oct 2019 00:00:00 GMT/2019/10/27/clojure-sorting-tuples.html/2019/10/27/clojure-sorting-tuples.htmlClojure: generating HTML and XMLSun, 8 Sep 2019 00:00:00 GMT/2019/09/08/clojure-generating-html-and-xml.html/2019/09/08/clojure-generating-html-and-xml.htmlClojure: using java.time with clojure.java.jdbcSat, 3 Aug 2019 00:00:00 GMT/2019/08/03/clojure-using-java-time-with-jdbc.html/2019/08/03/clojure-using-java-time-with-jdbc.htmlClojure: connection pooling with hikari-cpSun, 14 Jul 2019 00:00:00 GMT/2019/07/14/clojure-connection-pooling-with-hikari-cp.html/2019/07/14/clojure-connection-pooling-with-hikari-cp.htmlClojure: emoji in stringsTue, 2 Jul 2019 00:00:00 GMT/2019/07/02/clojure-emoji-in-strings.html/2019/07/02/clojure-emoji-in-strings.htmlClojure: a debug macro for threading macros using tap>Tue, 4 Jun 2019 00:00:00 GMT/2019/06/04/clojure-a-debug-macro-for-threading-macros-using-tap.html/2019/06/04/clojure-a-debug-macro-for-threading-macros-using-tap.htmlClojure: intro to tap> and accessing private varsSat, 1 Jun 2019 00:00:00 GMT/2019/06/01/clojure-intro-to-tap-and-accessing-private-vars.html/2019/06/01/clojure-intro-to-tap-and-accessing-private-vars.htmlClojure: sorting a sequence based on another sequenceSat, 25 May 2019 00:00:00 GMT/2019/05/25/clojure-sorting-a-sequence-based-on-another-sequence.html/2019/05/25/clojure-sorting-a-sequence-based-on-another-sequence.htmlClojure: personalising textSat, 18 May 2019 00:00:00 GMT/2019/05/18/clojure-personalising-text.html/2019/05/18/clojure-personalising-text.htmlClojure: case conversion and boundariesSat, 4 May 2019 00:00:00 GMT/2019/05/04/clojure-case-conversion-and-boundaries.html/2019/05/04/clojure-case-conversion-and-boundaries.htmlClojure: contains? and someFri, 5 Apr 2019 00:00:00 GMT/2019/04/05/clojure-contains-and-some.html/2019/04/05/clojure-contains-and-some.htmlClojure: sortingSat, 9 Mar 2019 00:00:00 GMT/2019/03/09/clojure-sorting.html/2019/03/09/clojure-sorting.htmlLisp-1 vs Lisp-2Fri, 8 Mar 2019 00:00:00 GMT/2019/03/08/lisp-1-vs-lisp-2.html/2019/03/08/lisp-1-vs-lisp-2.htmlClojure: merging maps by key (join)Sat, 16 Feb 2019 00:00:00 GMT/2019/02/16/clojure-merging-maps-by-key.html/2019/02/16/clojure-merging-maps-by-key.htmlClojure: string interpolationTue, 15 Jan 2019 00:00:00 GMT/2019/01/15/clojure-string-interpolation.html/2019/01/15/clojure-string-interpolation.htmlClojure: sending emails with SendGridSun, 6 Jan 2019 00:00:00 GMT/2019/01/06/clojure-sending-emails-with-sendgrid.html/2019/01/06/clojure-sending-emails-with-sendgrid.htmlClojure: validating phone numbersSat, 24 Nov 2018 00:00:00 GMT/2018/11/24/clojure-validating-phone-numbers.html/2018/11/24/clojure-validating-phone-numbers.htmlClojure: juxt and separateSun, 18 Nov 2018 00:00:00 GMT/2018/11/18/clojure-juxt-and-separate.html/2018/11/18/clojure-juxt-and-separate.htmlClojure: map-values and map-keysSat, 10 Nov 2018 00:00:00 GMT/2018/11/10/clojure-map-values-and-keys.html/2018/11/10/clojure-map-values-and-keys.htmlDesert island code: compose and pipeThu, 4 Jan 2018 00:00:00 GMT/2018/01/04/desert-island-code-compose-and-pipe.html/2018/01/04/desert-island-code-compose-and-pipe.htmlDesert island code: curryTue, 2 Jan 2018 00:00:00 GMT/2018/01/02/desert-island-code-curry.html/2018/01/02/desert-island-code-curry.htmlDesert island code: reduce map and filterThu, 28 Dec 2017 00:00:00 GMT/2017/12/28/desert-island-code-reduce-map-and-filter.html/2017/12/28/desert-island-code-reduce-map-and-filter.htmlManaging obfuscation with annotationsSat, 8 Oct 2016 00:00:00 GMT/2016/10/08/managing-obfuscation-with-annotations.html/2016/10/08/managing-obfuscation-with-annotations.htmlUsing Proguard instead of multidexThu, 19 May 2016 00:00:00 GMT/2016/05/19/using-proguard-instead-of-multidex.html/2016/05/19/using-proguard-instead-of-multidex.htmlSigning your appTue, 17 May 2016 00:00:00 GMT/2016/05/17/signing-your-app.html/2016/05/17/signing-your-app.htmlIntroduction to Kotlin on AndroidTue, 6 Oct 2015 00:00:00 GMT/2015/10/06/introduction-to-kotlin-on-android.html/2015/10/06/introduction-to-kotlin-on-android.htmlSetting up RetrolambdaWed, 16 Sep 2015 00:00:00 GMT/2015/09/16/setting-up-retrolambda.html/2015/09/16/setting-up-retrolambda.htmlEnabling multidex on AndroidThu, 10 Sep 2015 00:00:00 GMT/2015/09/10/enabling-multidex-on-android.html/2015/09/10/enabling-multidex-on-android.htmlBinding Android views with Butter KnifeWed, 2 Sep 2015 00:00:00 GMT/2015/09/02/binding-android-views-with-butter-knife.html/2015/09/02/binding-android-views-with-butter-knife.htmlAdvantages of an Android free zoneThu, 27 Aug 2015 00:00:00 GMT/2015/08/27/advantages-of-an-android-free-zone.html/2015/08/27/advantages-of-an-android-free-zone.html \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index 19ba90c0..e7742d09 100644 --- a/docs/index.html +++ b/docs/index.html @@ -8,4 +8,4 @@ font-src 'self'; connect-src 'self'; style-src 'self' 'unsafe-inline' -" http-equiv="Content-Security-Policy" />

Clojure: managing throughput with virtual threads


Before the introduction of Java 21 virtual threads we were heavy users of claypoole a fantastic library for simple but effective parallelism using thread pools. However, things get complicated with virtual threads, they shouldn't be pooled, as they aren't a scarce resource. So how do we limit throughput when we have "unlimited" threads? In this post we will look at using java Semaphore class to implement basic token bucket rate limiting to control throughput.

Clojure: pruning HTML with clojure.walk


A problem that comes up when web crawling is you get a lot of data that you don't necessarily care about: layout divs, scripts, classes, ids etc. Thankfully, Clojure comes with tools that make removing the data you don't care about straight forward.

Emacs: streaming radio with emms


One of the few things I miss about commuting by car is the radio. Unfortunately, I don't have a radio at home. But I do have Emacs. Surely Emacs can do radio?

Clojure: the REPL makes contributing to open source easy


Clojure has a great interactive development experience. This makes it surprisingly easy to contribute to open source. It goes something like this: you're using an open source library, you run into a potential bug, you clone the library, you write some code, you evaluate it and you see if the bug is fixed (all without ever having to restart your REPL).

Clojure: clj-kondo datalog linting


clj-kondo has had really nice datalog syntax checking for a while. However, on some projects you might have wrapped the underlying datalog query function q or be using a datalog implementation clj-kondo doesn't know about. Thankfully, it's trivial to add linting support to any implementation of the query function q as long as it conforms to the Datalog dialect of Datomic.

Clojure: virtual threads with ring and jetty


Java 19 introduce virtual threads as a preview feature. Virtual threads are lightweight threads that allow you to write server applications in a simple thread-per-request style (as opposed to async style) and still scale with near-optimal hardware utilisation. The thread-per-request approach is generally easier to reason about, maintain and debug than it's asynchronous counterparts. In this post we'll cover configuring Jetty to make your ring handlers use virtual threads and benchmark it against regular threads to show how it alleviates the need for tuning thread pools.

Clojure: virtual threads with ring and http-kit


Java 19 introduce virtual threads as a preview feature. Virtual threads are lightweight threads that allow you to write server applications in a simple thread-per-request style (as opposed to async style) and still scale with near-optimal hardware utilisation. The thread-per-request approach is generally easier to reason about, maintain and debug than it's asynchronous counterparts. In this post we'll cover configuring http-kit to make your ring handlers use virtual threads and benchmark it against regular threads to show how it alleviates the need for tuning thread pools.

Fennel: making PICO-8 games


I recently wrote a game jam game for PICO-8 using Fennel. This post goes over setting up a project to write PICO-8 games in Fennel.

Clojure: SQLite application defined SQL functions with JDBC


SQLite provides a convenient interface called Application-Defined SQL Functions that lets you create custom SQL functions that call back into your application code to compute their results. This lets us extend SQLite in Clojure. Want to query with regex or jsonpath? Want custom aggregation functions? Don't want to write C? Don't want to manage different precompiled binaries for different operating systems. No problem Application Defined SQL Functions have you covered.

SQLite: building from source on macOS


I've been playing around with Project Panama to explore building a simpler and lighter SQLite driver to use from Clojure. This required downloading and compiling SQlite from source for macOS/OS X.

Clojure: sending emails with postal and Gmail SMTP


While services like SendGrid are easy to use and offer a plethora of features like bounce and open rate monitoring, they are not the cheapest option. Gmail's SMTP service on the other hand, although not as feature rich, is more affordable. In this post we will cover setting up Gmail SMTP with postal.

Clojure: pretty print strings


clojure.pprint/pprint can be used to print Clojure data structures in a pleasing, easy-to-understand format. However, sometimes you are not printing directly to *out*, for example when using third party logging libraries/services. Thankfully, we can also use clojure.pprint/pprint generate a pretty string by wrapping it in with-out-str.

Clojure: extend honeysql to support postgres 'alter column' and 'add constraint'


Honeysql is an amazing library that lets you write SQL using clojure data structures. This makes SQL much more composable as you no longer need to smash strings together. That being said, when using it with postgresql there are some features that don't work out of the box. Thankfully, honeysql is very easy to extend. This post will show you how to add support for alter column and add constraint.

Clojure: removing namespace from keywords in response middleware


Namespaced keywords let you add a namespace to a keyword :id -> :user/id, this is a really nice feature that prevents collisions when merging two maps. For example merging {:account/id "foo"} with {:user/id "bar"}) would give you {:account/id "foo" :user/id "bar"}. This lets you avoid unnecessary data nesting and keep your data flat. However, by the time this data gets to the edge of your system, where it meets something that expects json, you will often want to remove these namespaces. This post shows you how to write a middleware that automates the removal of namespaces from keywords.

Clojure: making missing environment variables fail at compile time


Have you ever deployed something to production only to have it break because of a missing environment variable? You can eliminate this risk with a simple macro.

Heroku: buildpack for Clojure tools


Over the past 4 years I've had the fortune of working full time in Clojure. The backend for the Relish mobile app is built in Clojure. It runs on Heroku and we use lein as our build tool. This has been a great development experience. But, for the next Clojure project I wanted to try tools.deps and tools.build. Unfortunately, there isn't an official Heroku buildpack and none of the unofficial alternatives were quite what I was looking for. In the end I decided to roll my own to get comfortable with Heroku's build pack API.

Clojure: compiling java source with tools.build


Recently I stumbled over an old Java project from 2011. I wanted to see if I could get it to run. However, the original program had a bunch of IDE related build files that I no longer understood. What if I used Clojure to build the project? The fruit of that journey is covered in this blog post.

Clojure: check if instant happened today at timezone


Say you are making a digital advent calendar app. You want users to get a special reward on the days that they open your app. But only once per day and only on the days they open the app. This sounds straight forward. What about time zones? What about users who open the app on the 1st of December at 23:55 and then on the 2nd of December at 00:03? Time is tricky.

Emacs: the joy of reducing workflow friction with elisp


Emacs is an interactive, self-documenting and extensible elisp interpreter. This makes it surprisingly enjoyable to extend. It goes something like this: you notice some friction when using a command, you use Emacs' self-documenting features to learn about the command, you investigate the source, you write some elisp, you evaluate it and you try the new and improved command out (all without ever having to restart Emacs).

Emacs: building from source on macOS


I've always wanted to build Emacs from source as it lets you try new features. Native compilation in particular was something I wanted to explore. Native compilation leverages libgccjit to compile Emacs lisp code to native code making Emacs very snappy.

Clojure: code highlights for this website


When I changed the styling of this website I removed code highlights as part of an exercise in minimalism. Anyway, I recently read this awesome article about writing a Clojure highlighter from scratch in 160 lines which inspired me to add some very basic highlights back to this site. This post is about implementing my own Clojure highlighter from scratch in 8 lines (and a fraction of the functionality).

Clojure: website link checker


Writing a simple website link checker in Clojure for fun and profit. Clojure has this nifty function called re-seq that returns a lazy sequence of successive matches of a pattern in a string we can combine this with recursion to write a primitive website link checker.

Clojure: map-occurrence


Sometimes you want behaviour that differs based on the number of times an item has been seen in a sequence. Clojure doesn't come with a function that does this. Before you say "What about frequencies?", frequencies gives you the total number of occurrences in a sequence of items, not the occurrence count.

Clojure: ensuring multimethods are required


Multimethods are fantastic. They give you polymorphism without objects or classes (the best part of Object Oriented without the baggage), multiple dispatch, dynamic dispatch and strong decoupling that allows you to extend code without modifying it (open closed principle), this even extends to third party code. This decoupling is so good that it's not unheard of to deploy your system without all the defmethod extensions being required! This post will teach you how to prevent this.

Clojure: destructive macros


In this post we'll cover writing a macro that supports destructuring and does something with the bindings. More specifically we will write a macro that makes building maps from arbitrary data less verbose.

Clojure: crawling Hacker News with re-seq


Clojure has this nifty function called re-seq that returns a lazy sequence of successive matches of a pattern in a string. This is really useful for turning any string into a list of data. Let's use it to crawl Hacker News!

Clojure: cond-merge revisited


In this post we created a macro called cond-merge to conditionally associate in values to a map. In this post we will revisit some of the limitations of cond-merge when it comes to nested conditionals and conditionals that return maps that can lead to overwriting data rather than merging data.

Clojure: adding dissoc-in to our cond-merge macro


In the previous post we created a macro called cond-merge to conditionally associate in values to a map. In this post we will cover adding disassociation (removal of items) to this macro.

Clojure: cond->, deep-merge, remove-nils and the shape of data


This article will cover various ways of conditionally hydrating/decorating an existing map with additional data. We'll explore different approaches and how they affect readability of the code as well as performance.

Clojure: string similarity


Sometimes you want to compare strings for similarity. To do this we can use cosine similarity to determine how similar two strings are to each other.

Clojure: adding compile time errors with macros


Clojure is a dynamic language. But, something you might not know is that unlike a lot of other dynamic languages it's not an interpreted language it's actually compiled. Even when programming at the REPL the Java Virtual Machine's (JVM) Just In Time (JIT) compiler compiles Clojure code to byte code before evaluating it. Combining this with macros which are evaluated at compile time allows us to add compile time errors to our code.

Clojure: previous, current and next


This article will cover a common pattern of iterating over a list of items whilst keeping a reference to the previous, current and next item.

Clojure: jdbc using any and all as alternatives to in


next-jdbc uses parameterised queries to prevent SQL Injections. These queries can take parameters by passing question marks (?) in the query and then by replacing each question mark index with required values. However this can make some sql operators more challenging to use programmatically. In particular in(?,?,?). In this post we cover using postgresql's any(?) and all(?) to get around this.

Emacs: setting up Apheleia to use Prettier and Zprint


Apheleia is an awesome Emacs package for formatting code quickly using external command line tools like prettier. Not only is it fast but it also does a great job at maintaining point position relative to its surroundings (which has been one of my minor frustrations with other Emacs formatters). It's also language agnostic so you can use it with any language that has a command line formatting tool. This post will cover setting up apheleia with prettier (for JavaScript) and zprint (for Clojure).

Homebrew: write your own brew formula


GraalVM is a recent development in the Java ecosystem that allows you to generate native binaries for applications that run on the Java Virtual Machine (JVM). One of the main advantages of this is that it gets around the JVMs slow startup time which is a problem for short lived programs that are run often. This has lead to a projects like zprint releasing native binaries. This is great but, it doesn't give you a nice reproducible way to install/manage/uninstall these executables. For that we want a package manager like homebrew.

Clojure: code formatting pre-commit hook with zprint


As a codebase and the team working on it grows, it helps to keep the formatting of a project consistent. This makes the code easier to read and removes the need for unnecessary formatting debates on pull requests. One way to achieve this is to add a formatting check to your continuous integration pipeline. The problem with this solution is that it can add unnecessary overhead. You push a commit, the continuous integration job fails due to incorrect formatting, you fix the formatting issue, and push a new commit. Why can't the formatter just fix the issue itself and save you the trouble? This article will cover setting up pre-commit to automatically format Clojure code with zprint to achieve a more streamlined workflow.

Clojure: java interop with bean


One of the great things with Clojure is that it has fantastic java interop. In this article we explore the bean function and how it makes working with java objects more idiomatic.

Clojure: code as data


In Clojure, the primary representation of code is the S-expression that takes the form of nested sequences (lists and vectors). The majority of Clojure's functions are designed to operate on sequences. As a result, Clojure code can be manipulated using the same functions that are used to manipulate Clojure data. In other words, there is no distinction between code and data. Code is data. This property is known as homoiconicity. This article will explore this concept.

Clojure: persistent rate limiting


Some business needs require you to limit the number of times you do something. An example of this would be sending a daily email to users. You could achieve this by making sure you run the function only once per day. However, if that function were to crash part way through how would you know which users had already been sent their daily email and which hadn't? Resolving this without sending some users multiple emails could be a large time sink. A more robust solution would be to make the email sending function idempotent; meaning the effects of the function are applied only once per user per day and any additional applications do nothing. This article will explore one approach to solving this problem in Clojure.

Clojure: permutations


I was solving an Advent of Code problem and part of the solution required generating all the permutations of a set of numbers. Permutation is the act of arranging the members of a set into different orders. I wanted to write a recursive solution rather than an imperative solution. After some searching I found a Scheme implementation. As Scheme and Clojure are both dialects of Lisp, translating one into the other wasn't too difficult.

Ruby: functional programming


In one of my previous jobs I worked as a full stack engineer on a codebase with a Ruby backend and a Javascript/React frontend. Having used Clojure a fair bit in my spare time I was keen to code in a functional style. At first glance this seems tricky in Ruby as it doesn't have first class functions.

Clojure: flattening key paths


This article will cover how to flatten the nested key paths of a map in Clojure; turning a nested map like {:a {:b {:c {:d 1} :e 2}}} into a flat map like {:a-b-c-d 1 :a-b-e 2}.

Clojure: manipulating HTML and XML with zippers


Clojure provides a powerful namespace for manipulating HTML/XML called clojure.zip. It uses the concept of functional zipper (see Functional Pearls The zipper) to make manipulating hierarchical data structures simple and efficient. This article will cover how to use zippers to manipulate HTML/XML in Clojure.

Clojure: sorting tuples


A tuple is a finite ordered sequence of elements. A common use of tuples in Clojure is for representing pairs of data that are related; for example a key and a value when mapping over a map (hashmap/dictionary). That being said, tuples can be of any length and are a common way of representing larger sets of related data in languages that don't use maps as prolifically as Clojure. This articles explores sorting tuples in Clojure.

Clojure: generating HTML and XML


HTML and XML are ubiquitous, whether it's the pages of a static site or configuration for a logger, being able to programmatically generate these files can be really powerful. This article will cover how to generate HTML and XML files in Clojure.

Clojure: using java.time with clojure.java.jdbc


Java 8 introduced java.time to replace the existing java representations of time java.util.Date, java.sql.Timestamp etc. There were many problems with this old implementation: it wasn't thread safe, it had a clunky API and no built in representation for timezones. java.time is the successor to the fantastic joda.time project which solves all these problems. So if java.time fixes everything then why this article? Well, java.sql.Timestamp still rears its ugly head at the database layer where it is still the default representation of time in the java.jdbc database drivers. In this article we will cover how to automate the conversion between java.sql.Timestamp and java.time so you never have to deal with java.sql.Timestamp again.

Clojure: connection pooling with hikari-cp


Connection pooling is a technique for improving app performance. A pool (cache) of reusable connections is maintained meaning when users connect to the database they can reuse an existing connection. When the user finishes using the connection it is placed back in the pool for other users to use. This reduces the overhead of connecting to the database by decreasing network traffic, limiting the cost of creating new connections, and reducing the load on the garbage collector. Effectively improving the responsiveness of your app for any task that requires connecting to the database.

Clojure: emoji in strings


Sometimes your problem domain requires the use of emoji. This article will cover how emoji are represented in Clojure strings.

Clojure: a debug macro for threading macros using tap>


This article will cover how to make a debug macro using tap. See this article for an introduction to Clojure 1.10's tap system.

Clojure: intro to tap> and accessing private vars


Clojure 1.10 introduced a new system called tap. From the release notes: tap is a shared, globally accessible system for distributing a series of informational or diagnostic values to a set of (presumably effectful) handler functions. It can be used as a better debug prn, or for facilities like logging etc.

Clojure: sorting a sequence based on another sequence


Sometimes web responses contain an unordered sequence of items along with a separate manifest that specifies the ordering. This article will cover how you can write a function to sort the list of items by the manifest order as well as using Clojure Spec to generate test data to verify its output.

Clojure: personalising text


Sometimes you want to make a user's experience feel more personal. An easy way to achieve this is by personalising text based content. For example in a text base adventure game you could replace placeholders in the text with information relevant to that particular player such as their name or class. This could help make your game more engaging.

Clojure: case conversion and boundaries


Inconsistent case is a problems that tends to come up at application boundaries in your software stack. For example your Clojure codebase might use kebab-case for keywords, whilst your database uses snake_case for column names and your client wants camelCase in its json responses. Often, conventions and/or technical limitations prevent you from simply having a single case throughout your entire stack.

Clojure: contains? and some


Checking for containment, whether a collection contains an item, is a common task in software development. This post covers the ways you can check for containment in Clojure.

Clojure: sorting


Sorting collections of items is something that comes up frequently in software development. This post covers the multitude of ways you can sort things in Clojure.

Lisp-1 vs Lisp-2


The Lisp family of languages is relatively new to me. I learned both Clojure and Emacs Lisp at the same time, as Emacs is a popular Clojure editor. Learning these two lisps side by side has made me wonder about the subtle differences between the two, in particular how they approach passing functions as arguments to other functions (first class functions). It turns out this boils down to Clojure being a lisp-1 and Emacs Lisp a lisp-2.

Clojure: merging maps by key (join)


We have two sequences of maps (tables) and we want to merge them on matching keys (relations).

Clojure: string interpolation


We have a URL with some placeholder values that we want to replace.

Clojure: sending emails with SendGrid


Your business needs you to generate an automated email report containing data from your app. In this example we will use the SendGrid web API to email a .csv file.

Clojure: validating phone numbers


Sometimes you need to validate phone numbers, googlei18n/libphonenumber is a Java library for doing just that (and more). Thanks to Clojure's great java interop using this library in your project is straightforward.

Clojure: juxt and separate


Juxt is one of those higher-order functions that you never knew you needed. Despite not using it that often I find it can still be surprisingly useful. Let's check out the docs.

Clojure: map-values and map-keys


This post covers some useful Clojure functions for transforming the keys and values of maps.

Desert island code: compose and pipe


You awake a castaway on a desert island. After some time you come across an ancient computation device, the programming of which might hold your salvation!

Desert island code: curry


You awake a castaway on a desert island. After some time you come across an ancient computation device, the programming of which might hold your salvation!

Desert island code: reduce map and filter


You awake a castaway on a desert island. After some time you come across an ancient computation device. The programming of which might hold your salvation!

Managing obfuscation with annotations


Obfuscation is when you deliberately make source code difficult to read. Often code is obfuscated to conceal its purpose and deter reverse engineering. Most obfuscation tools do this by replacing class, method and field names with gibberish.

Using Proguard instead of multidex


One of the downsides of using MultiDex to overcome "The 65k limit" is that build times can increase significantly. Another option is to use ProGuard. ProGuard overcomes "The 65k limit" by removing unused method references, this can make a big difference if you are using large third party libraries like Guava. If configured correctly (disabling optimization/obfuscation) ProGuard can have little to no negative impact on your build times (in the case of larger project it can even decrease build time).

Signing your app


Android requires that all apps be digitally signed with a certificate before they can be installed. To install a release version of your app on a device it will need to signed. Thankfully signing an app is relatively straightforward.

Introduction to Kotlin on Android


Java is a very verbose language. The simplest tasks often require writing a lot of boiler plate code. Arguably, a lot of this code can be generated by IDEs like IntelliJ IDEA or Android Studio. Although, this eases the burden on the writer, it doesn't ease it on the reader. As a developer, you spend a lot more of your time reading code than writing it. This is where I find the additional cognitive overhead of boiler plate code has a habit of stymying development.

Setting up Retrolambda


Java 8 introduces the much awaited lambda expression and method reference. Unfortunately, at the time of writing, Android does not support Java 8. Thankfully, there is a project called Retrolambda that allows you to backport lambda expressions and method references to Java 7. As Android uses the Gradle build system, this guide will be using the Retrolambda gradle plugin to add Retrolambda to an Android project.

Enabling multidex on Android


As the codebase of an Android app continues to grow, it will eventually hit “The 65K limit”. This limit is characterised by the following build error:

Binding Android views with Butter Knife


As Android apps get more complex activities tend to get cluttered with calls to findViewById(id). This unnecessary boilerplate takes time to write and makes code harder to read. Butter Knife to the rescue!

Advantages of an Android free zone


In Android projects I like to set up an “Android Free Zone”. This is a Java module that doesn’t have any dependencies on the Android libraries. This is where the business logic of the app lives.

\ No newline at end of file +" http-equiv="Content-Security-Policy" />

Clojure: structured concurrency and scoped values


In this post we'll explore some useful tools for making working with virtual threads easier. Structured concurrency helps eliminate common problems like thread leaks and cancellation delays. Scoped values let you extend parent thread based context to child threads so you can treat a group of threads as a single unit of work with the same shared context.

Clojure: managing throughput with virtual threads


Before the introduction of Java 21 virtual threads we were heavy users of claypoole a fantastic library for simple but effective parallelism using thread pools. However, things get complicated with virtual threads, they shouldn't be pooled, as they aren't a scarce resource. So how do we limit throughput when we have "unlimited" threads? In this post we will look at using java Semaphore class to implement basic token bucket rate limiting to control throughput.

Clojure: pruning HTML with clojure.walk


A problem that comes up when web crawling is you get a lot of data that you don't necessarily care about: layout divs, scripts, classes, ids etc. Thankfully, Clojure comes with tools that make removing the data you don't care about straight forward.

Emacs: streaming radio with emms


One of the few things I miss about commuting by car is the radio. Unfortunately, I don't have a radio at home. But I do have Emacs. Surely Emacs can do radio?

Clojure: the REPL makes contributing to open source easy


Clojure has a great interactive development experience. This makes it surprisingly easy to contribute to open source. It goes something like this: you're using an open source library, you run into a potential bug, you clone the library, you write some code, you evaluate it and you see if the bug is fixed (all without ever having to restart your REPL).

Clojure: clj-kondo datalog linting


clj-kondo has had really nice datalog syntax checking for a while. However, on some projects you might have wrapped the underlying datalog query function q or be using a datalog implementation clj-kondo doesn't know about. Thankfully, it's trivial to add linting support to any implementation of the query function q as long as it conforms to the Datalog dialect of Datomic.

Clojure: virtual threads with ring and jetty


Java 19 introduce virtual threads as a preview feature. Virtual threads are lightweight threads that allow you to write server applications in a simple thread-per-request style (as opposed to async style) and still scale with near-optimal hardware utilisation. The thread-per-request approach is generally easier to reason about, maintain and debug than it's asynchronous counterparts. In this post we'll cover configuring Jetty to make your ring handlers use virtual threads and benchmark it against regular threads to show how it alleviates the need for tuning thread pools.

Clojure: virtual threads with ring and http-kit


Java 19 introduce virtual threads as a preview feature. Virtual threads are lightweight threads that allow you to write server applications in a simple thread-per-request style (as opposed to async style) and still scale with near-optimal hardware utilisation. The thread-per-request approach is generally easier to reason about, maintain and debug than it's asynchronous counterparts. In this post we'll cover configuring http-kit to make your ring handlers use virtual threads and benchmark it against regular threads to show how it alleviates the need for tuning thread pools.

Fennel: making PICO-8 games


I recently wrote a game jam game for PICO-8 using Fennel. This post goes over setting up a project to write PICO-8 games in Fennel.

Clojure: SQLite application defined SQL functions with JDBC


SQLite provides a convenient interface called Application-Defined SQL Functions that lets you create custom SQL functions that call back into your application code to compute their results. This lets us extend SQLite in Clojure. Want to query with regex or jsonpath? Want custom aggregation functions? Don't want to write C? Don't want to manage different precompiled binaries for different operating systems. No problem Application Defined SQL Functions have you covered.

SQLite: building from source on macOS


I've been playing around with Project Panama to explore building a simpler and lighter SQLite driver to use from Clojure. This required downloading and compiling SQlite from source for macOS/OS X.

Clojure: sending emails with postal and Gmail SMTP


While services like SendGrid are easy to use and offer a plethora of features like bounce and open rate monitoring, they are not the cheapest option. Gmail's SMTP service on the other hand, although not as feature rich, is more affordable. In this post we will cover setting up Gmail SMTP with postal.

Clojure: pretty print strings


clojure.pprint/pprint can be used to print Clojure data structures in a pleasing, easy-to-understand format. However, sometimes you are not printing directly to *out*, for example when using third party logging libraries/services. Thankfully, we can also use clojure.pprint/pprint generate a pretty string by wrapping it in with-out-str.

Clojure: extend honeysql to support postgres 'alter column' and 'add constraint'


Honeysql is an amazing library that lets you write SQL using clojure data structures. This makes SQL much more composable as you no longer need to smash strings together. That being said, when using it with postgresql there are some features that don't work out of the box. Thankfully, honeysql is very easy to extend. This post will show you how to add support for alter column and add constraint.

Clojure: removing namespace from keywords in response middleware


Namespaced keywords let you add a namespace to a keyword :id -> :user/id, this is a really nice feature that prevents collisions when merging two maps. For example merging {:account/id "foo"} with {:user/id "bar"}) would give you {:account/id "foo" :user/id "bar"}. This lets you avoid unnecessary data nesting and keep your data flat. However, by the time this data gets to the edge of your system, where it meets something that expects json, you will often want to remove these namespaces. This post shows you how to write a middleware that automates the removal of namespaces from keywords.

Clojure: making missing environment variables fail at compile time


Have you ever deployed something to production only to have it break because of a missing environment variable? You can eliminate this risk with a simple macro.

Heroku: buildpack for Clojure tools


Over the past 4 years I've had the fortune of working full time in Clojure. The backend for the Relish mobile app is built in Clojure. It runs on Heroku and we use lein as our build tool. This has been a great development experience. But, for the next Clojure project I wanted to try tools.deps and tools.build. Unfortunately, there isn't an official Heroku buildpack and none of the unofficial alternatives were quite what I was looking for. In the end I decided to roll my own to get comfortable with Heroku's build pack API.

Clojure: compiling java source with tools.build


Recently I stumbled over an old Java project from 2011. I wanted to see if I could get it to run. However, the original program had a bunch of IDE related build files that I no longer understood. What if I used Clojure to build the project? The fruit of that journey is covered in this blog post.

Clojure: check if instant happened today at timezone


Say you are making a digital advent calendar app. You want users to get a special reward on the days that they open your app. But only once per day and only on the days they open the app. This sounds straight forward. What about time zones? What about users who open the app on the 1st of December at 23:55 and then on the 2nd of December at 00:03? Time is tricky.

Emacs: the joy of reducing workflow friction with elisp


Emacs is an interactive, self-documenting and extensible elisp interpreter. This makes it surprisingly enjoyable to extend. It goes something like this: you notice some friction when using a command, you use Emacs' self-documenting features to learn about the command, you investigate the source, you write some elisp, you evaluate it and you try the new and improved command out (all without ever having to restart Emacs).

Emacs: building from source on macOS


I've always wanted to build Emacs from source as it lets you try new features. Native compilation in particular was something I wanted to explore. Native compilation leverages libgccjit to compile Emacs lisp code to native code making Emacs very snappy.

Clojure: code highlights for this website


When I changed the styling of this website I removed code highlights as part of an exercise in minimalism. Anyway, I recently read this awesome article about writing a Clojure highlighter from scratch in 160 lines which inspired me to add some very basic highlights back to this site. This post is about implementing my own Clojure highlighter from scratch in 8 lines (and a fraction of the functionality).

Clojure: website link checker


Writing a simple website link checker in Clojure for fun and profit. Clojure has this nifty function called re-seq that returns a lazy sequence of successive matches of a pattern in a string we can combine this with recursion to write a primitive website link checker.

Clojure: map-occurrence


Sometimes you want behaviour that differs based on the number of times an item has been seen in a sequence. Clojure doesn't come with a function that does this. Before you say "What about frequencies?", frequencies gives you the total number of occurrences in a sequence of items, not the occurrence count.

Clojure: ensuring multimethods are required


Multimethods are fantastic. They give you polymorphism without objects or classes (the best part of Object Oriented without the baggage), multiple dispatch, dynamic dispatch and strong decoupling that allows you to extend code without modifying it (open closed principle), this even extends to third party code. This decoupling is so good that it's not unheard of to deploy your system without all the defmethod extensions being required! This post will teach you how to prevent this.

Clojure: destructive macros


In this post we'll cover writing a macro that supports destructuring and does something with the bindings. More specifically we will write a macro that makes building maps from arbitrary data less verbose.

Clojure: crawling Hacker News with re-seq


Clojure has this nifty function called re-seq that returns a lazy sequence of successive matches of a pattern in a string. This is really useful for turning any string into a list of data. Let's use it to crawl Hacker News!

Clojure: cond-merge revisited


In this post we created a macro called cond-merge to conditionally associate in values to a map. In this post we will revisit some of the limitations of cond-merge when it comes to nested conditionals and conditionals that return maps that can lead to overwriting data rather than merging data.

Clojure: adding dissoc-in to our cond-merge macro


In the previous post we created a macro called cond-merge to conditionally associate in values to a map. In this post we will cover adding disassociation (removal of items) to this macro.

Clojure: cond->, deep-merge, remove-nils and the shape of data


This article will cover various ways of conditionally hydrating/decorating an existing map with additional data. We'll explore different approaches and how they affect readability of the code as well as performance.

Clojure: string similarity


Sometimes you want to compare strings for similarity. To do this we can use cosine similarity to determine how similar two strings are to each other.

Clojure: adding compile time errors with macros


Clojure is a dynamic language. But, something you might not know is that unlike a lot of other dynamic languages it's not an interpreted language it's actually compiled. Even when programming at the REPL the Java Virtual Machine's (JVM) Just In Time (JIT) compiler compiles Clojure code to byte code before evaluating it. Combining this with macros which are evaluated at compile time allows us to add compile time errors to our code.

Clojure: previous, current and next


This article will cover a common pattern of iterating over a list of items whilst keeping a reference to the previous, current and next item.

Clojure: jdbc using any and all as alternatives to in


next-jdbc uses parameterised queries to prevent SQL Injections. These queries can take parameters by passing question marks (?) in the query and then by replacing each question mark index with required values. However this can make some sql operators more challenging to use programmatically. In particular in(?,?,?). In this post we cover using postgresql's any(?) and all(?) to get around this.

Emacs: setting up Apheleia to use Prettier and Zprint


Apheleia is an awesome Emacs package for formatting code quickly using external command line tools like prettier. Not only is it fast but it also does a great job at maintaining point position relative to its surroundings (which has been one of my minor frustrations with other Emacs formatters). It's also language agnostic so you can use it with any language that has a command line formatting tool. This post will cover setting up apheleia with prettier (for JavaScript) and zprint (for Clojure).

Homebrew: write your own brew formula


GraalVM is a recent development in the Java ecosystem that allows you to generate native binaries for applications that run on the Java Virtual Machine (JVM). One of the main advantages of this is that it gets around the JVMs slow startup time which is a problem for short lived programs that are run often. This has lead to a projects like zprint releasing native binaries. This is great but, it doesn't give you a nice reproducible way to install/manage/uninstall these executables. For that we want a package manager like homebrew.

Clojure: code formatting pre-commit hook with zprint


As a codebase and the team working on it grows, it helps to keep the formatting of a project consistent. This makes the code easier to read and removes the need for unnecessary formatting debates on pull requests. One way to achieve this is to add a formatting check to your continuous integration pipeline. The problem with this solution is that it can add unnecessary overhead. You push a commit, the continuous integration job fails due to incorrect formatting, you fix the formatting issue, and push a new commit. Why can't the formatter just fix the issue itself and save you the trouble? This article will cover setting up pre-commit to automatically format Clojure code with zprint to achieve a more streamlined workflow.

Clojure: java interop with bean


One of the great things with Clojure is that it has fantastic java interop. In this article we explore the bean function and how it makes working with java objects more idiomatic.

Clojure: code as data


In Clojure, the primary representation of code is the S-expression that takes the form of nested sequences (lists and vectors). The majority of Clojure's functions are designed to operate on sequences. As a result, Clojure code can be manipulated using the same functions that are used to manipulate Clojure data. In other words, there is no distinction between code and data. Code is data. This property is known as homoiconicity. This article will explore this concept.

Clojure: persistent rate limiting


Some business needs require you to limit the number of times you do something. An example of this would be sending a daily email to users. You could achieve this by making sure you run the function only once per day. However, if that function were to crash part way through how would you know which users had already been sent their daily email and which hadn't? Resolving this without sending some users multiple emails could be a large time sink. A more robust solution would be to make the email sending function idempotent; meaning the effects of the function are applied only once per user per day and any additional applications do nothing. This article will explore one approach to solving this problem in Clojure.

Clojure: permutations


I was solving an Advent of Code problem and part of the solution required generating all the permutations of a set of numbers. Permutation is the act of arranging the members of a set into different orders. I wanted to write a recursive solution rather than an imperative solution. After some searching I found a Scheme implementation. As Scheme and Clojure are both dialects of Lisp, translating one into the other wasn't too difficult.

Ruby: functional programming


In one of my previous jobs I worked as a full stack engineer on a codebase with a Ruby backend and a Javascript/React frontend. Having used Clojure a fair bit in my spare time I was keen to code in a functional style. At first glance this seems tricky in Ruby as it doesn't have first class functions.

Clojure: flattening key paths


This article will cover how to flatten the nested key paths of a map in Clojure; turning a nested map like {:a {:b {:c {:d 1} :e 2}}} into a flat map like {:a-b-c-d 1 :a-b-e 2}.

Clojure: manipulating HTML and XML with zippers


Clojure provides a powerful namespace for manipulating HTML/XML called clojure.zip. It uses the concept of functional zipper (see Functional Pearls The zipper) to make manipulating hierarchical data structures simple and efficient. This article will cover how to use zippers to manipulate HTML/XML in Clojure.

Clojure: sorting tuples


A tuple is a finite ordered sequence of elements. A common use of tuples in Clojure is for representing pairs of data that are related; for example a key and a value when mapping over a map (hashmap/dictionary). That being said, tuples can be of any length and are a common way of representing larger sets of related data in languages that don't use maps as prolifically as Clojure. This articles explores sorting tuples in Clojure.

Clojure: generating HTML and XML


HTML and XML are ubiquitous, whether it's the pages of a static site or configuration for a logger, being able to programmatically generate these files can be really powerful. This article will cover how to generate HTML and XML files in Clojure.

Clojure: using java.time with clojure.java.jdbc


Java 8 introduced java.time to replace the existing java representations of time java.util.Date, java.sql.Timestamp etc. There were many problems with this old implementation: it wasn't thread safe, it had a clunky API and no built in representation for timezones. java.time is the successor to the fantastic joda.time project which solves all these problems. So if java.time fixes everything then why this article? Well, java.sql.Timestamp still rears its ugly head at the database layer where it is still the default representation of time in the java.jdbc database drivers. In this article we will cover how to automate the conversion between java.sql.Timestamp and java.time so you never have to deal with java.sql.Timestamp again.

Clojure: connection pooling with hikari-cp


Connection pooling is a technique for improving app performance. A pool (cache) of reusable connections is maintained meaning when users connect to the database they can reuse an existing connection. When the user finishes using the connection it is placed back in the pool for other users to use. This reduces the overhead of connecting to the database by decreasing network traffic, limiting the cost of creating new connections, and reducing the load on the garbage collector. Effectively improving the responsiveness of your app for any task that requires connecting to the database.

Clojure: emoji in strings


Sometimes your problem domain requires the use of emoji. This article will cover how emoji are represented in Clojure strings.

Clojure: a debug macro for threading macros using tap>


This article will cover how to make a debug macro using tap. See this article for an introduction to Clojure 1.10's tap system.

Clojure: intro to tap> and accessing private vars


Clojure 1.10 introduced a new system called tap. From the release notes: tap is a shared, globally accessible system for distributing a series of informational or diagnostic values to a set of (presumably effectful) handler functions. It can be used as a better debug prn, or for facilities like logging etc.

Clojure: sorting a sequence based on another sequence


Sometimes web responses contain an unordered sequence of items along with a separate manifest that specifies the ordering. This article will cover how you can write a function to sort the list of items by the manifest order as well as using Clojure Spec to generate test data to verify its output.

Clojure: personalising text


Sometimes you want to make a user's experience feel more personal. An easy way to achieve this is by personalising text based content. For example in a text base adventure game you could replace placeholders in the text with information relevant to that particular player such as their name or class. This could help make your game more engaging.

Clojure: case conversion and boundaries


Inconsistent case is a problems that tends to come up at application boundaries in your software stack. For example your Clojure codebase might use kebab-case for keywords, whilst your database uses snake_case for column names and your client wants camelCase in its json responses. Often, conventions and/or technical limitations prevent you from simply having a single case throughout your entire stack.

Clojure: contains? and some


Checking for containment, whether a collection contains an item, is a common task in software development. This post covers the ways you can check for containment in Clojure.

Clojure: sorting


Sorting collections of items is something that comes up frequently in software development. This post covers the multitude of ways you can sort things in Clojure.

Lisp-1 vs Lisp-2


The Lisp family of languages is relatively new to me. I learned both Clojure and Emacs Lisp at the same time, as Emacs is a popular Clojure editor. Learning these two lisps side by side has made me wonder about the subtle differences between the two, in particular how they approach passing functions as arguments to other functions (first class functions). It turns out this boils down to Clojure being a lisp-1 and Emacs Lisp a lisp-2.

Clojure: merging maps by key (join)


We have two sequences of maps (tables) and we want to merge them on matching keys (relations).

Clojure: string interpolation


We have a URL with some placeholder values that we want to replace.

Clojure: sending emails with SendGrid


Your business needs you to generate an automated email report containing data from your app. In this example we will use the SendGrid web API to email a .csv file.

Clojure: validating phone numbers


Sometimes you need to validate phone numbers, googlei18n/libphonenumber is a Java library for doing just that (and more). Thanks to Clojure's great java interop using this library in your project is straightforward.

Clojure: juxt and separate


Juxt is one of those higher-order functions that you never knew you needed. Despite not using it that often I find it can still be surprisingly useful. Let's check out the docs.

Clojure: map-values and map-keys


This post covers some useful Clojure functions for transforming the keys and values of maps.

Desert island code: compose and pipe


You awake a castaway on a desert island. After some time you come across an ancient computation device, the programming of which might hold your salvation!

Desert island code: curry


You awake a castaway on a desert island. After some time you come across an ancient computation device, the programming of which might hold your salvation!

Desert island code: reduce map and filter


You awake a castaway on a desert island. After some time you come across an ancient computation device. The programming of which might hold your salvation!

Managing obfuscation with annotations


Obfuscation is when you deliberately make source code difficult to read. Often code is obfuscated to conceal its purpose and deter reverse engineering. Most obfuscation tools do this by replacing class, method and field names with gibberish.

Using Proguard instead of multidex


One of the downsides of using MultiDex to overcome "The 65k limit" is that build times can increase significantly. Another option is to use ProGuard. ProGuard overcomes "The 65k limit" by removing unused method references, this can make a big difference if you are using large third party libraries like Guava. If configured correctly (disabling optimization/obfuscation) ProGuard can have little to no negative impact on your build times (in the case of larger project it can even decrease build time).

Signing your app


Android requires that all apps be digitally signed with a certificate before they can be installed. To install a release version of your app on a device it will need to signed. Thankfully signing an app is relatively straightforward.

Introduction to Kotlin on Android


Java is a very verbose language. The simplest tasks often require writing a lot of boiler plate code. Arguably, a lot of this code can be generated by IDEs like IntelliJ IDEA or Android Studio. Although, this eases the burden on the writer, it doesn't ease it on the reader. As a developer, you spend a lot more of your time reading code than writing it. This is where I find the additional cognitive overhead of boiler plate code has a habit of stymying development.

Setting up Retrolambda


Java 8 introduces the much awaited lambda expression and method reference. Unfortunately, at the time of writing, Android does not support Java 8. Thankfully, there is a project called Retrolambda that allows you to backport lambda expressions and method references to Java 7. As Android uses the Gradle build system, this guide will be using the Retrolambda gradle plugin to add Retrolambda to an Android project.

Enabling multidex on Android


As the codebase of an Android app continues to grow, it will eventually hit “The 65K limit”. This limit is characterised by the following build error:

Binding Android views with Butter Knife


As Android apps get more complex activities tend to get cluttered with calls to findViewById(id). This unnecessary boilerplate takes time to write and makes code harder to read. Butter Knife to the rescue!

Advantages of an Android free zone


In Android projects I like to set up an “Android Free Zone”. This is a Java module that doesn’t have any dependencies on the Android libraries. This is where the business logic of the app lives.

\ No newline at end of file diff --git a/docs/sitemap.xml b/docs/sitemap.xml index 5782d883..770736f4 100644 --- a/docs/sitemap.xml +++ b/docs/sitemap.xml @@ -1 +1 @@ -/2024-05-07/2024/05/06/clojure-managing-throughput-with-virtual-threads.html2024-05-06/2024/04/06/clojure-ci-with-github-actions-and-postgres.html2024-04-06/2024/04/01/clojure-pruning-html-with-clojure-walk.html2024-04-01/2024/03/31/emacs-streaming-radio-with-emms.html2024-03-31/2024/03/04/clojure-the-repl-makes-contributing-to-open-source-easy.html2024-03-04/2024/01/03/clojure-clj-kondo-datalog-linting.html2024-01-03/2023/09/16/clojure-virtual-threads-with-ring-and-jetty.html2023-09-16/2023/09/15/clojure-virtual-threads-with-ring-and-http-kit.html2023-09-15/2023/09/08/fennel-making-pico-8-games.html2023-09-08/2023/07/16/clojure-sqlite-application-defined-sql-functions-with-jdbc.html2023-07-16/2023/07/09/sqlite-building-from-source-on-macos.html2023-07-09/2022/06/14/clojure-sending-emails-with-postal-and-gmail-smtp.html2022-06-14/2022/05/22/clojure-pretty-print-strings.html2022-05-22/2022/05/02/clojure-extend-honeysql-to-support-postgres-alter-column-and-add-constraint.html2022-05-02/2022/03/27/clojure-removing-namespace-from-keywords-in-response-middleware.html2022-03-27/2022/03/20/clojure-making-missing-environment-variables-fail-at-compile-time.html2022-03-20/2022/03/19/heroku-buildpack-for-clojure-tools.html2022-03-19/2021/12/12/clojure-compiling-java-source-with-tools-build.html2021-12-12/2021/12/04/clojure-check-if-instant-happened-today-at-timezone.html2021-12-04/2021/11/30/emacs-the-joy-of-reducing-workflow-friction-with-elisp.html2021-11-30/2021/11/14/emacs-building-from-source-on-macos.html2021-11-14/2021/11/11/clojure-code-highlights-for-this-website.html2021-11-11/2021/10/31/clojure-website-link-checker.html2021-10-31/2021/10/30/clojure-map-occurrence.html2021-10-30/2021/10/24/clojure-ensuring-multimethods-are-required.html2021-10-24/2021/07/18/clojure-destructive-macros.html2021-07-18/2021/07/17/clojure-crawling-hackernews-with-re-seq.html2021-07-17/2020/12/30/clojure-cond-merge-revisited.html2020-12-30/2020/12/29/clojure-adding-dissoc-in-to-the-cond-merge-macro.html2020-12-29/2020/12/27/clojure-cond-deep-merge-remove-nils-and-the-shape-of-data.html2020-12-27/2020/12/13/clojure-string-similarity.html2020-12-13/2020/11/11/clojure-adding-compile-time-errors-with-macros.html2020-11-11/2020/10/11/clojure-previous-current-and-next.html2020-10-11/2020/09/06/clojure-jdbc-using-any-and-all-as-an-alternative-to-in.html2020-09-06/2020/08/20/emacs-setting-up-apheleia-to-use-zprint.html2020-08-20/2020/08/18/homebrew-write-your-own-brew-formula.html2020-08-18/2020/08/16/clojure-code-formatting-pre-commit-hook-with-zprint.html2020-08-16/2020/03/27/clojure-java-interop-with-bean.html2020-03-27/2020/03/01/clojure-code-as-data.html2020-03-01/2020/02/08/clojure-persistent-rate-limiting.html2020-02-08/2019/12/19/clojure-permutations.html2019-12-19/2019/12/07/ruby-functional-programming.html2019-12-07/2019/11/30/clojure-flattening-key-paths.html2019-11-30/2019/11/17/clojure-manipulating-html-and-xml-with-zippers.html2019-11-17/2019/10/27/clojure-sorting-tuples.html2019-10-27/2019/09/08/clojure-generating-html-and-xml.html2019-09-08/2019/08/03/clojure-using-java-time-with-jdbc.html2019-08-03/2019/07/14/clojure-connection-pooling-with-hikari-cp.html2019-07-14/2019/07/02/clojure-emoji-in-strings.html2019-07-02/2019/06/04/clojure-a-debug-macro-for-threading-macros-using-tap.html2019-06-04/2019/06/01/clojure-intro-to-tap-and-accessing-private-vars.html2019-06-01/2019/05/25/clojure-sorting-a-sequence-based-on-another-sequence.html2019-05-25/2019/05/18/clojure-personalising-text.html2019-05-18/2019/05/04/clojure-case-conversion-and-boundaries.html2019-05-04/2019/04/05/clojure-contains-and-some.html2019-04-05/2019/03/09/clojure-sorting.html2019-03-09/2019/03/08/lisp-1-vs-lisp-2.html2019-03-08/2019/02/16/clojure-merging-maps-by-key.html2019-02-16/2019/01/15/clojure-string-interpolation.html2019-01-15/2019/01/06/clojure-sending-emails-with-sendgrid.html2019-01-06/2018/11/24/clojure-validating-phone-numbers.html2018-11-24/2018/11/18/clojure-juxt-and-separate.html2018-11-18/2018/11/10/clojure-map-values-and-keys.html2018-11-10/2018/01/04/desert-island-code-compose-and-pipe.html2018-01-04/2018/01/02/desert-island-code-curry.html2018-01-02/2017/12/28/desert-island-code-reduce-map-and-filter.html2017-12-28/2016/10/08/managing-obfuscation-with-annotations.html2016-10-08/2016/05/19/using-proguard-instead-of-multidex.html2016-05-19/2016/05/17/signing-your-app.html2016-05-17/2015/10/06/introduction-to-kotlin-on-android.html2015-10-06/2015/09/16/setting-up-retrolambda.html2015-09-16/2015/09/10/enabling-multidex-on-android.html2015-09-10/2015/09/02/binding-android-views-with-butter-knife.html2015-09-02/2015/08/27/advantages-of-an-android-free-zone.html2015-08-27 \ No newline at end of file +/2024-05-14/2024/05/14/clojure-structured-concurrency-and-scoped-values.html2024-05-14/2024/05/06/clojure-managing-throughput-with-virtual-threads.html2024-05-06/2024/04/06/clojure-ci-with-github-actions-and-postgres.html2024-04-06/2024/04/01/clojure-pruning-html-with-clojure-walk.html2024-04-01/2024/03/31/emacs-streaming-radio-with-emms.html2024-03-31/2024/03/04/clojure-the-repl-makes-contributing-to-open-source-easy.html2024-03-04/2024/01/03/clojure-clj-kondo-datalog-linting.html2024-01-03/2023/09/16/clojure-virtual-threads-with-ring-and-jetty.html2023-09-16/2023/09/15/clojure-virtual-threads-with-ring-and-http-kit.html2023-09-15/2023/09/08/fennel-making-pico-8-games.html2023-09-08/2023/07/16/clojure-sqlite-application-defined-sql-functions-with-jdbc.html2023-07-16/2023/07/09/sqlite-building-from-source-on-macos.html2023-07-09/2022/06/14/clojure-sending-emails-with-postal-and-gmail-smtp.html2022-06-14/2022/05/22/clojure-pretty-print-strings.html2022-05-22/2022/05/02/clojure-extend-honeysql-to-support-postgres-alter-column-and-add-constraint.html2022-05-02/2022/03/27/clojure-removing-namespace-from-keywords-in-response-middleware.html2022-03-27/2022/03/20/clojure-making-missing-environment-variables-fail-at-compile-time.html2022-03-20/2022/03/19/heroku-buildpack-for-clojure-tools.html2022-03-19/2021/12/12/clojure-compiling-java-source-with-tools-build.html2021-12-12/2021/12/04/clojure-check-if-instant-happened-today-at-timezone.html2021-12-04/2021/11/30/emacs-the-joy-of-reducing-workflow-friction-with-elisp.html2021-11-30/2021/11/14/emacs-building-from-source-on-macos.html2021-11-14/2021/11/11/clojure-code-highlights-for-this-website.html2021-11-11/2021/10/31/clojure-website-link-checker.html2021-10-31/2021/10/30/clojure-map-occurrence.html2021-10-30/2021/10/24/clojure-ensuring-multimethods-are-required.html2021-10-24/2021/07/18/clojure-destructive-macros.html2021-07-18/2021/07/17/clojure-crawling-hackernews-with-re-seq.html2021-07-17/2020/12/30/clojure-cond-merge-revisited.html2020-12-30/2020/12/29/clojure-adding-dissoc-in-to-the-cond-merge-macro.html2020-12-29/2020/12/27/clojure-cond-deep-merge-remove-nils-and-the-shape-of-data.html2020-12-27/2020/12/13/clojure-string-similarity.html2020-12-13/2020/11/11/clojure-adding-compile-time-errors-with-macros.html2020-11-11/2020/10/11/clojure-previous-current-and-next.html2020-10-11/2020/09/06/clojure-jdbc-using-any-and-all-as-an-alternative-to-in.html2020-09-06/2020/08/20/emacs-setting-up-apheleia-to-use-zprint.html2020-08-20/2020/08/18/homebrew-write-your-own-brew-formula.html2020-08-18/2020/08/16/clojure-code-formatting-pre-commit-hook-with-zprint.html2020-08-16/2020/03/27/clojure-java-interop-with-bean.html2020-03-27/2020/03/01/clojure-code-as-data.html2020-03-01/2020/02/08/clojure-persistent-rate-limiting.html2020-02-08/2019/12/19/clojure-permutations.html2019-12-19/2019/12/07/ruby-functional-programming.html2019-12-07/2019/11/30/clojure-flattening-key-paths.html2019-11-30/2019/11/17/clojure-manipulating-html-and-xml-with-zippers.html2019-11-17/2019/10/27/clojure-sorting-tuples.html2019-10-27/2019/09/08/clojure-generating-html-and-xml.html2019-09-08/2019/08/03/clojure-using-java-time-with-jdbc.html2019-08-03/2019/07/14/clojure-connection-pooling-with-hikari-cp.html2019-07-14/2019/07/02/clojure-emoji-in-strings.html2019-07-02/2019/06/04/clojure-a-debug-macro-for-threading-macros-using-tap.html2019-06-04/2019/06/01/clojure-intro-to-tap-and-accessing-private-vars.html2019-06-01/2019/05/25/clojure-sorting-a-sequence-based-on-another-sequence.html2019-05-25/2019/05/18/clojure-personalising-text.html2019-05-18/2019/05/04/clojure-case-conversion-and-boundaries.html2019-05-04/2019/04/05/clojure-contains-and-some.html2019-04-05/2019/03/09/clojure-sorting.html2019-03-09/2019/03/08/lisp-1-vs-lisp-2.html2019-03-08/2019/02/16/clojure-merging-maps-by-key.html2019-02-16/2019/01/15/clojure-string-interpolation.html2019-01-15/2019/01/06/clojure-sending-emails-with-sendgrid.html2019-01-06/2018/11/24/clojure-validating-phone-numbers.html2018-11-24/2018/11/18/clojure-juxt-and-separate.html2018-11-18/2018/11/10/clojure-map-values-and-keys.html2018-11-10/2018/01/04/desert-island-code-compose-and-pipe.html2018-01-04/2018/01/02/desert-island-code-curry.html2018-01-02/2017/12/28/desert-island-code-reduce-map-and-filter.html2017-12-28/2016/10/08/managing-obfuscation-with-annotations.html2016-10-08/2016/05/19/using-proguard-instead-of-multidex.html2016-05-19/2016/05/17/signing-your-app.html2016-05-17/2015/10/06/introduction-to-kotlin-on-android.html2015-10-06/2015/09/16/setting-up-retrolambda.html2015-09-16/2015/09/10/enabling-multidex-on-android.html2015-09-10/2015/09/02/binding-android-views-with-butter-knife.html2015-09-02/2015/08/27/advantages-of-an-android-free-zone.html2015-08-27 \ No newline at end of file diff --git a/resources/posts/2024-05-14-clojure-structured-concurrency-and-scoped-values.md b/resources/posts/2024-05-14-clojure-structured-concurrency-and-scoped-values.md new file mode 100644 index 00000000..e04a34bf --- /dev/null +++ b/resources/posts/2024-05-14-clojure-structured-concurrency-and-scoped-values.md @@ -0,0 +1,375 @@ +title: Clojure: structured concurrency and scoped values + +In this post we'll explore some useful tools for making working with virtual threads easier. Structured concurrency helps eliminate common problems like thread leaks and cancellation delays. Scoped values let you extend parent thread based context to child threads so you can treat a group of threads as a single unit of work with the same shared context. + +## Enable preview + +Structured concurrency and scoped values are available in java 21 as preview features, so we'll need to enable preview: + +```clojure +{:paths ["src"] + :deps {org.clojure/clojure {:mvn/version "1.12.0-alpha11"}} + :aliases +{:dev {:jvm-opts ["--enable-preview"]}}} +``` + +## Example code + +We'll be implementing our own version of `pmap` as it has a clear thread hierarchy which is exactly the sort of place both structured concurrency and scoped values are useful: + +```clojure +(ns server.core + (:refer-clojure :exclude [pmap]) + (:import + (java.util.concurrent + ExecutorService + Executors + Callable))) + +(defonce executor + (Executors/newVirtualThreadPerTaskExecutor)) + +(defn pmap [f coll] + (->> (mapv (fn [x] (ExecutorService/.submit executor + ;; More than one matching method found: submit + ;; So we need to type hint Callable + ^Callable (fn [] (f x)))) + coll) + (mapv deref))) +``` + +Let's run this code and make one of the tasks cause an exception: + +```clojure +(pmap (fn [x] + (let [result (inc x)] + (Thread/sleep 50) ;; simulate some io + (print (str "complete " result "\n")) + result)) + [1 2 "3" 4 5 6]) + +=> Error printing return value (ClassCastException) +at clojure.lang.Numbers/inc (Numbers.java:139). +class java.lang.String cannot be cast to class +java.lang.Number (java.lang.String and java.lang.Number +are in module java.base of loader 'bootstrap') + +complete 7 +complete 2 +complete 5 +complete 4 +complete 6 +``` + +Despite one of the tasks causing an exception all the other tasks keep running +and complete. This might not be the behaviour we want, particularly if we require all tasks to succeed. + +This is where structured concurrency comes in. + +>Simplify concurrent programming by introducing an API for structured concurrency. Structured concurrency 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. +> +> - JEP 462 + +## Structured Task Scope + +First let's import the classes we will need from `StructuredTaskScope`: + +```clojure +(ns server.core + (:refer-clojure :exclude [pmap]) + (:import + (java.util.concurrent ++ StructuredTaskScope ++ StructuredTaskScope$Subtask ++ StructuredTaskScope$ShutdownOnFailure ++ StructuredTaskScope$ShutdownOnSuccess + ExecutorService + Executors + Callable))) +``` + +When dealing with concurrent subtasks it is common to use short-circuiting patterns to avoid doing unnecessary work. Currently, `StructuredTaskScope` provides two shutdown policies `ShutdownOnFailure` and `ShutdownOnSuccess`. These policies shut down the scope when the first subtask fails or succeeds, respectively. + +We're going to explore the `ShutdownOnFailure` shutdown policy first. + +Let's redefine our `pmap` function: + +```clojure +(defn pmap [f coll] + (with-open [scope (StructuredTaskScope$ShutdownOnFailure/new)] + (let [r (mapv (fn [x] + (StructuredTaskScope/.fork scope + (fn [] (f x)))) + coll)] + ;; join subtasks and propagate errors + (.. scope join throwIfFailed) + ;; fork returns a Subtask/Supplier not a future + (mapv StructuredTaskScope$Subtask/.get r)))) +``` + +Then run this new version with one task causing an exception: + +```clojure +(pmap (fn [x] + (let [result (inc x)] + (Thread/sleep 50) + (print (str "complete " result "\n")) + result)) + [1 2 "3" 4 5 6]) + +=> Error printing return value (ClassCastException) +at clojure.lang.Numbers/inc (Numbers.java:139). +class java.lang.String cannot be cast to class +java.lang.Number (java.lang.String and java.lang.Number +are in module java.base of loader 'bootstrap') +``` + +As you can see the other threads are shutdown before they run/complete. Note: this depends on execution order and task completion time. Some threads might complete before the exception occurs. + +Next lets look at the `ShutdownOnSuccess` shutdown policy. This policy works well in a situation where you only care about one of the results. For example reaching out to three data providers that provide the same data (for redundancy). + +We are going to implement a function called `alts` that will take the first completed task from a sequence of tasks being executed in parallel. Only failing if all tasks fail. + +```clojure +(defn alts [f coll] + (with-open [scope (StructuredTaskScope$ShutdownOnSuccess/new)] + (run! (fn [x] + (StructuredTaskScope/.fork scope (fn [] (f x)))) + coll) + ;; Throws if none of the subtasks completed successfully + (.. scope join result))) +``` + +Let's run `alts` and make one of the tasks cause an exception: + +```clojure +(alts (fn [x] + (let [result (inc x)] + (Thread/sleep 100) + (print (str "complete " result "\n")) + result)) + [1 2 "3" 4 5 6]) + +=> +complete 2 +complete 4 + +2 +``` + +We can see two of the tasks manage to complete, the rest are shutdown and only one result is returned. + +Structured concurrency is a really nice addition to Java. It's great for automatically handling thread cancellation which can help keep latency down and avoid thread leaks in the case of error. + +That being said it's not a natural fit for all use cases. Sometimes you do want unstructured concurrency, like in my previous post on [Clojure: managing throughput with virtual threads](https://andersmurphy.com/2024/05/06/clojure-managing-throughput-with-virtual-threads.html) where `upmap` produces tasks in one thread and consumes their results in another. + +Something I haven't covered but plan on covering in a future post is that `StructuredTaskScope` can be extended to implement your own shutdown policies. + +## Dynamic var binding conveyance + +Before we get on to scoped values lets explore Clojure's existing mechanism for thread bound state: dynamic vars. Dynamic vars implement a nice feature called binding conveyance which means thread context gets passed to futures and agents spawned by the parent thread. However, because `StructuredTaskScope` returns `StructuredTaskScope$Subtask`/`Supplier` and not a `future` we don't get binding conveyance automatically: + +```clojure +(def ^:dynamic *inc-amount* nil) + +(binding [*inc-amount* 3] + (pmap (fn [x] + (let [result (+ x *inc-amount*)] + (Thread/sleep 50) + (print (str "complete " result "\n")) + result)) + [1 2 3 4 5 6])) + +=> Execution error (NullPointerException) +at server.core/eval3782$fn (REPL:6). +Cannot invoke "Object.getClass()" because "x" is null +``` + +The task threads do not inherit the value of the `*inc-aomunt*` binding so we get an error. Thankfully, this is easy to fix with the `bound-fn*` function. A higher order function that transfers the current bindings to the new thread: + +```clojure +(binding [*inc-amount* 3] + (pmap ++ (bound-fn* + (fn [x] + (let [result (+ x *inc-amount*)] + (Thread/sleep 50) + (print (str "complete " result "\n")) + result))) + [1 2 3 4 5 6])) + +=> complete 9 +complete 6 +complete 7 +complete 5 +complete 4 +complete 8 + +[4 5 6 7 8 9] +``` + +Binding conveyance now works as we would expect. + +## Scoped Values + +This brings us to scoped values. These are similar to Clojure's dynamic vars and Java's thread-local variables but designed for use with virtual threads. + +>Scoped values, values that may be safely and efficiently shared to methods without using method parameters. They are preferred to thread-local variables, especially when using large numbers of virtual threads. This is a preview API. +> +> - JEP 446 + +With the following stated goals: + +>**Goals** +> +> - **Ease of use** — Provide a programming model to share data both within a thread >and with child threads, so as to simplify reasoning about data flow. +> +> - **Comprehensibility** — Make the lifetime of shared data visible from the syntactic >structure of code. +> +> - **Robustness** — Ensure that data shared by a caller can be retrieved only by legitimate callees. +> +> - **Performance** — Allow shared data to be immutable so as to allow sharing by a large number of threads, and to enable runtime optimizations. + +First let's import the classes we will need from `ScopedValue`: + +```clojure +(ns server.core + (:refer-clojure :exclude [pmap]) ++ (java.lang ScopedValue) + (:import + (java.util.concurrent + StructuredTaskScope + StructuredTaskScope$Subtask + StructuredTaskScope$ShutdownOnFailure + StructuredTaskScope$ShutdownOnSuccess + ExecutorService + Executors + Callable))) +``` + +Scoped values have conveyance built in as this is the behaviour that makes the most sense with hierarchical tasks: + +>Subtasks forked in a scope inherit ScopedValue bindings (JEP 446). If a scope's owner reads a value from a bound ScopedValue then each subtask will read the same value. +> +> - JEP 462 + +Let's see how to use a single scoped value: + +```clojure +(def scoped-inc-amount (ScopedValue/newInstance)) + +(ScopedValue/getWhere scoped-inc-amount 3 + (delay + (pmap (fn [x] + (let [result (+ x (ScopedValue/.get scoped-inc-amount))] + (Thread/sleep 50) + (print (str "complete " result "\n")) + result)) + [1 2 3 4 5 6]))) + +=> complete 4 +complete 6 +complete 9 +complete 8 +complete 5 +complete 7 + +[4 5 6 7 8 9] +``` + +It's worth pointing out the use of `delay` to satisfy the `Supplier` interface. This is a recent and welcome addition in Clojure 1.12 (see [CLJ-2792](https://clojure.atlassian.net/browse/CLJ-2792)). Effectively it avoids us having to `reify` `Supplier`: + +```clojure +(ScopedValue/getWhere scoped-inc-amount 3 + (reify Supplier + (get [_] + (pmap (fn [x] + (let [result (+ x (ScopedValue/.get scoped-inc-amount))] + (Thread/sleep 50) + (print (str "complete " result "\n")) + result)) + [1 2 3 4 5 6])))) +``` + +Now let's see how we set multiple scoped values: + +```clojure +(def scoped-dec-amount (ScopedValue/newInstance)) + +(.. (ScopedValue/where scoped-inc-amount 3) + (ScopedValue/where scoped-dec-amount -2) + (ScopedValue/get + (delay + (pmap (fn [x] + (let [result (+ x + (ScopedValue/.get scoped-inc-amount) + (ScopedValue/.get scoped-dec-amount))] + (Thread/sleep 50) + (print (str "complete " result "\n")) + result)) + [1 2 3 4 5 6])))) + +=> complete 4 +complete 2 +complete 3 +complete 5 +complete 7 +complete 6 + +[2 3 4 5 6 7] +``` + +Finally, let's make this more ergonomic by writing a convenience macro for scoped values called `scoped-binding` that mirrors Clojure's `binding` macro: + +```clojure +(defmacro scoped-binding [bindings & body] + (assert (vector? bindings) + "a vector for its binding") + (assert (even? (count bindings)) + "an even number of forms in binding vector") + `(.. ~@(->> (partition 2 bindings) + (map (fn [[k v]] + (assert (-> k resolve deref type (= ScopedValue)) + (str k " is not a ScopedValue")) + `(ScopedValue/where ~k ~v)))) + (ScopedValue/get (delay ~@body)))) +``` + +And see if it works: + +```clojure +(scoped-binding [scoped-inc-amount 3 + scoped-dec-amount -2] + (pmap (fn [x] + (let [result (+ x + (ScopedValue/.get scoped-inc-amount) + (ScopedValue/.get scoped-dec-amount))] + (Thread/sleep 50) + (print (str "complete " result "\n")) + result)) + [1 2 3 4 5 6])) + +=> complete 4 +complete 6 +complete 9 +complete 8 +complete 5 +complete 7 + +[4 5 6 7 8 9] +``` + +Great, we now have all the tools for using scoped values in Clojure. + +Yet again we've seen how Clojure's seamless and constantly improving integration with Java makes exploring the latest Java features effortless, and thanks to macros we can even improve on the Java experience. + +The full example [project can be found here](https://github.com/andersmurphy/clj-cookbook/tree/master/virtual-threads/structured-concurrency). + +**Further Reading:** + +- [JEP 462: Structured Concurrency](https://openjdk.org/jeps/462) +- [JEP 446: Scoped Values](https://openjdk.org/jeps/446) +- [Binding conveyance](https://clojure.org/reference/vars#conveyance) +- [bound-fn*](https://clojuredocs.org/clojure.core/bound-fn*) +- [Java Supplier interop CLJ-2792](https://clojure.atlassian.net/browse/CLJ-2792) +- [On the Perils of Dynamic Scope](https://stuartsierra.com/2013/03/29/perils-of-dynamic-scope) diff --git a/src/core.clj b/src/core.clj index cff0f469..e34c08c3 100644 --- a/src/core.clj +++ b/src/core.clj @@ -339,3 +339,5 @@ style-src 'self' 'unsafe-inline' (generate-site) ) + +