<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Howard M. Lewis Ship</title>
    <description>The latest articles on Forem by Howard M. Lewis Ship (@hlship).</description>
    <link>https://forem.com/hlship</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F144922%2Faf3bb668-3465-40f3-82e3-a7bf9939275d.jpeg</url>
      <title>Forem: Howard M. Lewis Ship</title>
      <link>https://forem.com/hlship</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/hlship"/>
    <language>en</language>
    <item>
      <title>Datastar Observations</title>
      <dc:creator>Howard M. Lewis Ship</dc:creator>
      <pubDate>Sat, 07 Feb 2026 23:35:22 +0000</pubDate>
      <link>https://forem.com/hlship/datastar-observations-3icg</link>
      <guid>https://forem.com/hlship/datastar-observations-3icg</guid>
      <description>&lt;p&gt;I've been very impressed, so far, with &lt;a href="https://data-star.dev" rel="noopener noreferrer"&gt;Datastar&lt;/a&gt;, a tiny JavaScript library for front-end work; I've been switching a &lt;a href="https://github.com/hlship/dialog-tool" rel="noopener noreferrer"&gt;personal side-project&lt;/a&gt; from using &lt;a href="https://svelte.dev/" rel="noopener noreferrer"&gt;Svelte&lt;/a&gt; for it's UI to Datastar, and as amazing as Svelte is, Datastar has impressed me more.&lt;/p&gt;

&lt;p&gt;Datastar's essential concept is for the client to shift virtually all logic and all markup rendering back to the server; event handlers can succinctly call server endpoints, which return markup, and the markup is morphed into the running DOM. This makes the server-side is the system of record. Datastar has a nice DSL, based on &lt;code&gt;data-*&lt;/code&gt; attributes, allowing you to do nearly anything you need to do in the client, declaratively.&lt;/p&gt;

&lt;p&gt;Alternately, the server can start an SSE (server sent event) stream and send down markup to morph into the DOM, or JavaScript to execute, over any period of time. For example, my project has a long running process and it was a snap to create a modal progress dialog and keep it updated as the server-side process looped through its inputs.&lt;/p&gt;

&lt;p&gt;The mantra of Datastar is to &lt;em&gt;trust the morph and the browser&lt;/em&gt; -- it's surprisingly fast to update even when sending a fair bit of content. It feels wasteful to update a whole page just to change a few small things (say, mark a button as disabled) but it works, and it's fast, and it frees you from a nearly all client-side reactive updates (and all the related edge cases and unforseen consequences).&lt;/p&gt;

&lt;p&gt;The server side is not bound to any particular language or framework (they have API implementations for Clojure, Java, Python, Ruby, and many others) ... and you could probably write your own API in an afternoon.&lt;/p&gt;

&lt;p&gt;I especially like side-stepping the issue of needing more representations of data; the data lives server-side, all that is ever sent to the client is markup. There's no over-the-wire representation, and no parallel client-side data model. All that's ever exposed as endpoints are intentional ones that do work and deliver markup ... in other words, always use-case based, never schema based. &lt;/p&gt;

&lt;p&gt;There's a minimal amount of reactive logic in the client, but the essence of moving the logic to server feels like home; Tapestry (way back in 2005) had some similar ideas, but was far more limited (due to many factors, including JavaScript and browser maturity in that time).&lt;/p&gt;

&lt;p&gt;I value simplicity, and Datastar looks to fit my needs without doing so much that is magical or hidden. I consider that a big win!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>clojure</category>
    </item>
    <item>
      <title>Moving to deps.edn</title>
      <dc:creator>Howard M. Lewis Ship</dc:creator>
      <pubDate>Thu, 09 Dec 2021 01:05:34 +0000</pubDate>
      <link>https://forem.com/hlship/moving-to-depsedn-1474</link>
      <guid>https://forem.com/hlship/moving-to-depsedn-1474</guid>
      <description>&lt;p&gt;I've been using Clojure for ... some time now; I think I started experimenting with it in 2009, possibly earlier. At both Aviso and Walmart I have used, and often fought with, &lt;a href="https://leiningen.org/" rel="noopener noreferrer"&gt;Leiningen&lt;/a&gt;, the standard build tool. &lt;/p&gt;

&lt;p&gt;Finally, I'm changing over to using &lt;code&gt;clj&lt;/code&gt; and &lt;a href="https://clojure.org/guides/deps_and_cli" rel="noopener noreferrer"&gt;deps.edn&lt;/a&gt;. As is often the case, the inertia of using the tool you know can get in the way of learning a tool that may just fit your needs better.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;deps&lt;/em&gt; is another case of Clojure's emphasis on data and simplicity, and on &lt;em&gt;explicitness&lt;/em&gt; as a better goal than &lt;em&gt;conciseness&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;At its core, &lt;em&gt;deps&lt;/em&gt; does two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build the classpath (resolving dependencies and downloading as needed)&lt;/li&gt;
&lt;li&gt;Start a Clojure instance and execute a function using the classpath&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's a few bells and whistles built on top of these two essentials, such as &lt;em&gt;aliases&lt;/em&gt; to adjust the classpath and the executed function and function arguments, such as to launch a test runner.&lt;/p&gt;

&lt;p&gt;Most importantly, though, deps's more narrow focus makes it easier to understand what's going on as it executes, and to build new and customized functionality that every project seems to need.&lt;/p&gt;

&lt;p&gt;For Lacinia (and my other open source projects), I had a goal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Store the version number of the library in &lt;em&gt;one&lt;/em&gt; place&lt;/li&gt;
&lt;li&gt;Include that version number in &lt;a href="https://github.com/weavejester/codox" rel="noopener noreferrer"&gt;generated documentation&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Leiningen, your basic meta-data (&lt;code&gt;defproject&lt;/code&gt;) includes the version number right there, accessible to any plugins.  With deps, a version number doesn't really make sense ... remember, we're just putting together the classpath and running some Clojure code.&lt;/p&gt;

&lt;p&gt;So, if it comes to building and publishing a JAR, you need to implement some project-level build tools.&lt;/p&gt;

