<?xml version='1.0' encoding='UTF-8'?>
<rss version='2.0' xmlns:atom='http://www.w3.org/2005/Atom'>
<channel>
<atom:link href='https://humorless.github.io/' rel='self' type='application/rss+xml'/>
<title>
Let over map merge
</title>
<link>
https://humorless.github.io/
</link>
<description>
This personal blog describes about the ideas around Clojure/ClojureScript/Datomic
</description>
<lastBuildDate>
Thu, 14 May 2026 04:37:12 +0000
</lastBuildDate>
<generator>
clj-rss
</generator>
<item>
<guid>
https://humorless.github.io/posts-output/agent-skill
</guid>
<link>
https://humorless.github.io/posts-output/agent-skill
</link>
<title>
Expert Clojure Workflows for AI Agents: Four Skills from Production Experience
</title>
<description>
&lt;p&gt;When you let an AI agent write Clojure code, you expect it to leverage the language's superpowers—the REPL's interactivity, structural editing, format-preserving code manipulation, and the rich ecosystem of wrapper libraries. Instead, what you typically see is mediocre code written slowly, as the agent makes the same mistakes every developer learns to avoid.&lt;/p&gt;&lt;p&gt;I discovered this the hard way.&lt;/p&gt;&lt;h2 id=&quot;the&amp;#95;setup:&amp;#95;vibe&amp;#95;coding&amp;#95;with&amp;#95;observations&quot;&gt;The Setup: Vibe Coding with Observations&lt;/h2&gt;&lt;p&gt;While building &lt;a href='https://github.com/humorless/lite-crm'&gt;lite-crm&lt;/a&gt; with Claude Code, I deliberately avoided the &lt;code&gt;&amp;ndash;dangerously-skip-permissions&lt;/code&gt; flag. Instead, I sat beside the agent and watched it work—observing its patterns, frustrations, and failures. What I saw was an agent trained on millions of codebases but ignorant of how Clojure practitioners actually think.&lt;/p&gt;&lt;p&gt;Three concrete problems emerged:&lt;/p&gt;&lt;h3 id=&quot;problem&amp;#95;1:&amp;#95;the&amp;#95;wrapper&amp;#95;library&amp;#95;blind&amp;#95;spot&quot;&gt;Problem 1: The Wrapper Library Blind Spot&lt;/h3&gt;&lt;p&gt;When encountering Java interop, the agent jumps straight into direct interoperability without ever asking: &quot;Is there a Clojure wrapper library for this?&quot;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;The result:&lt;/strong&gt; Uglier code, harder to maintain, and a missed opportunity for idiomatic Clojure.&lt;/p&gt;&lt;h3 id=&quot;problem&amp;#95;2:&amp;#95;formatting&amp;#95;brittleness&quot;&gt;Problem 2: Formatting Brittleness&lt;/h3&gt;&lt;p&gt;Code formatters like &lt;code&gt;cljfmt&lt;/code&gt; are essential—but they create a sneaky problem. When the agent modifies source and the formatter shifts indentation by a single space, the agent's subsequent &lt;code&gt;str&amp;#95;replace&lt;/code&gt; operations fail due to whitespace mismatch.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;The result:&lt;/strong&gt; I watched it fail, retry, fail again, then give up and rewrite entire files. Enormous token waste.&lt;/p&gt;&lt;h3 id=&quot;problem&amp;#95;3:&amp;#95;primitive&amp;#95;debugging&quot;&gt;Problem 3: Primitive Debugging&lt;/h3&gt;&lt;p&gt;When a test failed, the agent fell back on the crudest debugging technique: add &lt;code&gt;println&lt;/code&gt; statements, run the test, inspect output, delete the logs, restore the code. Repeat.&lt;/p&gt;&lt;p&gt;This is especially wasteful in a Clojure project where I've provided direct access to the REPL via the &lt;a href='https://github.com/licht1stein/brepl'&gt;brepl&lt;/a&gt; CLI. The agent could inspect values interactively, test hypotheses instantly, and trace execution without touching source code. But it never did.&lt;h2&gt;&lt;/h2&gt;&lt;/p&gt;&lt;h2 id=&quot;the&amp;#95;recognition&quot;&gt;The Recognition&lt;/h2&gt;&lt;p&gt;These weren't knowledge gaps. They were &lt;strong&gt;behavioral gaps&lt;/strong&gt;—places where the agent's default approach conflicted with Clojure expertise.&lt;/p&gt;&lt;p&gt;In the context of Clojure Stack Lite (which includes proper testing harness and real database, not mocks), the agent wasn't just writing suboptimal code—it was making design decisions based on unfamiliar tools.&lt;/p&gt;&lt;p&gt;I decided to address this not by teaching the agent more facts, but by redirecting its behavior.&lt;h2&gt;&lt;/h2&gt;&lt;/p&gt;&lt;h2 id=&quot;four&amp;#95;skills&amp;#95;to&amp;#95;close&amp;#95;the&amp;#95;gap&quot;&gt;Four Skills to Close the Gap&lt;/h2&gt;&lt;p&gt;The result is four skills, each targeting a specific behavioral pattern that distinguishes novice agents from expert Clojure practitioners:&lt;/p&gt;&lt;h3 id=&quot;1.&amp;#95;&lt;strong&gt;clj-debug&lt;/strong&gt;:&amp;#95;from&amp;#95;logging&amp;#95;to&amp;#95;repl&amp;#95;inspection&quot;&gt;1. &lt;strong&gt;clj-debug&lt;/strong&gt;: From Logging to REPL Inspection&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt; Agents default to adding &lt;code&gt;println&lt;/code&gt;, &lt;code&gt;tap&amp;gt;&lt;/code&gt;, or logging statements, then running tests to inspect output.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;The Pattern:&lt;/strong&gt; In Clojure, this is backwards. The REPL lets you pin a value with &lt;code&gt;def&lt;/code&gt;, explore its structure instantly, test hypotheses interactively—all without modifying code.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;What the skill does:&lt;/strong&gt; When you're about to debug, clj-debug redirects from logging patterns to REPL-based inline inspection. It teaches the agent to use &lt;code&gt;def&lt;/code&gt;, &lt;code&gt;keys&lt;/code&gt;, keyword access, and structural exploration—the actual workflow expert Clojure developers follow.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Behavioral change:&lt;/strong&gt; From edit-test-inspect cycle to interactive REPL inspection. This is faster, non-invasive, and gives immediate feedback.&lt;/p&gt;&lt;h3 id=&quot;2.&amp;#95;&lt;strong&gt;clj-discover&lt;/strong&gt;:&amp;#95;systematic&amp;#95;api&amp;#95;exploration&quot;&gt;2. &lt;strong&gt;clj-discover&lt;/strong&gt;: Systematic API Exploration&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt; When encountering unfamiliar Java classes or macros, agents jump to direct integration without exploring whether an idiomatic Clojure wrapper already exists.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;The Pattern:&lt;/strong&gt; Expert Clojure developers follow a deliberate workflow:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Search for a Clojure wrapper library first (usually there is one)&lt;/li&gt;&lt;li&gt;If not, inspect the Java class via reflection&lt;/li&gt;&lt;li&gt;For macros, expand them to understand what code they generate&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;strong&gt;What the skill does:&lt;/strong&gt; clj-discover codifies this workflow, ensuring the agent prioritizes idiomatic libraries and systematic exploration before writing integration code.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Behavioral change:&lt;/strong&gt; From direct interop to research-first integration. The result is cleaner, more maintainable code.&lt;/p&gt;&lt;h3 id=&quot;3.&amp;#95;&lt;strong&gt;clj-replace&lt;/strong&gt;:&amp;#95;format-aware&amp;#95;structural&amp;#95;replacement&quot;&gt;3. &lt;strong&gt;clj-replace&lt;/strong&gt;: Format-Aware Structural Replacement&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt; Code formatters shift indentation by spaces, breaking text-based &lt;code&gt;str&amp;#95;replace&lt;/code&gt;. The agent then wastes tokens failing repeatedly or rewriting entire files.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;The Pattern:&lt;/strong&gt; Clojure is homoiconic—code is data. Two S-expressions are semantically equivalent even if formatted differently. Expert editors handle this automatically via structural editing.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;What the skill does:&lt;/strong&gt; clj-replace compares code by structure (S-expression equivalence) rather than text, ignoring whitespace while preserving the original file's formatting style. It uses the &lt;a href='https://cljdoc.org/d/rewrite-clj/rewrite-clj/1.1.45/doc/user-guide'&gt;rewrite-clj&lt;/a&gt; library to parse, match, and replace nodes safely.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Behavioral change:&lt;/strong&gt; From brittle text matching to robust structural matching. Formatting variations become irrelevant.&lt;/p&gt;&lt;h3 id=&quot;4.&amp;#95;&lt;strong&gt;clj-refactor&lt;/strong&gt;:&amp;#95;mechanism/policy&amp;#95;separation&quot;&gt;4. &lt;strong&gt;clj-refactor&lt;/strong&gt;: Mechanism/Policy Separation&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt; Without guidance, agents write tangled code where reusable mechanisms are mixed with business policy, creating inflexible designs that accumulate technical debt.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;The Pattern:&lt;/strong&gt; Arne Brasseur's &lt;a href='https://lambdaisland.com/blog/2022-03-10-mechanism-vs-policy'&gt;mechanism/policy separation&lt;/a&gt; principle is core to building maintainable Clojure systems. Mechanism is context-free, stable, and reusable. Policy is opinionated, domain-specific, and volatile. Expert developers keep these separate.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;What the skill does:&lt;/strong&gt; clj-refactor scans code for opportunities to extract mechanisms from policy—functions where hard-coded values or implicit context can be made explicit, dependencies can be pushed to parameters, and reusable logic can be isolated.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Behavioral change:&lt;/strong&gt; From monolithic functions to extracted, composable mechanisms. Code becomes easier to test, reuse, and reason about.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Unlike clj-debug, clj-discover, and clj-replace—which activate automatically when the agent encounters problems—clj-refactor is &lt;strong&gt;user-initiated&lt;/strong&gt;. You invoke it when you want the agent to analyze code for refactoring opportunities, not in response to a failure.&lt;h2&gt;&lt;/h2&gt;&lt;/p&gt;&lt;h2 id=&quot;why&amp;#95;this&amp;#95;matters&quot;&gt;Why This Matters&lt;/h2&gt;&lt;p&gt;These aren't reference manuals or API documentation. They're &lt;strong&gt;workflow redirects&lt;/strong&gt;—rules that teach AI agents to think like expert Clojure developers instead of generic code writers.&lt;/p&gt;&lt;p&gt;The underlying philosophy is simple: &lt;strong&gt;A skill's value is measured by behavioral change, not knowledge transfer.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;When an agent uses clj-debug, it stops adding logging. When it uses clj-discover, it checks for idiomatic wrappers before raw interop. When it uses clj-replace, formatting becomes irrelevant. When you invoke clj-refactor, the agent identifies tangled mechanisms and suggests extraction. Each skill shifts the agent's default patterns closer to expert practice.&lt;/p&gt;&lt;p&gt;This matters because Clojure is a language of leverage. The REPL, immutability, homoiconicity, and the functional approach all reward practitioners who use them correctly. An agent that doesn't leverage these features isn't just writing slow code—it's missing the point of the language.&lt;h2&gt;&lt;/h2&gt;&lt;/p&gt;&lt;p&gt;The goal is simple: your AI agent shouldn't just write Clojure code—it should think like a Clojure developer. These four skills make that possible.&lt;/p&gt;&lt;p&gt;Find them at: &lt;a href='https://github.com/humorless/clj-native-agent/'&gt;github.com/humorless/clj-native-agent&lt;/a&gt;&lt;/p&gt;
</description>
<pubDate>
Thu, 14 May 2026 00:00:00 +0000
</pubDate>
</item>
<item>
<guid>
https://humorless.github.io/posts-output/agent-ready-stack
</guid>
<link>
https://humorless.github.io/posts-output/agent-ready-stack
</link>
<title>
Agent-Ready Stack
</title>
<description>
&lt;p&gt;I keep seeing people share vibe-coded apps built on TypeScript/React + Supabase — seemingly the default recommendation from Lovable or Cursor. As a Clojure programmer, I can't stay quiet about this. In an era where AI agents are deeply embedded in the development workflow, that choice carries structural hidden costs that almost nobody is talking about.&lt;/p&gt;&lt;h2 id=&quot;context&amp;#95;window&amp;#95;is&amp;#95;the&amp;#95;bottleneck,&amp;#95;and&amp;#95;framework&amp;#95;design&amp;#95;determines&amp;#95;burn&amp;#95;rate&quot;&gt;Context Window Is the Bottleneck, and Framework Design Determines Burn Rate&lt;/h2&gt;&lt;p&gt;LongCodeBench research shows that Claude 3.5 Sonnet's accuracy on bug-fixing tasks drops from 29% to 3% as context grows from 32K to 256K tokens. Chroma tested 18 frontier models and found the same pattern across all of them.&lt;/p&gt;&lt;p&gt;Coding agents accelerate this degradation: every tool call, every file read, every error message accumulates in the context. A 30-step agent session can consume more than ten times the context of a single conversation turn.&lt;/p&gt;&lt;p&gt;Countless efforts are already underway to manage context from the harness-design side — but the tech stack itself has an enormous impact on context efficiency that rarely gets discussed.&lt;/p&gt;&lt;h2 id=&quot;task-relevant&amp;#95;subgraph&quot;&gt;Task-Relevant Subgraph&lt;/h2&gt;&lt;p&gt;An AI agent completing a task doesn't need to read the entire codebase — only the files relevant to that task. Call this set the task-relevant subgraph. &lt;strong&gt;The size of the subgraph is determined by the architectural design of the framework, not by the model.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;The problem with TypeScript + React + Supabase is that a single feature naturally spans multiple layers — component, hook, state, API client, type definition — each living in a different file. The subgraph starts large and only grows as shared dependencies accumulate.&lt;/p&gt;&lt;p&gt;AI tends to recommend the stack it was trained on the most, but &quot;easy to generate&quot; is not the same as &quot;efficient for long-term AI-assisted development.&quot; These are two different things.&lt;/p&gt;&lt;h2 id=&quot;what&amp;#95;makes&amp;#95;a&amp;#95;stack&amp;#95;more&amp;#95;agent-ready&quot;&gt;What Makes a Stack More Agent-Ready&lt;/h2&gt;&lt;p&gt;My current go-to is &lt;a href='https://stack.bogoyavlensky.com/'&gt;Clojure Stack Lite&lt;/a&gt;, and several of its design choices structurally shrink the task-relevant subgraph.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;HTMX eliminates implicit client state.&lt;/strong&gt; React state is scattered across multiple interdependent files; to verify behavior, an agent has to simulate browser interactions. HTMX is driven by server responses, so an agent can verify with a plain &lt;code&gt;curl&lt;/code&gt; — the response is an HTML fragment, right or wrong, no ambiguity.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;HoneySQL eliminates implicit lazy loading.&lt;/strong&gt; When an ORM produces an N+1 problem, the debug subgraph includes model definitions, association configs, and migration files, because the issue is buried in implicit behavior. HoneySQL expresses queries as SQL-as-data — no lazy loading, no association magic. N+1 can't happen silently, because the syntax simply doesn't allow it to sneak in. The debug subgraph shrinks from five files to one.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Blocking IO eliminates implicit error paths.&lt;/strong&gt; The fundamental problem with async isn't the syntax — it's that error paths are implicit. Every async call site is a potential break point where an exception can detach from the main flow. To locate a root cause, an agent must trace the entire call chain, and context width grows linearly with chain length. Clojure's blocking IO has no async boundaries; exceptions follow a single path — propagate upward, handled uniformly in middleware. When debugging, an agent only needs two places: the middleware log and the call site the log points to. Context scope stays fixed regardless of system size.&lt;/p&gt;&lt;h2 id=&quot;explicit&amp;#95;over&amp;#95;implicit&amp;#95;is&amp;#95;not&amp;#95;just&amp;#95;a&amp;#95;clojure&amp;#95;virtue&quot;&gt;Explicit Over Implicit Is Not Just a Clojure Virtue&lt;/h2&gt;&lt;p&gt;All three points share a common structure: &lt;strong&gt;the less implicit behavior, the smaller the context an agent needs to bring in.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;The point here isn't a framework or language comparison — it's an observation about design philosophy. Explicit over implicit is a virtue for human developers; for AI agents, it's a structural guarantee that they won't go dumb prematurely.&lt;/p&gt;&lt;p&gt;Design principles the Clojure community has championed for years happen to be a competitive advantage in the AI agent era. I've chosen to frame this in terms of context efficiency, hoping it helps more people appreciate what the Clojure community figured out a long time ago.&lt;/p&gt;
</description>
<pubDate>
Mon, 11 May 2026 00:00:00 +0000
</pubDate>
</item>
<item>
<guid>
https://humorless.github.io/posts-output/tutoring-class
</guid>
<link>
https://humorless.github.io/posts-output/tutoring-class
</link>
<title>
Teaching Clojure programming class
</title>
<description>
&lt;p&gt;When I told others that I am a Clojure programmer, they responded apathetically. Why so many people in Taiwan never heard of this great programming language? One day, an idea occurred to me that how about teaching some students? &lt;/p&gt;&lt;h2 id=&quot;the&amp;#95;advertisement&quot;&gt;The advertisement&lt;/h2&gt;&lt;p&gt;I re-wrote my advertisement again and again. What kind of value proposition would be appreciated by my prospects? I actually did not know. At the end, I wrote 3 objectives in my &lt;a href='https://medium.com/@replware/%E5%87%BD%E6%95%B8%E5%BC%8F%E7%A8%8B%E5%BC%8F%E8%A8%AD%E8%A8%88-functional-programming-%E5%BE%9E%E5%9F%BA%E7%A4%8E%E5%88%B0%E5%AF%A6%E5%8B%99-bc06f68c7bfb'&gt;advertisement&lt;/a&gt;. &lt;/p&gt;&lt;ol&gt;&lt;li&gt;Help you learn the Clojure programming language&lt;/li&gt;&lt;li&gt;Help you become the real senior programmer in the eyes of your colleagues.&lt;/li&gt;&lt;li&gt;Help you become more confident whenever you want to ask for a raise.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;My friends did not believe I could get students, and they tried to tell the uncomfortable truth mildly. They asked something like &quot;Who is your target audience?&quot;&lt;/p&gt;&lt;p&gt;Fortunately, I got two students just after I posted it. Two of my college classmates wanted to learn Clojure programming language from me. &lt;/p&gt;&lt;h2 id=&quot;the&amp;#95;ways&amp;#95;of&amp;#95;teaching&quot;&gt;The ways of teaching&lt;/h2&gt;&lt;p&gt;At the very beginning, I asked my students what they want to learn. This was a one-on-one tutoring class, so it could be customized. I organized the class into 4 stages.&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;a href='https://github.com/humorless/dotfiles'&gt;Development environment setup&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;About the Clojure's productivity &amp;mdash; &lt;a href='https://www.slideshare.net/humorless/the-productivity-brought-by-clojure-149170292'&gt;The productivity brought by Clojure&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;Practicing at the &lt;a href='http://www.4clojure.com/'&gt;4clojure&lt;/a&gt; website.&lt;/li&gt;&lt;li&gt;Studying any specific topics they were interested. (Customization part)&lt;/li&gt;&lt;/ol&gt;&lt;h2 id=&quot;lessions&amp;#95;learned&amp;#95;from&amp;#95;teaching&quot;&gt;Lessions learned from teaching&lt;/h2&gt;&lt;p&gt;Through the questions from my students I found out some obstacles in learning Clojure. &lt;/p&gt;&lt;h3 id=&quot;switching&amp;#95;between&amp;#95;purpose&amp;#95;view&amp;#95;and&amp;#95;implementation&amp;#95;view&quot;&gt;Switching between purpose view and implementation view&lt;/h3&gt;&lt;p&gt;When I write recursion, I split it into three steps:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Choose the name of this recursion function. The name is about this function's purpose.&lt;/li&gt;&lt;li&gt;Think about the boundary condition of this function. When will it stop?&lt;/li&gt;&lt;li&gt;Write the implementation of this function. This function should be implemented by a call to itself with a different input argument and some connection code.&lt;/li&gt;&lt;/ol&gt;&lt;pre&gt;&lt;code&gt;&amp;#40;defn range-to-zero &amp;#91;x&amp;#93; 
  &amp;#40;when &amp;#40;&amp;gt; x 0&amp;#41;
    &amp;#40;conj &amp;#40;range-to-zero &amp;#40;dec x&amp;#41;&amp;#41; x&amp;#41;&amp;#41;&amp;#41;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Take the above code as an example. I think the output of &lt;code&gt;&amp;#40;range-to-zero 4&amp;#41;&lt;/code&gt; is &lt;code&gt;'&amp;#40;4 3 2 1&amp;#41;&lt;/code&gt;. When I want to define the &lt;code&gt;&amp;#40;range-to-zero 5&amp;#41;&lt;/code&gt;, I just need to conj a &lt;code&gt;5&lt;/code&gt; to the &lt;code&gt;'&amp;#40;4 3 2 1&amp;#41;&lt;/code&gt;. &lt;/p&gt;&lt;p&gt;My students did not think like this way: they simulated the execution of the code from the very top toward the boundary conditions. They organized their mind like an interpreter and they traced the code just like the interpreter did. I told my student that you need to switch your thinking between purpose view and implementation view.&lt;/p&gt;&lt;h3 id=&quot;different&amp;#95;levels&amp;#95;of&amp;#95;complexity&quot;&gt;Different levels of complexity&lt;/h3&gt;&lt;p&gt;After my students solved about 50 questions in 4clojure, they felt a sense of confidence to fly alone. However, when they just solved about 25 questions, they felt quite confused. They were very confused about the idiomatic ways to solve 4clojure questions. &lt;/p&gt;&lt;p&gt;I considered there were 5 levels of complexity:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Solve the question by remembering the Clojure function name. For example, &lt;code&gt;frequencies&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;Solve the question by using some sequence questions: &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;filter&lt;/code&gt;, &lt;code&gt;mapcat&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;Solve the question by using &lt;code&gt;reduce&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;Solve the question by using recursion.&lt;/li&gt;&lt;li&gt;Solve the question by using mutual recursion.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;I encouraged my students to solve any questions using as fewer level of complexity as possible. There were still certain special cases like &lt;code&gt;flatten&lt;/code&gt;, which might not fit into my categories.&lt;/p&gt;&lt;h2 id=&quot;final&amp;#95;notes&quot;&gt;Final notes&lt;/h2&gt;&lt;p&gt;One of my students told me that he decided to learn the Clojure programming language because of Robert C. Martin's recommendation. Thanks to uncle Bob that he had done great marketing for me.&lt;/p&gt;