&lt;p&gt;You do this by defining a &lt;code&gt;:build&lt;/code&gt; alias:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;;; clj -T:build &amp;lt;command&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="no"&gt;:build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:deps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;io.github.clojure/tools.build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:git/tag&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"v0.7.4"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:git/sha&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"ac442da"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                 &lt;/span&gt;&lt;span class="n"&gt;slipset/deps-deploy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"0.2.0"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="no"&gt;:ns-default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The -T option alias also includes the current directory as a source, so we can create a &lt;code&gt;build.clj&lt;/code&gt; file there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;clojure.tools.build.api&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;deps-deploy.deps-deploy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'com.walmartlabs/lacinia&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"1.1-alpha-7"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;class-dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"target/classes"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;jar-file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"target/%s-%s.jar"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;copy-srcs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"src"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"resources"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;clean&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_params&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;b/delete&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;jar&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_params&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;basis&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;b/create-basis&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;b/write-pom&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:class-dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;class-dir&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="no"&gt;:lib&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="no"&gt;:version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="no"&gt;:basis&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;basis&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="no"&gt;:src-dirs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"src"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="no"&gt;:resource-dirs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"resources"&lt;/span&gt;&lt;span class="p"&gt;]})&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;b/copy-dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:src-dirs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;copy-srcs&lt;/span&gt;&lt;span class="w"&gt;
                 &lt;/span&gt;&lt;span class="no"&gt;:target-dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;class-dir&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;b/jar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:class-dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;class-dir&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="no"&gt;:jar-file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;jar-file&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;println&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Created:"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;jar-file&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;deploy&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_params&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;clean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;jar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;d/deploy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:installer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:remote&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="no"&gt;:artifact&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;jar-file&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="no"&gt;:pom-file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;b/pom-path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:lib&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:class-dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;class-dir&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="no"&gt;:sign-releases?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="no"&gt;:sign-key-id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;System/getenv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"CLOJARS_GPG_ID"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;throw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;RuntimeException.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"CLOJARS_GPG_ID environment variable not set"&lt;/span&gt;&lt;span class="p"&gt;)))}))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each function here becomes a runnable command, such as &lt;code&gt;clj -T:build clean&lt;/code&gt;. Each of these functions accepts a &lt;code&gt;params&lt;/code&gt; map and returns it (these are values provided as command arguments, read as Clojure forms, and assembled into a map of keys and values).&lt;/p&gt;

&lt;p&gt;Core to all of this is &lt;code&gt;clojure.tools.build.api/create-basis&lt;/code&gt;; a basis is a description of the features of the project, including the classpath with source directories and third party dependencies resolved and expanded.  Once you have the basis, other &lt;code&gt;build.api&lt;/code&gt; commands are used to generate Jar files and deploy to Clojars.&lt;/p&gt;

&lt;p&gt;The above &lt;code&gt;build.clj&lt;/code&gt; code is good so far ... a bit of boilerplate, but it is reasonably expressive and not too verbose.  It's very easy to see how to customize this for different projects.&lt;/p&gt;

&lt;p&gt;But back to my challenge - I want to generate documentation with the right version number. Normally, Codox is invoked via an alias defined in &lt;code&gt;deps.edn&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;;; clj -Xdev:codox&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="no"&gt;:codox&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:extra-deps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;codox/codox&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"0.10.7"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="no"&gt;:exec-fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;codox.main/generate-docs&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="no"&gt;:exec-args&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:metadata&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:doc/format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:markdown&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="no"&gt;:name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"com.walmartlabs/lacinia"&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="no"&gt;:version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"1.1-alpha-7"&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="no"&gt;:description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Clojure-native implementation of GraphQL"&lt;/span&gt;&lt;span class="p"&gt;}}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This adds Codox to the classpath along with the project dependencies, and the dependencies of the &lt;code&gt;:dev&lt;/code&gt; alias, and invokes &lt;code&gt;codox.main/generate-docs&lt;/code&gt; to do the actual work.&lt;/p&gt;

&lt;p&gt;Alas, this approach still requires keeping a second copy of the project version synchronized.  &lt;/p&gt;

&lt;p&gt;Ideally, there should be a way to &lt;em&gt;inject&lt;/em&gt; the true version number, from &lt;code&gt;build.clj&lt;/code&gt;, into the &lt;code&gt;:exec-args&lt;/code&gt; above, but that goes against he grain of deps; the contents of &lt;code&gt;deps.edn&lt;/code&gt; are just data; there's no functions to be called to read the version number from somewhere else.&lt;/p&gt;

&lt;p&gt;The next best thing is to have a have a &lt;code&gt;codox&lt;/code&gt; build command, defined in &lt;code&gt;build.clj&lt;/code&gt;, that calls &lt;code&gt;codox.main/generate-docs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I did a few iterations on that approach but there are problems:  the  classpath for &lt;code&gt;-T&lt;/code&gt; commands &lt;em&gt;does not include&lt;/em&gt; the project's classpath, and Codox needs to be able to load namespaces as part of how it organizes and generates the documentation.&lt;/p&gt;

&lt;p&gt;Eventually, I came across the following solution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build a classpath that includes the main dependencies, and Codox&lt;/li&gt;
&lt;li&gt;Build and execute a Java command to run Codox&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It ends up looking like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;codox&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_params&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;basis&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;b/create-basis&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:extra&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:deps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;codox/codox&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:mvn/version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"0.10.8"&lt;/span&gt;&lt;span class="p"&gt;}}}&lt;/span&gt;&lt;span class="w"&gt;
                               &lt;/span&gt;&lt;span class="c1"&gt;;; This is needed because some of the namespaces&lt;/span&gt;&lt;span class="w"&gt;
                               &lt;/span&gt;&lt;span class="c1"&gt;;; rely on optional dependencies provided by :dev&lt;/span&gt;&lt;span class="w"&gt;
                               &lt;/span&gt;&lt;span class="no"&gt;:aliases&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:dev&lt;/span&gt;&lt;span class="p"&gt;]})&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;do&lt;/span&gt;&lt;span class="w"&gt;
                      &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nf"&gt;requiring-resolve&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'codox.main/generate-docs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
                       &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:metadata&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:doc/format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:markdown&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="no"&gt;:name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="no"&gt;:version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="no"&gt;:description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Clojure-native implementation of GraphQL"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
                      &lt;/span&gt;&lt;span class="n"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;process-params&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;b/java-command&lt;/span&gt;&lt;span class="w"&gt;
                         &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:basis&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;basis&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="no"&gt;:main&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"clojure.main"&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="no"&gt;:main-args&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"--eval"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;pr-str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;)]})]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;b/process&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;process-params&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the top, we build a new basis that includes the project's base dependencies, the &lt;code&gt;:dev&lt;/code&gt; alias dependencies, and Codox (and its dependencies). We're going to start a process to run a Java command to execute a Clojure expression - this expression ultimately is what invokes &lt;code&gt;generate-docs&lt;/code&gt; - and &lt;code&gt;build.api&lt;/code&gt; provides all the heavy lifting related to setting up the classpath and starting processes.&lt;/p&gt;

&lt;p&gt;Even though the above code isn't a macro definition, we can still use the Clojure &lt;em&gt;syntax quote&lt;/em&gt; (the backtick) to create a snippet of Clojure code that gets included on the command line to the Java process.&lt;/p&gt;

&lt;p&gt;So, running &lt;code&gt;clj -T:build codox&lt;/code&gt; will invoke the &lt;code&gt;codox&lt;/code&gt; function, which in-turn starts a second process running Java with an entirely different classpath.&lt;/p&gt;

&lt;p&gt;At the end of the day, we have a short command to generate documentation, and our library's version number appears in just one place, &lt;code&gt;build.clj&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I'm just scratching the surface of what can be accomplished using these techniques, but I'm excited at the possibilities of using real Clojure code to tackle some of our interesting build- and deploy-time requirements.&lt;/p&gt;

</description>
      <category>clojure</category>
    </item>
    <item>
      <title>Debugging Clojure at the REPL using tap&gt;</title>
      <dc:creator>Howard M. Lewis Ship</dc:creator>
      <pubDate>Wed, 15 Sep 2021 23:56:26 +0000</pubDate>
      <link>https://forem.com/hlship/debugging-clojure-at-the-repl-using-tap-2pm5</link>
      <guid>https://forem.com/hlship/debugging-clojure-at-the-repl-using-tap-2pm5</guid>
      <description>&lt;p&gt;Sometimes, old habits die hard.  For a long time, I've done a lot of debugging in my Clojure code at the REPL, using &lt;code&gt;prn&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With Clojure, debugging by writing to the console is not as primitive as it may sound; sure, you can fire up the debugger and set a break point, but by injecting a &lt;code&gt;prn&lt;/code&gt; at just the right spot, and with just the right data, you often get just exactly the useful data you need to diagnose a problem.&lt;/p&gt;

&lt;p&gt;Other times, with meatier data structures, I will break out &lt;code&gt;clojure.pprint/pprint&lt;/code&gt; ... but this requires more work, to import the namespace, and means I also have to cleanup the &lt;code&gt;ns&lt;/code&gt; declaration.&lt;/p&gt;

&lt;p&gt;Now, I noticed a ways back that Clojure 1.10 added the &lt;a href="https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/tap%3E" rel="noopener noreferrer"&gt;&lt;code&gt;tap&amp;gt;&lt;/code&gt;&lt;/a&gt; function, and I vaguely knew it would be helpful for this kind of thing; I finally got around to trying it out to debug some hairy NullPointerException bugs in &lt;a href="https://github.com/walmartlabs/lacinia/pull/379" rel="noopener noreferrer"&gt;Lacinia&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What does &lt;code&gt;tap&amp;gt;&lt;/code&gt; do?  By default ... nothing. It's just a function you can pass a value to, but nothing happens when you do (it also returns true).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tap&amp;gt;&lt;/code&gt; is hardly useful until you add a tap with &lt;a href="https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/add-tap" rel="noopener noreferrer"&gt;add-tap&lt;/a&gt;; a tap is any function that takes a single argument.&lt;/p&gt;

&lt;p&gt;Invoking &lt;code&gt;tap&amp;gt;&lt;/code&gt; will cause the function (or functions, if &lt;code&gt;add-tap&lt;/code&gt; is called multiple times) to be invoked asynchronously.&lt;/p&gt;

&lt;p&gt;So if you, for example, &lt;code&gt;(add-tap clojure.pprint/pprint)&lt;/code&gt; each subsequent call to &lt;code&gt;tap&amp;gt;&lt;/code&gt; will be pretty-printed to the console.  And the &lt;code&gt;tap&amp;gt;&lt;/code&gt; function is in core, so no require needed.&lt;/p&gt;

&lt;p&gt;I've found that I don't want to &lt;code&gt;tap&amp;gt;&lt;/code&gt; simple values, because single values do not provide much context into whatever problem I'm trying to solve. Instead I build a map that is tapped as a unit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fn-i-want-to-debug&lt;/span&gt;&lt;span class="w"&gt; 
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;simple-arg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sometimes-nil-but-too-large-to-print-arg&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tap&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="n"&gt;fn-i-want-to-debug&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="no"&gt;:simple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;simple-arg&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="no"&gt;:nil?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;nil?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sometimes-nil-but-too-large-to-print-arg&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="n"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because of course in Clojure, it's always easy to just make a new map on the fly.  Here, the backquote (it's the &lt;a href="https://www.clojure.org/reference/reader#syntax-quote" rel="noopener noreferrer"&gt;syntax quote&lt;/a&gt;) will ensure that the function name is a fully namespace qualified symbol.&lt;/p&gt;

&lt;p&gt;As the above example shows, sometime an argument is too large to effectively print but maybe, for your debugging, you just need to know if it is nil or not ... or maybe you want to &lt;code&gt;dissoc&lt;/code&gt; some keys, or otherwise prepare the data that gets output.  That's fine, that's why REPL oriented development can be faster and better than firing up the debugger.&lt;/p&gt;

&lt;p&gt;A debugger can only show you &lt;em&gt;one&lt;/em&gt; thing at a time; and even using Cursive, it can be a lot of clicks and a lot of squinting at the screen to see exactly what's going on.&lt;/p&gt;