</description>
<pubDate>
Tue, 05 May 2020 00:00:00 +0000
</pubDate>
</item>
<item>
<guid>
https://humorless.github.io/posts-output/datomic-cache
</guid>
<link>
https://humorless.github.io/posts-output/datomic-cache
</link>
<title>
Using Datomic with disk cache and LU cache
</title>
<description>
&lt;h2 id=&quot;the&amp;#95;background&amp;#95;of&amp;#95;this&amp;#95;post&quot;&gt;The background of this post&lt;/h2&gt;&lt;p&gt;I began to use Datomic seriously in my project at work from February 2019. I encountered certain performance issues and I solved them through disk cache and LU cache.&lt;/p&gt;&lt;h2 id=&quot;analytical&amp;#95;queries&amp;#95;need&amp;#95;pre-computation&quot;&gt;Analytical queries need pre-computation&lt;/h2&gt;&lt;p&gt;My project had several analytical queries which implemented the business rules. With Datomic expressive query power, it was very easy to implement queries that closely related to domain model. Great for expressiveness, but the query speed was quite slow, I needed to do some pre-computation.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;How to save the query results? My query results were in the form of EDN format. Should I prepare a key-value database to cache it? Or, should I use just Datomic to serve as the key-value database?&lt;/p&gt;&lt;h2 id=&quot;using&amp;#95;datomic&amp;#95;as&amp;#95;key-value&amp;#95;store&quot;&gt;Using Datomic as key-value store&lt;/h2&gt;&lt;p&gt;In my use cases, I used Datomic as key-value store with the following schema.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;| schema name | :booking/tx  | :booking/team      | :booking/bytes |
|-------------|--------------|--------------------|----------------|
| data type   | long         |  string            |  bytes         |