&lt;p&gt;By comparison, console output (via &lt;code&gt;tap&amp;gt;&lt;/code&gt;) can be a little history of what's going on in your program leading up to a problem.  &lt;/p&gt;

&lt;p&gt;Further, nothing is stopping you from using a debugger and &lt;code&gt;tap&amp;gt;&lt;/code&gt; together (but you may find that you don't need that nearly as often as you'd think).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tap&amp;gt;&lt;/code&gt; is better than straight-up calls to &lt;code&gt;prn&lt;/code&gt; or &lt;code&gt;pprint&lt;/code&gt; because you can leave the calls to &lt;code&gt;tap&amp;gt;&lt;/code&gt; in your code with little or no cost; you can &lt;code&gt;add-tap&lt;/code&gt; when you need to ... and in fact, you can also &lt;code&gt;remove-tap&lt;/code&gt; when you want to continue developing without the extra output, all without restarting your REPL.&lt;/p&gt;

&lt;p&gt;That's pretty classic Clojure for you ... a minimal and practical tool that becomes significantly more useful because Clojure and its REPL are so well wedded together.&lt;/p&gt;

</description>
      <category>clojure</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Down the rabbit hole with Clojure, defrecord, and macros</title>
      <dc:creator>Howard M. Lewis Ship</dc:creator>
      <pubDate>Mon, 16 Aug 2021 23:54:16 +0000</pubDate>
      <link>https://forem.com/hlship/down-the-rabbit-hole-with-clojure-defrecord-and-macros-3aal</link>
      <guid>https://forem.com/hlship/down-the-rabbit-hole-with-clojure-defrecord-and-macros-3aal</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Lisp Programmers Knows The Value Of Everything And The Cost Of Nothing - Alan Perlis&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Our team at Walmart are well known for our use of &lt;a href="https://www.youtube.com/watch?v=av9Xi6CNqq4" rel="noopener noreferrer"&gt;Clojure&lt;/a&gt; and &lt;a href="https://www.youtube.com/watch?v=I0vVkQfmy9w" rel="noopener noreferrer"&gt;GraphQL&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As I've said recently, &lt;a href="https://podcasts.apple.com/us/podcast/s4-e32-lacinia-with-howard-lewis-ship/id1461500416?i=1000526576095" rel="noopener noreferrer"&gt;if you are doing it right, you're creating an orchestration layer&lt;/a&gt;, and that's the bread and butter of our team; we're responsible for customer purchase history - your history of in-store transactions and on-line orders at Walmart.  Other teams write the front end (web, iOS, and Android) that allow our customers to view their orders, initiate returns, reorder products, and so forth - our job is to &lt;em&gt;facilitate&lt;/em&gt; the efforts of those front end teams by exposing all the necessary data, sliced and diced and organized for their convenience.&lt;/p&gt;

&lt;p&gt;That means that we need to interact with many (&lt;em&gt;many&lt;/em&gt;) back-end systems, then integrate all the data about the customer, their orders, product information, details about returns ... this list goes on and on.&lt;/p&gt;

&lt;p&gt;And we have to do this fast.  Part of Walmart's home page is based on our service, so we're on a tight performance budget to meet our service level agreements.&lt;/p&gt;

&lt;p&gt;A recent cross-team stress test revealed some problems in our service; partly our cluster was too small for the amount of simulated traffic, but at the same time, we also determined (using &lt;a href="https://www.oracle.com/java/technologies/jdk-mission-control.html" rel="noopener noreferrer"&gt;JDK Mission Control&lt;/a&gt;) that we had a fair amount of CPU bound, not I/O bound, performance issues.&lt;/p&gt;

&lt;p&gt;Part of this was due to some &lt;a href="https://github.com/walmartlabs/lacinia/pull/371" rel="noopener noreferrer"&gt;inefficiencies in Lacinia&lt;/a&gt;, since corrected, but we also found other hot-spots specific to our application's code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenge
&lt;/h2&gt;

&lt;p&gt;One hot-spot in particular was ... interesting.  As I mentioned above, our code does a lot of integrating and organizing data from multiple systems; internally, a major piece of that was the "pre-processed order line", a map that contains a flattening of data from many systems around a single line of a single order; we then use that pre-processed data to organize order lines into categories and groups and generate the final GraphQL structured output.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/candid82" rel="noopener noreferrer"&gt;Roman&lt;/a&gt; defined a &lt;a href="https://clojuredocs.org/clojure.core/defrecord" rel="noopener noreferrer"&gt;&lt;code&gt;defrecord&lt;/code&gt;&lt;/a&gt; for this PreprocessedOrderLine, and just using that had some measurable benefits, but also introduced a new hot-spot: constructing the PreprocessedOrderLine record itself.&lt;/p&gt;

&lt;p&gt;We ended up with three bad options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Create a hash map, then call the &lt;code&gt;map-&amp;gt;PreprocessedOrderLine&lt;/code&gt; constructor function - We're creating a map to avoid the cost of creating a map?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create an empty PreprocessedOrderLine record, then use &lt;code&gt;assoc&lt;/code&gt; to add in the keys and values - We're creating a record then doing 60 mutations on it?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use the &lt;code&gt;-&amp;gt;PreprocessedOrderLine&lt;/code&gt; constructor function, and pass 60 positional arguments into it - Although nominally just a wrapper around &lt;code&gt;new&lt;/code&gt;, if there are &lt;a href="https://github.com/clojure/clojure/blob/a29f9b911b569b0a4890f320ec8f946329bbe0fd/src/jvm/clojure/lang/IFn.java#L89" rel="noopener noreferrer"&gt;more than 20 parameters&lt;/a&gt; Clojure creates a much less efficient variadic function.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which leaves us with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Invoke the constructor directly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But this approach didn't sit well from a maintainability viewpoint; we frequently add new keys (now, new fields) to this record, and have to be very careful at construction time to pass all the parameters to the &lt;code&gt;PreprocessedOrderLine&lt;/code&gt; constructor in &lt;em&gt;exactly the right order&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;What to do?  Well, the nature of problem solving in Clojure is to look at where you are, look at where you want to be, and find the way to bridge that gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bridging That Gap
&lt;/h2&gt;

&lt;p&gt;My thinking was this: What if we had an additional constructor for the record, &lt;code&gt;kv-&amp;gt;PreprocessedOrderLine&lt;/code&gt;, that would accept key/value pairs (like the &lt;code&gt;hash-map&lt;/code&gt; function), but would instead figure out the correct parameters, in the correct order, to invoke the PreprocessedOrderLine instance constructor?&lt;/p&gt;

&lt;p&gt;For this to make sense, we'd need to figure out how to map keyword keys to PreprocessedOrderLine constructor argument positions.  And for this to not be a new hot-spot, the ordering calculation would have to happen once, at compile time, not on each execution of &lt;code&gt;kv-&amp;gt;PreprocessedOrderLine&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Unavoidably, since we're talking about moving what would normally be execution time behavior to compile time, we're talking about writing a macro.&lt;/p&gt;

&lt;p&gt;My goal here was to write a new macro, &lt;code&gt;defkvrecord&lt;/code&gt;, whose purpose would be to do everything that &lt;code&gt;defrecord&lt;/code&gt; does, but then add a third way to construct a record instance, the &lt;code&gt;kv-&amp;gt;&lt;/code&gt; prefixed macro.&lt;/p&gt;

&lt;p&gt;Here's where we'll end up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;defkvrecord&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Point&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="ss"&gt;'user/kv-&amp;gt;Point&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can define our record type, as normal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;kv-&amp;gt;Point&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:z&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="n"&gt;user.Point&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:z&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Evaluating the &lt;code&gt;kv-&amp;gt;Point&lt;/code&gt; macro results in a new instance of Point, and the order of the keys doesn't matter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;macroexpand-1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;kv-&amp;gt;Point&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user.Point&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;macroexpand-1&lt;/code&gt; shows shows how &lt;code&gt;kv-&amp;gt;Point&lt;/code&gt; expands into a &lt;code&gt;new&lt;/code&gt; constructor call. Unspecified fields receive a nil.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;macroexpand-1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;kv-&amp;gt;Point&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:color&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:red&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;clojure.core/assoc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user.Point&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:color&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:red&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When extra keys, not matching a field, are provided, a call to &lt;code&gt;assoc&lt;/code&gt; is used to add those extra keys.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;doc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kv-&amp;gt;Point&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;-------------------------&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;user/kv-&amp;gt;Point&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;key-values&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Macro&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;Factory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user.Point&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;taking&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;keywords&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;values.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The macro has proper documentation.&lt;/p&gt;

&lt;p&gt;It's worthwhile to reflect here that in this scenario, our final goal is to create a macro &lt;em&gt;that defines another custom macro&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;If writing a macro is potentially shooting yourself in the foot, what is this?  I guess it's building a machine to shoot yourself in the foot, automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;It's intimidating to jump from nothing to the final implementation of the &lt;code&gt;defkvrecord&lt;/code&gt; macro; instead, let's break our thinking down into smaller steps.&lt;/p&gt;

&lt;p&gt;First, imagine just writing &lt;code&gt;kv-&amp;gt;Point&lt;/code&gt; by hand, but as a function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;defrecord&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Point&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kv-&amp;gt;Point&lt;/span&gt;&lt;span class="w"&gt; 
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;key-values&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;kv-map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;apply&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;hash-map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;key-values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kv-map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:z&lt;/span&gt;&lt;span class="p"&gt;])]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;apply&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Point.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the general idea; but note that this code won't actually work, - I've used &lt;code&gt;apply&lt;/code&gt; with &lt;code&gt;new&lt;/code&gt;, which won't compile because &lt;code&gt;new&lt;/code&gt; is a special form. Let's ignore that for the moment.&lt;/p&gt;

&lt;p&gt;What's important is that we go from keys and values, to a map, to a correctly ordered list of constructor parameters.&lt;/p&gt;

&lt;p&gt;Next, let's handle the case where there are keys that don't match fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kv-&amp;gt;Point&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;key-values&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field-kws&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:z&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;kv-map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;apply&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;hash-map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;key-values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;extras&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;reduce&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;dissoc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kv-map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;field-kws&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;extra-kvs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;seq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;reduce&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;into&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;extras&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mapv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kv-map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;field-kws&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;apply&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Point.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;extra-kvs&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;apply&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;assoc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;extra-kvs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;extras&lt;/code&gt; is a map of any keys that don't match a field; we convert that to a seq of keys and values as &lt;code&gt;extra-kvs&lt;/code&gt; and, if there are any, we use &lt;code&gt;apply assoc&lt;/code&gt; to add them to the Point record.&lt;/p&gt;

&lt;p&gt;At this point, we're ready to build the &lt;code&gt;kv-&amp;gt;Point&lt;/code&gt; macro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defmacro&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kv-&amp;gt;Point&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;key-values&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field-kws&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;:x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:z&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;kv-map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;apply&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;hash-map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;key-values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;extras&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;reduce&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;dissoc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kv-map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;field-kws&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;extra-kvs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;reduce&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;into&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;extras&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mapv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kv-map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;field-kws&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;concat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user.Point&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;seq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;extra-kvs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;concat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'assoc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;extra-kvs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember, a macro returns forms that will be evaluated; this is often done using the &lt;a href="https://clojure.org/reference/reader#syntax-quote" rel="noopener noreferrer"&gt;syntax quote&lt;/a&gt;, but it is also valid to just build up the forms using &lt;code&gt;list&lt;/code&gt; and &lt;code&gt;concat&lt;/code&gt;, as we've done here.&lt;/p&gt;

&lt;p&gt;Now, in terms of generalizing from this specific record type to any record type, we need to adapt for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The correct &lt;code&gt;kv-&amp;gt;&lt;/code&gt; macro name&lt;/li&gt;
&lt;li&gt;The actual field names&lt;/li&gt;
&lt;li&gt;The actual class name&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In addition, we want &lt;code&gt;(doc kv-&amp;gt;Point)&lt;/code&gt; to work correctly.&lt;/p&gt;