&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;:booking/tx&lt;/code&gt; and &lt;code&gt;:booking/team&lt;/code&gt; served as keys and &lt;code&gt;:booking/bytes&lt;/code&gt; served as value. Before I stored the EDN format value into &lt;code&gt;:booking/bytes&lt;/code&gt;, I first required a smart library: &lt;a href='https://github.com/ptaoussanis/nippy'&gt;Nippy&lt;/a&gt;. Nippy helped to transform Clojure composite data structure into plain Java bytes.&lt;/p&gt;&lt;p&gt;Here came another question: Was there any size limit with the Datomic schema type: &lt;code&gt;db.type/bytes&lt;/code&gt;? I spent some time to find the answer in Datomic google group.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt; I believe the rule of thumb is that values stored in Datomic — strings, bytes, etc. — should not exceed one kilobyte. Nothing will break if they do, but Datomic's storage layout is optimized for values this size or smaller. &lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Great! Nothing will break if they do.&lt;/p&gt;&lt;h2 id=&quot;outofmemory&amp;#95;error&amp;#95;occurred&quot;&gt;OutOfMemory Error occurred&lt;/h2&gt;&lt;p&gt;Some of my queries used &lt;code&gt;not-join&lt;/code&gt; syntaxes. At the beginning, &lt;code&gt;not-join&lt;/code&gt; looked like great things because with &lt;code&gt;not-join&lt;/code&gt; I could express my intent without any low level interpretation. Soonly, the queries with &lt;code&gt;not-join&lt;/code&gt; threw an &lt;code&gt;OutOfMemory&lt;/code&gt; error. Therefore, I decided to do some optimizations.&lt;/p&gt;&lt;h2 id=&quot;extract&amp;#95;not-join&amp;#95;out&amp;#95;of&amp;#95;query&amp;#95;and&amp;#95;use&amp;#95;lu-cached&amp;#95;memoize&quot;&gt;Extract not-join out of query and use LU-cached memoize&lt;/h2&gt;&lt;p&gt;The original query was like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;#40;d/q '&amp;#91;:find &amp;#40;count ?r&amp;#41; .
       :where &amp;#91;?r :release/name &amp;quot;Live at Carnegie Hall&amp;quot;&amp;#93;
              &amp;#40;not-join &amp;#91;?r&amp;#93;
                &amp;#91;?r :release/artists ?a&amp;#93;
                &amp;#91;?a :artist/name &amp;quot;Bill Withers&amp;quot;&amp;#93;&amp;#41;&amp;#93;
       db&amp;#41;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The modified equivalent queries were:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;#40;def B &amp;#40;into #{}
             &amp;#40;d/q '&amp;#91;:find ?r
                    :where &amp;#91;?r :release/artist ?a&amp;#93;
                           &amp;#91;?a :artist/name &amp;quot;Bill Withers&amp;quot;&amp;#93;&amp;#93;
                    db&amp;#41;&amp;#41;&amp;#41;

&amp;#40;d/q '&amp;#91;:find &amp;#40;count ?r&amp;#41; .
       :in $a $b
       :where
       &amp;#91;$a ?r :release/name &amp;quot;Live at Carnegie Hall&amp;quot;&amp;#93;
       &amp;#40;$b not &amp;#91;?r&amp;#93;&amp;#41;&amp;#93;
     db B&amp;#41;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After I extracted &lt;code&gt;not-join&lt;/code&gt; part out of the original query, I discovered that some of my new query would be called with similar inputs across several queries. It would save some computation resources if I used &lt;code&gt;memoize&lt;/code&gt; to modify the new query.&lt;/p&gt;&lt;p&gt;However, the standard version &lt;code&gt;clojure.core/memoize&lt;/code&gt; would cause memory leak. I chose the Clojure contrib library &lt;a href='https://github.com/clojure/core.memoize'&gt;core.memoize&lt;/a&gt; to cache the query result with LU cache.&lt;/p&gt;&lt;p&gt;The to be memoized query functions were like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;#40;defn memo-q&amp;#42;
  &amp;#91;db t&amp;#93;
  &amp;#40;into #{}
        &amp;#40;d/q '&amp;#91;:find ?r
               :where &amp;#91;?r :release/artist ?a&amp;#93;
                      &amp;#91;?a :artist/name &amp;quot;Bill Withers&amp;quot;&amp;#93;&amp;#93;
             db&amp;#41;&amp;#41;&amp;#41;

&amp;#40;def memo-q &amp;#40;clojure.core.memoize/lu memo-q&amp;#42; {} :lu/threshold 2&amp;#41;&amp;#41;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The memoized query function was called like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;#40;memo-q db &amp;#40;d/basis-t db&amp;#41;&amp;#41;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The Datomic t of the most recent transaction reachable via the db value served as the input parameter to decide whether the cached result was out of date.&lt;/p&gt;
</description>
<pubDate>
Sun, 15 Sep 2019 00:00:00 +0000
</pubDate>
</item>
<item>
<guid>
https://humorless.github.io/posts-output/etl
</guid>
<link>
https://humorless.github.io/posts-output/etl
</link>
<title>
A Clojurian's idioms and patterns for ETL
</title>
<description>
&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;&lt;p&gt;I needed to do eight Excel ETLs at my project. At the beginning, I just implemented some of the ETLs without any design. I did not even implement schema validation, and then I felt the pain soon. After several re-writing, I abstracted out some idioms and patterns for ETL.&lt;/p&gt;&lt;h2 id=&quot;problems&quot;&gt;Problems&lt;/h2&gt;&lt;p&gt;We need to import data from several Excel files into Datomic database. There are several concerns with the ETL (extract-transform-load):&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Schema validation:   Can we have a validation function that we only need to inject the schema and then the validation function will handle all the rest for us? &lt;/li&gt;&lt;li&gt;Transformation complexity:   The transformation from Excel to Datomic table varies a lot. The simplest one is just copy data, but the complex ones need to look up tables in the database. How can we organize different type of transformation functions such that the functions can be more reusable and composable?&lt;/li&gt;&lt;li&gt;Database upsert semantic:   The identity key of the database table may be compound fields, or there may be some cardinality-many fields in the database table. That is to say, the basic upsert semantic offered by Datomic is not enough.&lt;/li&gt;&lt;/ol&gt;&lt;h3 id=&quot;solution&amp;#95;for&amp;#95;schema&amp;#95;validation&quot;&gt;Solution for schema validation&lt;/h3&gt;&lt;p&gt;The library &lt;code&gt;clojure.spec&lt;/code&gt; is great for schema validation. &lt;/p&gt;&lt;pre&gt;&lt;code&gt;;; library functions defined at utility namespace
&amp;#40;defn check-raw-fn
  &amp;quot;assemble schema and then create a validation fn&amp;quot;
  &amp;#91;schema&amp;#93;
  &amp;#40;fn check-raw
    &amp;#91;data&amp;#93;
    &amp;#40;if &amp;#40;spec/valid? schema data&amp;#41;
      data
      &amp;#40;let &amp;#91;desc &amp;#40;spec/explain-str schema data&amp;#41;&amp;#93;
        &amp;#40;throw &amp;#40;ex-info desc {:causes data :desc desc}&amp;#41;&amp;#41;&amp;#41;&amp;#41;&amp;#41;&amp;#41;