&lt;p&gt;Knowing how we built to this point, the final code should not be too daunting to understand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defmacro&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;defkvrecord&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s"&gt;"Extension to clojure.core/defrecord, but adds an additional macro, kv-&amp;gt;RecordType,
  that accepts keyword keys and values and constructs an instance of the record directly,
  via its constructor.

  This is intended for use with records that have a very large number of fields, and
  where creating a map to invoke the record's map-&amp;gt; constructor is a performance hotspot;
  this code is more manageable than attempting to call the -&amp;gt;RecordType positional
  constructor function, which is untenable when there are more than a few fields.

  The macro is of the form:

    (defmacro kv-&amp;gt;RecordType [&amp;amp; key-values] ...)

  The keys are keywords, and the values are expressions that will be plugged into the new record
  instance via its constructor (when the keyword matches the name of a field of the record)
  or via an optional call to assoc (after the record has been constructed).

  Any field whose value is not supplied as a key value pair is passed to the constructor as nil."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;record-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;opts+specs&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;strip-ns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; 
                   &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;with-meta&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; 
                              &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;meta&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;record-name&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;strip-ns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;record-name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;kv-symbol&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;symbol&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"kv-&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;record-name&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;ns-part&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;namespace-munge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;*ns*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;classname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;symbol&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ns-part&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;record-name&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;field-names&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;strip-ns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;field-kws&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mapv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;keyword&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;do&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;defrecord&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;record-name&lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;~@&lt;/span&gt;&lt;span class="n"&gt;field-names&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~@&lt;/span&gt;&lt;span class="n"&gt;opts+specs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defmacro&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;kv-symbol&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:doc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Factory function for class "&lt;/span&gt;&lt;span class="w"&gt;
                      &lt;/span&gt;&lt;span class="n"&gt;classname&lt;/span&gt;&lt;span class="w"&gt; 
                      &lt;/span&gt;&lt;span class="s"&gt;" taking keywords and field values."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="no"&gt;:arglists&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="ss"&gt;'key-values&lt;/span&gt;&lt;span class="p"&gt;])}&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;key-values&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;kv-map&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;apply&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;hash-map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;key-values&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="n"&gt;extras&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;reduce&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;dissoc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kv-map&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;field-kws&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="n"&gt;extra-kws&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;seq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;reduce&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;into&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;extras&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kv-map&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;field-kws&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;concat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;classname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
           &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;extra-kws&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;reduce&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;concat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;'assoc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;extras&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;))))))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the final version of &lt;code&gt;defkvrecord&lt;/code&gt;; it's more complicated since we need to go from a list of field &lt;em&gt;symbols&lt;/em&gt; to the list of field &lt;em&gt;keywords&lt;/em&gt; and we have do some extra work - duplicating code from &lt;code&gt;defrecord&lt;/code&gt;'s implementation - to work out the class name and other details. &lt;/p&gt;

&lt;p&gt;Still, in 27 lines of code, we've added a significant extension to Clojure's built-in record support, and done so without compromising on performance.&lt;/p&gt;

&lt;p&gt;In the long run, going from &lt;code&gt;map-&amp;gt;PreprocessedOrderLine&lt;/code&gt; to &lt;code&gt;kv-&amp;gt;PreprocessedOrderLine&lt;/code&gt; changed the time to instantiate a single instance from 11.5 microseconds to 104.0 nanoseconds, a speedup of about 99%. Of course, it would be necessary to instantiate hundreds of PreprocessedOrderLines per request to shave even a single millisecond off of the request processing time, which calls into question the usefulness of the entire exercise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;This was a fun journey because the end result was a case of having your cake and eating it too - we got the improved performance without any penalty in terms of the maintainability of our application's code.&lt;/p&gt;

&lt;p&gt;And finally, a bonus tip:  when developing macros, always &lt;code&gt;(set! *print-meta* true)&lt;/code&gt;.  You need to see the meta-data on the forms your macros output!&lt;/p&gt;

</description>
      <category>clojure</category>
      <category>performance</category>
      <category>macros</category>
    </item>
    <item>
      <title>Joker: Flex Your Scripting Muscles In (Almost) Clojure</title>
      <dc:creator>Howard M. Lewis Ship</dc:creator>
      <pubDate>Fri, 07 Jun 2019 20:07:08 +0000</pubDate>
      <link>https://forem.com/hlship/joker-flex-your-scripting-muscles-in-almost-clojure-2ph8</link>
      <guid>https://forem.com/hlship/joker-flex-your-scripting-muscles-in-almost-clojure-2ph8</guid>
      <description>&lt;p&gt;If you read enough dev blogs, and have a touch of imposter syndrome,&lt;br&gt;
it starts to look like our field is full of Uber-coders who can constantly&lt;br&gt;
switch gears between, say, writing machine learning code in Python,&lt;br&gt;
complex user interfaces in JavaScript and React, and then jump down and throw together a quick device driver in C. For fun, I imagine these Uber-coders then go home to write retro-games in 6502 assembly.&lt;/p&gt;

&lt;p&gt;These people probably exist.  I am not one of them. I've coded professionally&lt;br&gt;
in many languages, everything from C, to PL/1, to Java, to Lua. But for day-to-day work, I &lt;em&gt;specialize&lt;/em&gt;. I like &lt;a href="http://clojure.org" rel="noopener noreferrer"&gt;&lt;strong&gt;Clojure&lt;/strong&gt;&lt;/a&gt;. And I'm terrible at switching gears and languages, unlike my mythical Uber-coder.&lt;/p&gt;

&lt;p&gt;Clojure is &lt;em&gt;intensely&lt;/em&gt; good at what it does: REPL oriented development,&lt;br&gt;
functional thinking, data transformation, and leveraging existing Java libraries.&lt;/p&gt;

&lt;p&gt;But you don't write shell scripts in Clojure, even if you are as hopeless at Bash scripting as me. &lt;/p&gt;

&lt;p&gt;Not that you don't want to, it's just that startup time is an obstacle: all the JVM startup time, plus all the Clojure startup time, plus all the time to load whatever libraries you need.&lt;/p&gt;

&lt;p&gt;It adds up.&lt;/p&gt;

&lt;p&gt;If you are working on an application, you may only fire up your REPL once in an entire day, so this startup time is inconsequential. But if you are writing a &lt;em&gt;script&lt;/em&gt; ... either a throwaway, or something to  help you automate your environment, you don't want to be twiddling your thumbs waiting for that script to load (even a one second wait can feel like forever).&lt;/p&gt;

&lt;p&gt;That's why &lt;a href="https://github.com/candid82/joker" rel="noopener noreferrer"&gt;Joker&lt;/a&gt; is such a revelation; Joker is an interpreted dialect of Clojure, written in Google's Go, intended &lt;strong&gt;just&lt;/strong&gt; for scripting: we use it internally on my team for all of our automation.&lt;/p&gt;

&lt;p&gt;Nearly everything that makes Clojure amazing exists in Joker, including immutable data types, macros, and dynamic typing, and the vast majority of Clojure's core library.&lt;/p&gt;

&lt;p&gt;Joker starts up &lt;em&gt;very&lt;/em&gt; quickly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; time joker -e '(println "There and back again")'
There and back again

real    0m0.050s
user    0m0.038s
sys 0m0.017s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's virtually instantaneous. By comparison, Clojure has a noticable start up time:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; time clj -e '(println "A price must be paid")'
A price must be paid

real    0m1.011s
user    0m1.695s
sys 0m0.124s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Better yet, &lt;a href="https://candid82.github.io/joker/" rel="noopener noreferrer"&gt;Joker is batteries included&lt;/a&gt;: it has a selection of built-in libraries, that cover all of your typical tasks, including sending HTTP requests, JSON and YAML encoding, basic cryptography ... all the things you need to, say, work with Amazon AWS.&lt;/p&gt;

&lt;p&gt;One feature near and dear to me is &lt;code&gt;joker.tools.cli&lt;/code&gt;, a port of Clojure's &lt;br&gt;
&lt;a href="https://github.com/clojure/tools.cli" rel="noopener noreferrer"&gt;clojure.tools.cli&lt;/a&gt; library. This is &lt;em&gt;first class&lt;/em&gt; support for GNU-style command line options.&lt;/p&gt;
&lt;h2&gt;
  
  
  Using Joker
&lt;/h2&gt;

&lt;p&gt;Let's say you got yourself a sweet job working for the Department of Defense, and wanted to create a script to control a few things at NORAD:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;You can name this file &lt;code&gt;norad&lt;/code&gt;, and make it executable (&lt;code&gt;chmod u+x norad&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Because we made this a first class script, with properly parsed options, we can ask &lt;code&gt;norad&lt;/code&gt; what it does:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; norad --help
norad OPTIONS

  -d, --defcon VALUE  Set DEFCON level
  -v, --version       Show version information
  -h, --help          Show this summary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a relief; a sloppy script that expected the user to remember its non-standard options might have changed the DEFCON to null, or launched some missiles or something. Real options can be a safety net.&lt;/p&gt;

&lt;p&gt;And even though this script is non-trivial, it's still hella fast:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; time norad --defcon 3
Setting DEFCON to 3

real    0m0.086s
user    0m0.088s
sys 0m0.020s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And as someone who crusades for good &lt;strong&gt;feedback&lt;/strong&gt; from my tools and libraries, it's great to see this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; norad -d 0
norad OPTIONS

  -d, --defcon VALUE  Set DEFCON level
  -v, --version       Show version information
  -h, --help          Show this summary

Errors:
Failed to validate "-d 0": level must be 1 to 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... even in a throwaway script. More safety net.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it Works
&lt;/h2&gt;

&lt;p&gt;That first line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/usr/bin/env joker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's standard Posix for "find the &lt;code&gt;joker&lt;/code&gt; command and run the rest of the file through it". Any additional arguments to the command will be provided in &lt;code&gt;*command-line-args*&lt;/code&gt;, a sequence of strings.&lt;/p&gt;

&lt;p&gt;Because this is a script, and not a program, there's no &lt;code&gt;main&lt;/code&gt;; there's just Clojure &lt;em&gt;forms&lt;/em&gt;, evaluated one after another.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ns&lt;/code&gt; form is convenient for bringing in the other built-in libraries that are needed. We gave this namespace the name &lt;code&gt;script&lt;/code&gt;, but that's pretty arbitrary.&lt;/p&gt;

&lt;p&gt;The next few forms define constants and utility functions needed to parse command line options and perform actions based on them. A real implementation of &lt;code&gt;set-defcon&lt;/code&gt; might, for example, send an HTTP request to update the big display in the war room.&lt;/p&gt;

&lt;p&gt;The last form, the &lt;code&gt;let&lt;/code&gt; block, is where the real work starts; this is where those command line arguments are parsed into options, and then actions are dispatched.  This is all very familiar to any Clojure programmer.&lt;/p&gt;

&lt;p&gt;But that's it.  No &lt;code&gt;main&lt;/code&gt;. No classes. No compilation. No CLASSPATH. No overhead.  Just your code, in (very nearly) The One True Language, but running instantly.&lt;/p&gt;

</description>
      <category>clojure</category>
      <category>scripting</category>
      <category>joker</category>
    </item>
    <item>
      <title>GraphQL's Secret Super Power</title>
      <dc:creator>Howard M. Lewis Ship</dc:creator>
      <pubDate>Fri, 12 Apr 2019 21:43:29 +0000</pubDate>
      <link>https://forem.com/hlship/graphql-s-secret-super-power-1g1c</link>
      <guid>https://forem.com/hlship/graphql-s-secret-super-power-1g1c</guid>
      <description>&lt;p&gt;GraphQL has a certain, almost hidden feature in it, that is of critical importance  to me. It's mentioned in the documentation, but I think many people breeze past that page in the excitement of everything else GraphQL has to offer.&lt;/p&gt;

&lt;p&gt;Before I reveal what this feature is, let me explain some of my personal history and how it relates.&lt;/p&gt;