;; Example application functions
&amp;#40;spec/def ::apply-time inst?&amp;#41;
&amp;#40;spec/def ::customer-id string?&amp;#41;
&amp;#40;spec/def ::lamp-customer-id string?&amp;#41;
&amp;#40;spec/def ::sales-name string?&amp;#41;
&amp;#40;spec/def ::source #{&amp;quot;agp&amp;quot; &amp;quot;lap&amp;quot;}&amp;#41;

&amp;#40;spec/def ::mapping
  &amp;#40;spec/&amp;#42; 
    &amp;#40;spec/keys :req-un
               &amp;#91;::apply-time ::customer-id ::lamp-customer-id ::sales-name ::source&amp;#93;&amp;#41;&amp;#41;&amp;#41;

&amp;#40;def &amp;#94;:private check-raw
  &amp;#40;utility/check-raw-fn ::mapping&amp;#41;&amp;#41;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this design:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Even though I do not know how many rows an Excel file may have, I can still use &lt;code&gt;&amp;#40;spec/&amp;#42; ...&amp;#41;&lt;/code&gt; to represent the schema for the Excel file. If the spec does not offer the semantic like &lt;code&gt;&amp;#40;spec/&amp;#42; ...&amp;#41;&lt;/code&gt;, I have to write some loop logic in &lt;code&gt;check-raw-fn&lt;/code&gt; function, which causes the context dependency.&lt;/li&gt;&lt;li&gt;The spec names are just the same as the column names of Excel. Keep it simple making the program more robust.&lt;/li&gt;&lt;li&gt;If a string has only a few possible options, represent it in the form as &lt;code&gt;#{option1 option2 ...}&lt;/code&gt;&lt;/li&gt;&lt;li&gt;When throwing exception, I use &lt;code&gt;&amp;#40;ex-info ...&amp;#41;&lt;/code&gt; and I put the output of &lt;code&gt;&amp;#40;spec/explain-str ...&amp;#41;&lt;/code&gt; into an exception. Then, I can find out what is wrong by just reading the exception message. &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Also, at the trigger API of ETL, the web API deliberately catches only certain types of Exception:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;#40;try &amp;#40;if-let &amp;#91;r &amp;#40;etl/sync-data cmd filename&amp;#41;&amp;#93;
            &amp;#40;ok {:result :insert-done}&amp;#41;
            &amp;#40;ok {:result :already-sync}&amp;#41;&amp;#41;
          &amp;#40;catch clojure.lang.ExceptionInfo e
            &amp;#40;bad-request {:reason &amp;#40;ex-data e&amp;#41;}&amp;#41;&amp;#41;
          &amp;#40;catch java.util.concurrent.ExecutionException e
            &amp;#40;bad-request {:reason &amp;#40;.getCause e&amp;#41;}&amp;#41;&amp;#41;&amp;#41;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The exception &lt;code&gt;clojure.lang.ExceptionInfo&lt;/code&gt; only catches the schema validation error thrown by my application code. The exception &lt;code&gt;java.util.concurrent.ExecutionException&lt;/code&gt; can catch the error from Datomic transaction. Other exceptions may happen with lower possibility, so I let them pass over and be recorded in log file.&lt;/p&gt;&lt;h3 id=&quot;solution&amp;#95;for&amp;#95;transformation&amp;#95;complexity&amp;#95;&amp;mdash;&amp;#95;let&amp;#95;over&amp;#95;map&amp;#95;merge&quot;&gt;Solution for transformation complexity &amp;mdash; let over map merge&lt;/h3&gt;&lt;p&gt;I propose a pattern, which I call it as &lt;code&gt;let over map merge&lt;/code&gt; to handle the transformation complexity.&lt;/p&gt;&lt;p&gt;Consider a transformation function &lt;code&gt;data-&amp;gt;txes&lt;/code&gt;, both the input and the output are sequences of map:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;The single map in the input data represents the row in the Excel file.&lt;/li&gt;&lt;li&gt;The single map in the output &lt;code&gt;txes&lt;/code&gt; represents the row in the Datomic table.&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;&lt;code&gt;&amp;#40;defn- data-&amp;gt;txes
  &amp;quot;data is a sequence of {HashMap}&amp;quot;
  &amp;#91;data&amp;#93;
  &amp;#40;let &amp;#91;db &amp;#40;d/db conn&amp;#41;
        table &amp;#40;utility/tax-id-&amp;gt;c-eid db&amp;#41;&amp;#93;
    &amp;#40;map #&amp;#40;transformation-f table %&amp;#41; data&amp;#41;&amp;#41;&amp;#41;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can easily divide the transformation into two categories:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Basic transformation: Just copy the field, or with pure function transformation.&lt;/li&gt;&lt;li&gt;Complex transformation: When transforming the input data, we need to also look up the database content.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;If we pull out &lt;code&gt;basic-mapping&lt;/code&gt; and &lt;code&gt;complex-mapping&lt;/code&gt; from &lt;code&gt;transformation-f&lt;/code&gt;, we can change the original code into&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;#40;defn- data-&amp;gt;txes
  &amp;#91;data&amp;#93;
  &amp;#40;let &amp;#91;db &amp;#40;d/db conn&amp;#41;
        table &amp;#40;utility/tax-id-&amp;gt;c-eid db&amp;#41;&amp;#93;
    &amp;#40;let &amp;#91;basic-tx &amp;#40;map basic-mapping data&amp;#41;
          complex-tx &amp;#40;map #&amp;#40;complex-mapping table %&amp;#41; data&amp;#41;&amp;#93;
      &amp;#40;map merge basic-tx complex-tx&amp;#41;&amp;#41;&amp;#41;&amp;#41;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With this &lt;code&gt;let over map-merge&lt;/code&gt; pattern, we can make the granularity of the transformation function smaller so as to make them more reusable and composable. In certain cases, basic-mapping only needs to change the key-name in the hash map, so we can use &lt;code&gt;clojure.set/rename-keys&lt;/code&gt; to implement the basic-mapping.&lt;/p&gt;&lt;h3 id=&quot;solution&amp;#95;for&amp;#95;database&amp;#95;upsert&amp;#95;semantic&quot;&gt;Solution for database upsert semantic&lt;/h3&gt;&lt;p&gt;In Datomic, we can use the &lt;code&gt;:db.unique/identity&lt;/code&gt; to make certain schema work like primary key in traditional RDBMS. &lt;/p&gt;&lt;h4 id=&quot;compound&amp;#95;primary&amp;#95;key&quot;&gt;Compound primary key&lt;/h4&gt;&lt;p&gt;Consider tha table with compound primary key as &lt;code&gt;stream-unique-id, writing-time, source&lt;/code&gt;. How to do upsert when we have &lt;code&gt;txes&lt;/code&gt; like below?&lt;/p&gt;&lt;pre&gt;&lt;code&gt; &amp;#91;#:rev-stream{:stream-unique-id &amp;quot;AA&amp;quot;
               :writing-time #inst &amp;quot;2019-04-01T02:39:00.000-00:00&amp;quot;
               :source :etl.source/agp
               :campaign-name &amp;quot;BB&amp;quot;}&amp;#93;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With a db transaction function &lt;code&gt;upsert-rev-stream&lt;/code&gt;, we can simply write &lt;code&gt;txes&lt;/code&gt; as &lt;/p&gt;&lt;pre&gt;&lt;code&gt; &amp;#91;&amp;#91;:fn/upsert-rev-stream 
   #:rev-stream{:stream-unique-id &amp;quot;AA
                :writing-time #inst &amp;quot;2019-04-01T02:39:00.000-00:00&amp;quot;
                :source :etl.source/agp
                :campaign-name &amp;quot;BB&amp;quot;}&amp;#93;&amp;#93;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The transaction function &lt;code&gt;:fn/upsert-rev-stream&lt;/code&gt; handles the upsert complexity.&lt;/p&gt;&lt;pre&gt;&lt;code&gt; {:db/id #db/id &amp;#91;:db.part/user&amp;#93;
  :db/ident :fn/upsert-rev-stream
  :db/doc &amp;quot;The primary key of rev-stream is compound key&amp;quot;
  :db/fn #db/fn
  {:lang :clojure
   :params &amp;#91;db m&amp;#93;
   :code &amp;#40;if-let &amp;#91;id &amp;#40;ffirst
                      &amp;#40;d/q '&amp;#91;:find ?e
                             :in $ ?u ?t ?s
                             :where
                             &amp;#91;?e :rev-stream/stream-unique-id ?u&amp;#93;
                             &amp;#91;?e :rev-stream/writing-time ?t&amp;#93;
                             &amp;#91;?e :rev-stream/source ?s&amp;#93;&amp;#93;
                           db &amp;#40;:rev-stream/stream-unique-id m&amp;#41;
                           &amp;#40;:rev-stream/writing-time m&amp;#41;
                           &amp;#40;:rev-stream/source m&amp;#41;&amp;#41;&amp;#41;&amp;#93;
           &amp;#91;&amp;#40;-&amp;gt; &amp;#40;dissoc m :rev-stream/stream-unique-id
                        :rev-stream/writing-time
                        :rev-stream/source&amp;#41;
                &amp;#40;assoc :db/id id&amp;#41;&amp;#41;&amp;#93;
           &amp;#91;m&amp;#93;&amp;#41;}}
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id=&quot;cardinality&amp;#95;many&quot;&gt;Cardinality many&lt;/h4&gt;&lt;p&gt;Consider tha table with a cardinality-many schema &lt;code&gt;:order/accounting-data&lt;/code&gt; and &lt;code&gt;:order/product-unique-id&lt;/code&gt; with &lt;code&gt;:db.unique/identity&lt;/code&gt;. How to do upsert when we have &lt;code&gt;txes&lt;/code&gt; like below?&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;#91;#:order{:io-writing-time #inst &amp;quot;2019-04-01T02:39:00.000-00:00&amp;quot;,
         :service-category-enum :product.type/today,
         :accounting-data
         &amp;#91;#:accounting{:month &amp;quot;2019-04&amp;quot;, :revenue -2}
          #:accounting{:month &amp;quot;2019-05&amp;quot;, :revenue -3}
          #:accounting{:month &amp;quot;2019-02&amp;quot;, :revenue 4}
          #:accounting{:month &amp;quot;2019-01&amp;quot;, :revenue 5}&amp;#93;}&amp;#93;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With a db transaction function &lt;code&gt;upsert-order&lt;/code&gt;, we can simply write &lt;code&gt;txes&lt;/code&gt; as&lt;/p&gt;&lt;pre&gt;&lt;code&gt;  &amp;#91;&amp;#91;:fn/upsert-order
    #:order{:io-writing-time #inst &amp;quot;2019-04-01T02:39:00.000-00:00&amp;quot;,
            :service-category-enum :product.type/today,
            :accounting-data
            &amp;#91;#:accounting{:month &amp;quot;2019-04&amp;quot;, :revenue -2}
             #:accounting{:month &amp;quot;2019-05&amp;quot;, :revenue -3}
             #:accounting{:month &amp;quot;2019-02&amp;quot;, :revenue 4}
             #:accounting{:month &amp;quot;2019-01&amp;quot;, :revenue 5}&amp;#93;}&amp;#93;&amp;#93;
&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;The transaction function &lt;code&gt;:fn/upsert-order&lt;/code&gt; handles the upsert complexity.&lt;/p&gt;&lt;pre&gt;&lt;code&gt; {:db/id #db/id &amp;#91;:db.part/user&amp;#93;
  :db/ident :fn/upsert-order
  :db/doc &amp;quot;The :order/accounting-data is cardinality many.
  When insert semantic, transact `&amp;#91;m&amp;#93;`
  When update semantic, do retraction of :order/accounting-data first and then transact `m`  &amp;quot;
  :db/fn #db/fn
  {:lang :clojure
   :params &amp;#91;db m&amp;#93;
   :code &amp;#40;if-let &amp;#91;eid &amp;#40;ffirst
                      &amp;#40;d/q '&amp;#91;:find ?e
                             :in $ ?u
                             :where
                             &amp;#91;?e :order/product-unique-id ?u&amp;#93;&amp;#93;
                           db &amp;#40;:order/product-unique-id m&amp;#41;&amp;#41;&amp;#41;&amp;#93;
           &amp;#40;let &amp;#91;ad-refs &amp;#40;d/q '&amp;#91;:find &amp;#91;?a ...&amp;#93;
                                :in $ ?e
                                :where &amp;#91;?e :order/accounting-data ?a&amp;#93;&amp;#93;
                              db eid&amp;#41;
                 retracts &amp;#40;mapcat &amp;#40;fn &amp;#91;r&amp;#93;  &amp;#91;&amp;#91;:db/retractEntity r&amp;#93;
                                            &amp;#91;:db/retract eid :order/accounting-data r&amp;#93;&amp;#93;&amp;#41; ad-refs&amp;#41;&amp;#93;
             &amp;#40;conj &amp;#40;vec retracts&amp;#41; m&amp;#41;&amp;#41;
           &amp;#91;m&amp;#93;&amp;#41;}}
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;conclusions&quot;&gt;Conclusions&lt;/h2&gt;&lt;p&gt;From abstracting out idioms and patterns of ETL, I understand that context dependency is the primary cause of the complex application code. Both Datomic transaction functions and regular expression syntaxes of &lt;code&gt;clojure.spec&lt;/code&gt; can help to remove the context dependency of our application code. Use them wisely!&lt;/p&gt;
</description>
<pubDate>
Mon, 01 Jul 2019 00:00:00 +0000
</pubDate>
</item>
<item>
<guid>
https://humorless.github.io/posts-output/first-consulting
</guid>
<link>
https://humorless.github.io/posts-output/first-consulting
</link>
<title>
Lessons learned from the software consulting job
</title>
<description>
&lt;p&gt;I live in Taiwan and I can not find Clojure jobs here. Although the first legal gay wedding in Asia took place here, it seems that the real programming language innovation still needs some evangelists to spread it. Therefore, I decide to create Clojure job by myself. In January this year, I had a chance to develop enterprise software for a big company, and I chose Clojure as my primary technical stack.&lt;/p&gt;&lt;h2 id=&quot;technical&amp;#95;stack&amp;#95;issues&quot;&gt;Technical stack issues&lt;/h2&gt;&lt;p&gt;When I discussed with my clients about this enterprise software solution, we focused on the problem domain. However, when I told my clients that I want to use Clojure, Datomic, and ClojureScript, my clients said no. They said a lot of cliches like they never hear Clojure before, it would be difficult to find Clojure programmers. Then, I made some compromises: I would use React with javascript in frontend but Clojure in backend with Datomic as database. For Clojure, I provided the reason that the business requirements had temporal queries which were like a piece of cake for Datomic but very time-consuming for traditional relational databases.&lt;/p&gt;&lt;p&gt;After developing this project for a while, I regretted that I did not insist on ClojureScript. I really spent a lot of time on javascript boilerplate code, and the time spent did not bring any value to my clients.&lt;/p&gt;&lt;h2 id=&quot;a&amp;#95;very&amp;#95;simple&amp;#95;user&amp;#95;login&amp;#95;is&amp;#95;good&amp;#95;enough&amp;#95;for&amp;#95;a&amp;#95;small&amp;#95;group&amp;#95;of&amp;#95;users&quot;&gt;A very simple user login is good enough for a small group of users&lt;/h2&gt;&lt;p&gt;The enterprise software solution needed to be an on-premise solution, installed on the private network at company offices. There would be about 30 users login everyday. At the beginning, I thought three different ways to solve the user login problems:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Single signed-on with other enterprise software already existed&lt;/li&gt;&lt;li&gt;Leverage third party authorization service&lt;/li&gt;&lt;li&gt;Traditional user login backend APIs and frontend UI with login/register/user management functions like resetting password.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Option 2 might be fast enough, but my clients did not like third party service.&lt;/p&gt;&lt;p&gt;My final proposal was a login module like this:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Frontend UI provided the login and password modification functions to ordinary users.&lt;/li&gt;&lt;li&gt;The administrator of this system used ETL (extract-transform-load) to manage user accounts. Given this design, we did not need any user registration or user accounts management UI.&lt;/li&gt;&lt;/ol&gt;&lt;h2 id=&quot;revenue&amp;#95;spreading&amp;#95;problem&quot;&gt;Revenue spreading problem&lt;/h2&gt;&lt;p&gt;There was a business requirement, I called it as revenue spreading problem, in this enterprise software.&lt;/p&gt;&lt;p&gt;Revenue spreading problem:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;For every order, there is a start date and end date of this order. The total days of an order are &lt;code&gt;&amp;#40;end date - start date + 1&amp;#41;&lt;/code&gt;&lt;/li&gt;&lt;li&gt;For every order, there is a net revenue of this order.&lt;/li&gt;&lt;li&gt;For every order, we need to calculate the monthly revenue. The definition of monthly revenue is &lt;code&gt;net revenue &amp;#42; the revenue days of certain month / total days&lt;/code&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;If an order starts at 5/5, ends at 6/8 with total revenue as 35 dollars, then the total days of this order is (27+8) = 35 days. Also, the monthly revenue of May is 27 dollars and monthly revenue of June is 8 dollars.&lt;/p&gt;&lt;p&gt;To solve this, at the beginning, I used &lt;code&gt;first-day-of-the-month&lt;/code&gt; and &lt;code&gt;last-day-of-the-month&lt;/code&gt; in &lt;code&gt;clj-time&lt;/code&gt; library to calculate how many days within a month. The first version solution was a traditional imperative solution. I quickly found that I could do better with functional thinking.&lt;/p&gt;&lt;p&gt;My improved version:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Generate a sequence of time using &lt;code&gt;period-sec&lt;/code&gt; in &lt;code&gt;clj-time&lt;/code&gt;. The period of time is just one day long and the start date/end date are the start date/end date of certain order.&lt;/li&gt;&lt;li&gt;Apply &lt;code&gt;group-by&lt;/code&gt; to the step 1 day sequence with the grouping function that can return the year-month-string of a certain date. For example, a date of 2009/05/01 returns &quot;2019-05&quot;.&lt;/li&gt;&lt;li&gt;Calculate how many days of each group of the step 2 result.&lt;/li&gt;&lt;li&gt;Spread the revenue using step 3 result.&lt;/li&gt;&lt;/ol&gt;&lt;h2 id=&quot;ci/cd&amp;#95;issues&quot;&gt;CI/CD issues&lt;/h2&gt;&lt;p&gt;I was not an expert of DevOps. When I needed to deploy the project, I took some time to study ansible because the great book &lt;code&gt;Deploying Your First Clojure App ...From the Shadows shows&lt;/code&gt; introduced ansible. I still felt ansible is a great tool worth learning, however, the target servers were under the bastion host.&lt;/p&gt;&lt;p&gt;Engineers in the same company told me that they installed a Drone CI/CD server in the virtual private network behind the bastion host. As a Clojure developer, I decided to use LambdaCD. Actually, it was even simpler than Drone. Parentheses abundant lisp clj files were more expressive than yaml files.&lt;/p&gt;&lt;p&gt;When I encountered problems, I asked questions at LambdaCD github repo. Within two days, the author of LambdaCD kindly replied my questions. I thought LambdaCD is worth of recommendation, both the quality of the software and quick response.&lt;/p&gt;&lt;h2 id=&quot;evangelism&amp;#95;of&amp;#95;clojure&quot;&gt;Evangelism of Clojure&lt;/h2&gt;&lt;p&gt;Given that I did software consulting at a big company, I could apply for technical talk inside the company. Grabbing the chance, I introduced Clojure to 10~ developers. Those who already had experience with Scala showed more interests than others. Good beginning anyways, I thought. Here is the &lt;a href='https://www.slideshare.net/humorless/the-productivity-brought-by-clojure-149170292/'&gt;slide&lt;/a&gt; of technical talk.&lt;/p&gt;
</description>
<pubDate>
Sun, 23 Jun 2019 00:00:00 +0000
</pubDate>
</item>
<item>
<guid>
https://humorless.github.io/posts-output/assembly
</guid>
<link>
https://humorless.github.io/posts-output/assembly
</link>
<title>
Using datomic with Luminus: Where to put our queries?
</title>
<description>
&lt;p&gt;If we build a Luminus project with db option other than datomic, for example &lt;code&gt;+postgres&lt;/code&gt;, the code arrangement is much more straight forward. Open the file &lt;code&gt;resources/sql/queries.sql&lt;/code&gt;, and put sql query and sql transaction command in this file. Then, we can just &lt;code&gt;require&lt;/code&gt; the &lt;code&gt;xxx.db.core&lt;/code&gt; namespace, the db queries or commands are totally available. &lt;/p&gt;&lt;h2 id=&quot;where&amp;#95;to&amp;#95;put&amp;#95;the&amp;#95;db&amp;#95;queries&amp;#95;if&amp;#95;we&amp;#95;use&amp;#95;db&amp;#95;option&amp;#95;as&amp;#95;+datomic?&quot;&gt;Where to put the db queries if we use db option as +datomic?&lt;/h2&gt;&lt;p&gt;Put datomic queries in the same file with connection state in &lt;code&gt;xxx.db.core&lt;/code&gt; is the first attempt I tried. However, the datomic queries actually execute in the application program runtime, not in the db server runtime. Also, if we design the query function to accept datomic db value as input argument, then our query function will become pure functions.&lt;/p&gt;&lt;p&gt;After discovering that our query functions are pure functions, I decide to arrange my application namespaces like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;prj.&amp;#91;service&amp;#93;.assembly ---&amp;gt; prj.db.core
                            ;; assembly only refers conn variable from prj.db.core
                       ---&amp;gt; datomic.api
                       ---&amp;gt; prj.db.query
                             ;; I make all the query functions as pure functions and put them here.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The namespace &lt;code&gt;&amp;#91;service&amp;#93;.assembly&lt;/code&gt; is used to &lt;em&gt;wire&lt;/em&gt; utility funcitons (pure functions) and stateful things like datomic connection together.&lt;/p&gt;&lt;h2 id=&quot;where&amp;#95;to&amp;#95;put&amp;#95;the&amp;#95;db&amp;#95;transactions?&quot;&gt;Where to put the db transactions?&lt;/h2&gt;&lt;p&gt;Given that &lt;code&gt;&amp;#91;service&amp;#93;.assembly&lt;/code&gt; refers &lt;code&gt;conn&lt;/code&gt;, I decide to call &lt;code&gt;&amp;#40;d/transact conn ... &amp;#41;&lt;/code&gt; in this namespace. However, I still need to do some transformation to get proper transaction data that can directly put into &lt;code&gt;d/transact&lt;/code&gt;. Therefore, the arrangement will be like:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;prj.&amp;#91;service&amp;#93;.assembly ---&amp;gt; prj.db.command
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In &lt;code&gt;prj.db.command&lt;/code&gt;, I put the transformation functions that used to create datomic transaction data. The transformation functions are also pure functions.&lt;/p&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;&lt;p&gt;Compared to traditional sql db option, the reasonable place to put database queries of datomic db option is totally different. &lt;/p&gt;&lt;p&gt;In traditonal sql db options:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;We write HugSQL sql sourcre files with sql and tags.&lt;/li&gt;&lt;li&gt;We need integration test to test these queries.&lt;/li&gt;&lt;li&gt;We place our queries in &lt;code&gt;resource/sql/queries.sql&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;In datomic db options:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;We write Clojure source files with data.&lt;/li&gt;&lt;li&gt;We only need unit test to test these queries.&lt;/li&gt;&lt;li&gt;We place our queries in &lt;code&gt;prj.db.query&lt;/code&gt; namespace.&lt;/li&gt;&lt;/ul&gt;
</description>
<pubDate>
Wed, 12 Jun 2019 00:00:00 +0000
</pubDate>
</item>
<item>
<guid>
https://humorless.github.io/posts-output/vagrant
</guid>
<link>
https://humorless.github.io/posts-output/vagrant
</link>
<title>
Clojure development environment by Vagrant
</title>
<description>
&lt;p&gt;If you want to have a portable Clojure development environment and you use Vagrant, vim-fireplace, you may consider to try my &lt;a href='https://github.com/humorless/dotfiles'&gt;Vagrantfile&lt;/a&gt;. &lt;/p&gt;&lt;pre&gt;&lt;code&gt;git clone https://github.com/humorless/dotfiles
cd dotfiles
vagrant up
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;certain&amp;#95;part&amp;#95;of&amp;#95;vagrantfile&amp;#95;you&amp;#95;may&amp;#95;need&amp;#95;to&amp;#95;remove.&quot;&gt;Certain part of vagrantfile you may need to remove.&lt;/h2&gt;&lt;pre&gt;&lt;code class=&quot;ruby&quot;&gt;if Vagrant.has&amp;#95;plugin?&amp;#40;&amp;quot;vagrant-timezone&amp;quot;&amp;#41;
  config.timezone.value = &amp;quot;Asia/Taipei&amp;quot;
end
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;the&amp;#95;beginning&amp;#95;of&amp;#95;this&amp;#95;repo&quot;&gt;The beginning of this repo&lt;/h2&gt;&lt;p&gt;Several years before, I created a github repo called &lt;code&gt;dotfiles&lt;/code&gt;, which is used to record my vimrc file. Later, every time when I changed my job, I modified my favorite vim plugin. I modified my vim plugin collection so many times. Sometimes, I installed certain vim cool plugin, but after a while, I totally forgot how to use it. There are not too many vim plugins in this dotfiles, because I am not a vim &lt;code&gt;l33t hax0r&lt;/code&gt;.&lt;/p&gt;&lt;h2 id=&quot;development&amp;#95;and&amp;#95;deployment&quot;&gt;development and deployment&lt;/h2&gt;&lt;p&gt;I have had a job that I needed to work at AWS cloud9 environment. Some of my jobs required me to install totally new development environment. Recently, I needed to deploy Clojure enviroment on production system, so I learned a little &lt;code&gt;ansible&lt;/code&gt; and I used ansible to install java8.&lt;/p&gt;&lt;p&gt;One day, I found that vagrant can use ansible to do provisioning, so I combined them together.&lt;/p&gt;&lt;h2 id=&quot;some&amp;#95;nice&amp;#95;tools&amp;#95;i&amp;#95;cannot&amp;#95;live&amp;#95;without&quot;&gt;Some nice tools I cannot live without&lt;/h2&gt;&lt;p&gt;&lt;code&gt;nvm&lt;/code&gt; is important to me because I usually need to change node version. &lt;code&gt;autojump&lt;/code&gt; is also important. &lt;/p&gt;
</description>
<pubDate>
Mon, 13 May 2019 00:00:00 +0000
</pubDate>
</item>
<item>
<guid>
https://humorless.github.io/posts-output/datomic
</guid>
<link>
https://humorless.github.io/posts-output/datomic
</link>
<title>
Using Datomic in my app
</title>
<description>
&lt;h2 id=&quot;the&amp;#95;background&amp;#95;of&amp;#95;this&amp;#95;post&quot;&gt;The background of this post&lt;/h2&gt;&lt;p&gt;I began to use Datomic seriously in my project at work from February 2019. Now, it is time to write down certain experience. When I just began, I found a lot of documents talking about how to use Datomic. However, I still found certain points worth to mention from my project.&lt;/p&gt;&lt;h2 id=&quot;query&amp;#95;api&amp;#95;and&amp;#95;pull&amp;#95;api&amp;#95;are&amp;#95;enough&quot;&gt;Query API and Pull API are enough&lt;/h2&gt;&lt;p&gt;When I just begin to write Datomic, soon I found &lt;a href='https://vvvvalvalval.github.io/posts/2016-07-24-datomic-web-app-a-practical-guide.html'&gt;post&lt;/a&gt; from Val. In the post, Val used Entity API. &lt;/p&gt;&lt;p&gt;In my project, I used only Query API and Pull API. Query API was for taking out entity id mostly and Pull API was for pulling out necessary field or sometimes doing some 'join'. I think the article &lt;a href='http://blog.cognitect.com/blog/2017/4/21/separation-of-concerns-in-datomic-query-datalog-query-and-pull-expressions'&gt;SEPARATION OF CONCERNS IN DATOMIC QUERY: DATALOG QUERY AND PULL EXPRESSIONS&lt;/a&gt; has explained similar idea. Entity API is also good, but Pull API is even better.&lt;/p&gt;&lt;h2 id=&quot;occasionally,&amp;#95;a&amp;#95;generalized&amp;#95;cas&amp;#95;(compare-and-swap)&amp;#95;is&amp;#95;needed,&amp;#95;or&amp;#95;you&amp;#95;need&amp;#95;to&amp;#95;use&amp;#95;stamp.&quot;&gt;Occasionally, a generalized CAS (compare-and-swap) is needed, or you need to use stamp.&lt;/h2&gt;&lt;p&gt;In my project, I need to use Datomic to model:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;The user can propose request. Initially, the request is in open status.&lt;/li&gt;&lt;li&gt;The admin can approve/reject/modify the user request.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;The request schema is like:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;:req/status     ;; cardinality one. It can be - open, modified, approved, rejected
:req/things     ;; cardinality many. &amp;#91;thing-id ...&amp;#93;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The admin sees the user requests from a web application UI. There are three options for admin: approve, reject, modify. If a request is approved or rejected, then this request is no longer alive. It will disappear from admin UI. However, if a request is modified, it can still be approved, be rejected, or be modified again. When the request is modified, only the &lt;code&gt;req/things&lt;/code&gt; can be modified. There may be multiple admins operating at the same time on the same request in this system.&lt;/p&gt;&lt;p&gt;The state diagram of request status is:&lt;/p&gt;&lt;pre&gt;&lt;code&gt; open -&amp;gt; modified 
 modified -&amp;gt; modified 
 {modified, open} -&amp;gt; approved &amp;#40;done&amp;#41;
 {modified, open} -&amp;gt; rejected &amp;#40;done&amp;#41;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Consider a situation: Two admins A and B process on the same request and they do not sense each other. They push the button at the same time. One admin A approves the request and another admin B modifies the request. The request was originally modified before, so it is at the status &lt;code&gt;modified&lt;/code&gt; when the two admins process it.&lt;/p&gt;&lt;p&gt;The correct behavior of the system could be two possibilities: Either operation of admin A is successful or operation of admin B is successful. If operation of admin A is successful first, then the request can not be modified anymore. If the operation of admin B is successful first, then the approval of A should not happen, because the &lt;code&gt;req/things&lt;/code&gt; is already modified, but the admin A approved different set of &lt;code&gt;req/things&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;I consider to utilize &lt;code&gt;db.fn/cas&lt;/code&gt; to guarantee that only one operation of admin A or admin B can succeed. However, &lt;code&gt;db.fn/cas&lt;/code&gt; does not work on attributes with cardinality many.&lt;/p&gt;&lt;p&gt;I think there are two ways to solve this mutually exclusive concurrent operation problem:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Add an extra schema &lt;code&gt;req/stamp&lt;/code&gt; into req. The stamp is initially 0. Every operation will increase it by 1. Then I can use this stamp and &lt;code&gt;db.fn/cas&lt;/code&gt; to ensure the logically strictness of the operations.&lt;/li&gt;&lt;li&gt;Install some customized db function, which can do CAS on cardinality many to ensure the logically strictness.&lt;/li&gt;&lt;/ol&gt;&lt;h2 id=&quot;db&amp;#95;enumeration&quot;&gt;DB Enumeration&lt;/h2&gt;&lt;p&gt;I use &lt;code&gt;:db/ident&lt;/code&gt; to do enumerations in my project:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;#91;:db/add #db/id &amp;#91;:db.part/user&amp;#93; :db/ident :product.type/account&amp;#93;
&amp;#91;:db/add #db/id &amp;#91;:db.part/user&amp;#93; :db/ident :product.type/display&amp;#93;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;They are enumerations that represent the different products. Then, there are certain related issues associated with this modeling.&lt;/p&gt;&lt;h3 id=&quot;how&amp;#95;to&amp;#95;pull&amp;#95;out&amp;#95;all&amp;#95;the&amp;#95;enumerations&amp;#95;of&amp;#95;the&amp;#95;same&amp;#95;type?&quot;&gt;How to pull out all the enumerations of the same type?&lt;/h3&gt;&lt;p&gt;I deliberately set the enumeration of the same type with the same namespace, so I need to prepare a query that can filter based on the same namespace. It is very convenient that we can directly use Clojure function in Datomic query.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;#40;defn product-enum-eids
  &amp;quot;all the product enumeration eids&amp;quot;
  &amp;#91;db&amp;#93;
  &amp;#40;d/q '&amp;#91;:find &amp;#91;?e ...&amp;#93;
         :in $ ?nsp
         :where &amp;#91;?e :db/ident ?attr&amp;#93;
         &amp;#91;&amp;#40;namespace ?attr&amp;#41; ?nsp&amp;#93;&amp;#93;     ;;Datomic Function expression binds the ?nsp variable
       db &amp;quot;product.type&amp;quot;&amp;#41;&amp;#41;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;how&amp;#95;to&amp;#95;store&amp;#95;the&amp;#95;external&amp;#95;string&amp;#95;and&amp;#95;enumeration&amp;#95;mapping&amp;#95;in&amp;#95;datomic?&quot;&gt;How to store the external string and enumeration mapping in Datomic?&lt;/h3&gt;&lt;p&gt;Once again, I use simple schema with no magic.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;   {:db/doc &amp;quot;External name associated with a db enumeration value&amp;quot;
    :db/ident :enum/name
    :db/valueType :db.type/string
    :db/cardinality :db.cardinality/one
    :db/unique :db.unique/identity
    :db/id #db/id &amp;#91;:db.part/db&amp;#93;
    :db.install/&amp;#95;attribute :db.part/db}

   {:db/doc &amp;quot;db enumeration value&amp;quot;
    :db/ident :enum/value
    :db/valueType :db.type/ref
    :db/cardinality :db.cardinality/one
    :db/unique :db.unique/identity
    :db/id #db/id &amp;#91;:db.part/db&amp;#93;
    :db.install/&amp;#95;attribute :db.part/db}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When we need to import data from files and we need to map external names to DB enumeration values, we can pull out all the mapping at once. &lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;#40;defn name2enum-table
  &amp;quot;create a mapping table that can lookup enumeration from string name.&amp;quot;
  &amp;#91;db&amp;#93;
  &amp;#40;into {}  &amp;#40;d/q '&amp;#91;:find ?k ?enum
                   :where
                   &amp;#91;?e :enum/name ?k&amp;#93;
                   &amp;#91;?e :enum/value ?v&amp;#93;
                   &amp;#91;?v :db/ident ?enum&amp;#93;&amp;#93;
                 db&amp;#41;&amp;#41;&amp;#41;
&lt;/code&gt;&lt;/pre&gt;
</description>
<pubDate>
Sat, 27 Apr 2019 00:00:00 +0000
</pubDate>
</item>
<item>
<guid>
https://humorless.github.io/posts-output/REPL
</guid>
<link>
https://humorless.github.io/posts-output/REPL
</link>
<title>
REPL tips
</title>
<description>
&lt;p&gt;從今年 2 月開始，接了一個公司內部應用軟體的專案開發，我用 clojure + luminus + datomic 來實作。不知不覺也就每天寫 clojure 的 REPL 近兩個月了。每天玩 REPL 之後，很快就發現一些過去我用 REPL 的盲點。&lt;/p&gt;&lt;h3 id=&quot;沒有善用&amp;#95;&lt;code&gt;clojure.repl/pprint&lt;/code&gt;&quot;&gt;沒有善用 &lt;code&gt;clojure.repl/pprint&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;  沒有善用的主要原因，自然是因為在 &lt;code&gt;fireplace.vim&lt;/code&gt; 的環境下，一開始我沒有特別做一些設定時，直接做 cpp, cqp 之類 REPL 操作，並不會有 pretty print 的輸出。後來，我總算是下定決心，把 &lt;a href='https://github.com/humorless/dotfiles/blob/master/lein/profiles.clj'&gt;leiningen profiles&lt;/a&gt; 設定好，加入了一個叫 &lt;code&gt;vinyasa&lt;/code&gt; 的 leiningen dependency&lt;/p&gt;&lt;p&gt;  設定好之後，就可以用 &lt;code&gt;&amp;#40;&amp;gt;pprint ...&amp;#41;&lt;/code&gt; 來做 pretty print 。&lt;/p&gt;&lt;h3 id=&quot;沒有善用&amp;#95;&lt;code&gt;&amp;#42;1&lt;/code&gt;&amp;#95;&lt;code&gt;&amp;#42;2&lt;/code&gt;&quot;&gt;沒有善用 &lt;code&gt;&amp;#42;1&lt;/code&gt; &lt;code&gt;&amp;#42;2&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;  過去，我在做 REPL 操作時，常常做的事情是這樣子：&lt;/p&gt;&lt;p&gt;  &lt;code&gt;&amp;#40;f1 a b c&amp;#41;&lt;/code&gt; =&gt; 試到結果正確&lt;/p&gt;&lt;p&gt;  &lt;code&gt;&amp;#40;f2 &amp;#40;f1 a b c&amp;#41; d&amp;#41;&lt;/code&gt; =&gt; 也是試到結果也正確&lt;/p&gt;&lt;p&gt;  &lt;code&gt;&amp;#40;f3 &amp;#40;f2 &amp;#40;f1 a b c&amp;#41; d&amp;#41; e&amp;#41;&lt;/code&gt; =&gt; 然後指令就愈來愈長, 愈來愈難下&lt;/p&gt;&lt;p&gt;  其實不用這樣子麻煩，第二次可以這樣子下指令 &lt;code&gt;&amp;#40;f2 &amp;#42;1 d&amp;#41;&lt;/code&gt; 。&lt;/p&gt;
</description>
<pubDate>
Sat, 30 Mar 2019 00:00:00 +0000
</pubDate>
</item>
<item>
<guid>
https://humorless.github.io/posts-output/dependency-injection
</guid>
<link>
https://humorless.github.io/posts-output/dependency-injection
</link>
<title>
dependency injection with Clojure
</title>
<description>
&lt;p&gt;寫 clojure 的時候，雖然套用了 REPL-driven development 的開發方式，已經相對可以讓大多數的函數很快地做過測試。但是，隨著要開發的專案愈來愈大，還是一樣需要用標準的寫法來寫單元測試 (unit test) 。有一個非正規的統計，如果是 Ruby on Rail 的專案，一般而言，90% 的函數都是有副作用的。然而， clojure 語言的專案，往往只有 40% 的函數帶有副作用。&lt;/p&gt;&lt;p&gt;即使是寫 clojure 語言，還是會遇到有 side effect 的函數，那比較好的寫法是怎麼樣呢？&lt;/p&gt;&lt;p&gt;&lt;!&amp;ndash;more&amp;ndash;&gt;&lt;/p&gt;&lt;p&gt;我查了一下 stackoverflow 之後，很快就找到了一個很好用的函數 &lt;code&gt;with-redefs&lt;/code&gt; 。 stackoverflow 上的答案大意如下： 由於 clojure 語言有 Dynamic binding 的特性，使用 &lt;code&gt;with-redefs&lt;/code&gt; 就可以實現同樣的語意了。&lt;/p&gt;&lt;p&gt;我試了一下，還真的管用，範例如下：&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;clojure&quot;&gt;&amp;#40;deftest platform-contact-test
  &amp;#40;testing &amp;quot;platform-contact&amp;quot;
    ; use the DI technique to test the function platform-contact
    &amp;#40;is &amp;#40;= 170
           &amp;#40;with-redefs &amp;#91;get-platform-contact &amp;#40;fn &amp;#91;&amp;#95;&amp;#93; &amp;#40;slurp &amp;quot;./resources/contact&amp;#95;data.txt&amp;quot;&amp;#41;&amp;#41;&amp;#93;
             &amp;#40;count &amp;#40;platform-contact &amp;#40;temp-platform-all&amp;#41;&amp;#41;&amp;#41;&amp;#41;&amp;#41;&amp;#41;&amp;#41;&amp;#41;

&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在這個範例中，原本的 &lt;code&gt;get-platform-contact&lt;/code&gt; 函數是一個有副作用的函數，它會被 &lt;code&gt;platform-contact&lt;/code&gt; 函數呼叫。 &lt;code&gt;get-platform-contact&lt;/code&gt; 函數會發出一個 http request ，並且傳回遠端 server 上的資料，所以如果沒有加以代換，單元測試就會非常慢。用了 &lt;code&gt;with-redefs&lt;/code&gt; 之後，就可以輕易地將 &lt;code&gt;get-platform-contact&lt;/code&gt; 代換成一個會傳回固定檔案資料的函數，如此就可以執行快速的單元測試了。&lt;/p&gt;&lt;p&gt;對於 clojure 這種先進的特性， stackoverflow 上有一句評論： Needing a framework for DI is really just compensating for a lack of sufficient features in the language itself.&lt;/p&gt;
</description>
<pubDate>
Wed, 12 Jul 2017 00:00:00 +0000
</pubDate>
</item>
<item>
<guid>
https://humorless.github.io/posts-output/groupby
</guid>
<link>
https://humorless.github.io/posts-output/groupby
</link>
<title>
groupby
</title>
<description>
&lt;p&gt;一開始是我在寫 &lt;a href='http://www.4clojure.com/problem/63'&gt;4clojure&lt;/a&gt; 的練習題的時候，寫到了一個題目，要重新實現 clojure 語言的 groupby 函數。我糾結了好一陣子，又查了不少資料，才勉強用 reduce 寫出來。然而，最近卻在工作中，用上了 groupby 。&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;clojure&quot;&gt;&amp;#40;fn f &amp;#91;k coll&amp;#93;
  &amp;#40;reduce
    &amp;#40;fn &amp;#91;c v&amp;#93;
      &amp;#40;update-in c &amp;#91;&amp;#40;k v&amp;#41;&amp;#93; &amp;#40;fnil conj &amp;#91;&amp;#93;&amp;#41; v&amp;#41;&amp;#41;
    {} coll&amp;#41;&amp;#41;

&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;!&amp;ndash;more&amp;ndash;&gt; 工作上遇到的問題是要重構同事寫的程式碼。程式碼做的事情是：「接受資料庫 dump 的 json 輸出，跑兩層很複雜的迴圈，對原始的資料做主鍵交換的處理，然後將資料存入 mysql 資料庫。」資料庫 dump 出來的 json 大概長成如下的樣子：&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;json&quot;&gt;  &amp;quot;result&amp;quot;: &amp;#91;
    {
      &amp;quot;platform&amp;quot;: &amp;quot;c01.i01&amp;quot;,
      &amp;quot;ip&amp;#95;list&amp;quot;: &amp;#91;
        {
          &amp;quot;ip&amp;quot;: &amp;quot;192.168.0.1&amp;quot;,
          &amp;quot;hostname&amp;quot;: &amp;quot;ggyy6699&amp;quot;
        },
        {
          &amp;quot;ip&amp;quot;: &amp;quot;192.169.1.1&amp;quot;,
          &amp;quot;hostname:&amp;quot;: &amp;quot;ggyy7700&amp;quot;
        }
      &amp;#93;
    },
    {
      &amp;quot;platform&amp;quot;: &amp;quot;c01.i05&amp;quot;,
      &amp;quot;ip&amp;#95;list&amp;quot;: &amp;#91;
        {
          &amp;quot;ip&amp;quot;: &amp;quot;192.168.0.2&amp;quot;,
          &amp;quot;hostname&amp;quot;: &amp;quot;ggkk8899&amp;quot;
        },
        {
          &amp;quot;ip&amp;quot;: &amp;quot;192.169.1.2&amp;quot;,
          &amp;quot;hostname:&amp;quot;: &amp;quot;ggkk9900&amp;quot;
        }
      &amp;#93;
    }
  &amp;#93;
}

&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;從這個 json 來看的話，&lt;code&gt;platform&lt;/code&gt; 是主鍵 (primary key) 。而每一個 &lt;code&gt;platform&lt;/code&gt; 下之下會有多個 &lt;code&gt;hostname&lt;/code&gt; 。而程式碼做的事情是，先解析這個 json ，重新整理之後，讓 &lt;code&gt;hostname&lt;/code&gt; 變成主鍵 (primary key) ，並且做成一行又一行的 row ，最後要存入關聯式資料庫。讓我感到困擾的地方是因為整理屬性與屬性之間複雜關系的程式碼，都塞在雙重迴圈裡頭，所以雙重迴圈就變得很複雜，而且這一段雙重迴圈的程式碼也無法複用，難以修改、難以維護。&lt;/p&gt;&lt;p&gt;轉換成用資料庫的觀點來看待這個問題之後，就得到了還不錯的解法：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;資料庫的 dump 輸出，本質上也是 join 兩張資料表的結果輸出，所以主鍵 (primary key) 本來就有可能交換。&lt;/li&gt;&lt;li&gt;既然要解析的資料是 join 之後的結果，所以有效的處理方式是這樣子：&lt;ol&gt;&lt;li&gt;先將 json 的資料跑完簡單的雙重迴圈，雙重迴圈只做一件事，只將將資料做展開 (unfolding)，變成 join 完成的樣子。&lt;/li&gt;&lt;li&gt;python 的 &lt;code&gt;itertools.groupby&lt;/code&gt; ，可以讓資料表 (table) 重新整理，產生出以任意的 column 做為主鍵 (primary key) 的新資料表 (table)。&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;程式碼如下：&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;def get&amp;#95;h&amp;#95;platforms&amp;#40;res&amp;#41;:
    &amp;quot;&amp;quot;&amp;quot; sample output
    ctl-zj-061-130-028-019 &amp;#91;'c01.p02', 'c01.p02-kugou'&amp;#93;
    ctl-zj-061-130-028-020 &amp;#91;'c01.p02', 'c01.p02-kugou'&amp;#93;
    ctl-zj-061-130-028-022 &amp;#91;'c01.p02', 'c01.p02-kugou'&amp;#93;
    &amp;quot;&amp;quot;&amp;quot;
    product = &amp;#91;&amp;#40;p&amp;#91;&amp;quot;platform&amp;quot;&amp;#93;, device&amp;#91;&amp;quot;hostname&amp;quot;&amp;#93;&amp;#41;
               for p in res&amp;#91;&amp;quot;result&amp;quot;&amp;#93; for device in p&amp;#91;&amp;quot;ip&amp;#95;list&amp;quot;&amp;#93;&amp;#93;
    data = sorted&amp;#40;product, key=lambda x: x&amp;#91;1&amp;#93;&amp;#41;
    for key, grp in itertools.groupby&amp;#40;data, key=lambda x: x&amp;#91;1&amp;#93;&amp;#41;:
        print&amp;#40;key, list&amp;#40;map&amp;#40;lambda x: x&amp;#91;0&amp;#93;, set&amp;#40;grp&amp;#41;&amp;#41;&amp;#41;&amp;#41;
&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;
</description>
<pubDate>
Sun, 21 May 2017 00:00:00 +0000
</pubDate>
</item>
<item>
<guid>
https://humorless.github.io/posts-output/pattern
</guid>
<link>
https://humorless.github.io/posts-output/pattern
</link>
<title>
pattern
</title>
<description>
&lt;h2 id=&quot;patterns&amp;#95;=&amp;#95;programming&amp;#95;with&amp;#95;abstactions&amp;#95;that&amp;#95;are&amp;#95;not&amp;#95;powerful&amp;#95;enough&quot;&gt;patterns = programming with abstactions that are not powerful enough&lt;/h2&gt;&lt;p&gt;先來引述一下 Paul Graham 的句子&lt;blockquote&gt;&lt;p&gt; When I see patterns in my programs, I consider it a sign of trouble. The shape of a program should reflect only the problem it needs to solve. Any other regularity in the code is a sign, to me at least, that I'm using abstractions that aren't powerful enough. &lt;/p&gt;&lt;footer&gt; Paul Graham - Revenge of the Nerds&lt;/footer&gt; &lt;/blockquote&gt;為了想出可以妥善解釋這段話的意思的 non-trivial 範例，其實我還想了滿久的。不料真的就在我學習 clojure 語言的過程之中找到了。這個範例是對某個 array 的每一個元素，做相同的運算處理：一個是循序處理、一個是平行處理。&lt;/p&gt;&lt;p&gt;&lt;!&amp;ndash;more&amp;ndash;&gt;&lt;/p&gt;&lt;h3 id=&quot;golang&amp;#95;的兩個版本&quot;&gt;golang 的兩個版本&lt;/h3&gt;&lt;p&gt;循序處理的版本&lt;pre&gt;&lt;code class=&quot;golang&quot;&gt;res := make&amp;#40;&amp;#91;&amp;#93;float, N&amp;#41;;
for i,xi := range data {
    func &amp;#40;i int, xi float&amp;#41; {
        res&amp;#91;i&amp;#93; = doSomething&amp;#40;i,xi&amp;#41;;
    } &amp;#40;i, xi&amp;#41;;
}

&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;平行處理的版本&lt;pre&gt;&lt;code class=&quot;golang&quot;&gt;type empty {}
...
data := make&amp;#40;&amp;#91;&amp;#93;float, N&amp;#41;;
res := make&amp;#40;&amp;#91;&amp;#93;float, N&amp;#41;;
sem := make&amp;#40;chan empty, N&amp;#41;;  // semaphore pattern
...
for i,xi := range data {
    go func &amp;#40;i int, xi float&amp;#41; {
        res&amp;#91;i&amp;#93; = doSomething&amp;#40;i,xi&amp;#41;;
        sem &amp;lt;- empty{};
    } &amp;#40;i, xi&amp;#41;;
}
// wait for goroutines to finish
for i := 0; i &amp;lt; N; ++i { &amp;lt;-sem }
&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;h3 id=&quot;clojure&amp;#95;的兩個版本&quot;&gt;clojure 的兩個版本&lt;/h3&gt;&lt;p&gt;循序處理的版本&lt;pre&gt;&lt;code class=&quot;clj&quot;&gt;&amp;#40;defn myfun &amp;#91;coll&amp;#93;
  &amp;#40;map doSomething coll&amp;#41;&amp;#41;
&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;平行處理的版本&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;clj&quot;&gt;&amp;#40;defn myfun &amp;#91;coll&amp;#93;
  &amp;#40;pmap doSomething coll&amp;#41;&amp;#41;

&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;抽象層次的差異&quot;&gt;抽象層次的差異&lt;/h3&gt;&lt;p&gt;比較這兩種語言寫的四段程式碼，很快可以發現，循序處理的範例都相當的簡單。然而，當換成平行處理的版本時， golang 的實作比 clojure 難多了。需要用 golang 的 channel 做出一個 semaphore 的 pattern 才能實現。而相較之下， clojure 把 map 換成 pmap 就可以了。由此可見， clojure 在這個例子之中，是一種足夠強的抽象層，可以輕易地去表達這個平行處理的語意。&lt;/p&gt;
</description>
<pubDate>
Tue, 28 Feb 2017 00:00:00 +0000
</pubDate>
</item>
</channel>
</rss>