&lt;p&gt;Back in the early naughts, I created an open-source, server-side Java web framework, &lt;a href="http://tapestry.apache.org/" rel="noopener noreferrer"&gt;Apache Tapestry&lt;/a&gt;. I'm proud of many unique features of Tapestry that were forward thinking at the time, even if such features are now considered quite essential. Even so, I often struggled at the "elevator pitch" for why Tapestry was so valuable.&lt;/p&gt;

&lt;p&gt;Eventually, I came up with four essential aspects of Tapestry that drove its design and served to highlight what was uniquely useful about it: &lt;em&gt;Simplicity&lt;/em&gt;, &lt;em&gt;Consistency&lt;/em&gt;, &lt;em&gt;Efficiency&lt;/em&gt;, and &lt;em&gt;Feedback&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Simplicity at the time was essentially conciseness and hiding some of the necessary machinery. Thanks to &lt;a href="https://www.infoq.com/presentations/Simple-Made-Easy" rel="noopener noreferrer"&gt;Rich Hickey&lt;/a&gt;, my exact definition of simplicity has shifted subsequently.&lt;/p&gt;

&lt;p&gt;Consistency concerned being able to use the same techniques in different related areas: in Tapestry, creating a page, a component that can be reused within the same application, or a component that can be reused across applications were virtually identical.&lt;/p&gt;

&lt;p&gt;Efficiency concerned the overhead of using the framework; essentially, the beneficial features of Tapestry had to be deliverable with very good performance. In fact, Tapestry (despite doing far more per-request work than many competing frameworks) was always in the top-tier, performance-wise.&lt;/p&gt;

&lt;p&gt;And that leaves &lt;strong&gt;Feedback&lt;/strong&gt;, and its rather vague definition. Feedback, to my mind, covers many things within the general job of making the developer's life bearable. One aspect was that Tapestry supported live changes to code and templates, thus streamlining the developer's feedback loop: being able to see a change as soon as you refreshed a page, rather than in tens of seconds or longer (sometimes much, much longer) was quite liberating, even before the ever-present distraction of Twitter threatened to break your concentration while you waited.&lt;/p&gt;

&lt;p&gt;But, at its core, Feedback was a commitment throughout the code to catch common errors and report them with as much detail as possible; as well as a built-in detailed exception report page that provided a wealth of information, including stack traces and highlighted source lines from component templates.&lt;/p&gt;

&lt;p&gt;The intent was that when anything went wrong, the details needed to quickly diagnose the problem was right in front of you. I spent a lot of time in Tapestry training and conference sessions triggering that exception report and digging through it, then quickly fixing things and continuing to the next step.&lt;/p&gt;

&lt;p&gt;So, how does Feedback (as well as Simplicity, Consistency, and Efficiency) relate to GraphQL?&lt;/p&gt;

&lt;p&gt;Simplicity: for all that GraphQL tacks a type system on top of queries and JSON, the overall design of GraphQL is deliberately simple. If simplicity is what allows you to easily understanding the behavior of your system, then GraphQL earns high marks.&lt;/p&gt;

&lt;p&gt;Consistency: everything essential in implementing a GraphQL schema is a field (that may have arguments).  Queries and mutations?  Fields on a root Query or Mutation object. Subscriptions?  Still a field. Input objects: still looking like fields (without arguments). A single type system defines input objects and field arguments as well as all output values. It's all very consistent.&lt;/p&gt;

&lt;p&gt;Efficiency: A gain and a loss here; certainly it's more expensive computationally to parse and execute a GraphQL query than it is to route a request and spew out JSON; likewise, the fact that all GraphQL queries route through a single endpoint can be an impediment to traditional caching approaches. &lt;/p&gt;

&lt;p&gt;However, some of these challenges can be mitigated by leveraging server-stored queries (the client sends up the name of a query, not the query itself) and the fact that the client requests only what's actually needed can make a significant difference in actual throughput, especially in terms of downloading data to mobile devices. Our GraphQL integration layers allow most content from backend systems to be filtered out before it goes over the wire; multi-kilobytes JSON payloads full of extraneous data is filtered down to a handful of output values, numbering in the dozens of characters, not thousands.&lt;/p&gt;

&lt;p&gt;That leaves Feedback: GraphQL gets high marks here for its introspection capabilities, which make things like GraphiQL possible. But the specific aspect of feedback that prompted me to write this posting was &lt;a href="https://graphql.org/learn/validation/" rel="noopener noreferrer"&gt;&lt;em&gt;input validation&lt;/em&gt;&lt;/a&gt;; GraphQL specifically discusses exactly how and when to capture all manner of problems in the GraphQL query, and exactly how those problems are to be reported back to the client.  This includes unparsable query documents, references to unknown operations, fields, or arguments, omitting of required arguments, and even cases where the wrong scalar values (or improperly formatted scalar values) are provided.&lt;/p&gt;

&lt;p&gt;In each of these cases, GraphQL can provide an error detailing exactly what went wrong, and pinpoint the exact location in the query document related to the failure.&lt;/p&gt;

&lt;p&gt;By contrast, I'm working on integrating a piece of our application with another team's JSON-based API. We have to send a fairly large, deeply nested JSON object as the body of the request. If our code omits a field, includes an extra field, or provides &lt;em&gt;any&lt;/em&gt; content that isn't &lt;em&gt;exactly&lt;/em&gt; what the API expects, we get back the following:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;That's it.  No context, no indication of what underlying property of the request JSON is at fault, no clue as what to fix or how to proceed. No feedback.&lt;/p&gt;

&lt;p&gt;At a very &lt;em&gt;minimum&lt;/em&gt;, an equivalent GraphQL API would have identified precisely which field was executing when an error occurred, or which argument of which field had invalid or missing data. The error would be associated with a specific position within the input GraphQL query document. There would be a place for me to start in figuring out what went wrong.&lt;/p&gt;

&lt;p&gt;So, that may itself be the secret sauce in GraphQL; it's not just that it's useful (simple, consistent, efficient) when everything is working well, but it's extra-useful, with feedback, when things go wrong.&lt;/p&gt;

</description>
      <category>graphql</category>
    </item>
  </channel>
</rss>
