Wednesday, November 22, 2023

Clojure 取代 if 的技巧 (3) --- Atom validator

;; Using :validator to replace the `if`
(def a 
  (atom 3 :validator pos?))

(swap! a dec)

;; Without using `:validator`, we need to put a `if`
(def b
  (atom 3))

(swap! b (fn [n]
           (if (pos? (dec n))
             (dec n)
             (throw (ex-info "content must be positive"
                             {:a 1})))))


Wednesday, November 15, 2023

*out* 被 captured 時的變通之道

在 Clojure , *out* 是 System/out 這個變數被 java.io.OutputStreamWriter 的物件。

有的時候,有一些 codebase ,*out* 會被 captured ,這種時候,無論怎麼 print ,都無法 print 成功。這種時候,簡單的變通之道是:

(add-tap (fn [o] (.println System/out o)))
(tap> DATA)


Friday, October 27, 2023

Clojure 取代 if 的技巧 (2) --- cond->

問題:

有時候,我們會想要表達一種語意 (semantic)

if (c) {
  hm[x] = y
}

這種語意在 Clojure 會略顯得冗長

(if c
    (assoc hm :x y)
    hm)

但是,其實可以這樣子表達

(cond-> hm
   c (assoc :x y))







Clojure 取代 if 的技巧 (1) --- fnil

問題:
我們需要對一個 Clojure HashMap hm 裡的某個 path 做操作,插入 val 值。需要滿足的條件為:

  • 如果該 path 為空,就插入空的 vector,並且在該 vector 裡插入 val 
  • 如果該 path 已經有一個 vector  ,就把 val 插入 vector 的尾端
直覺的寫法可能是:

(if (nil? (get-in hm path)
    (assoc-in hm path [val])
    (update-in hm path conj val))

但是,這又略顯得重複、冗長

解法:
  • (update hm path (fnil conj []) val)
  • (update-in hm path (fnil conj []) val)


Clojure 的 tree 表示法

我本來是比較喜歡 (2) ,多想了一陣子之後,又覺得 (1) 似乎更好一些些。

使用 (1) 的表示法還有一個理由: 假設,你現在是要從一堆已經 serialized data 裡,一個又一個的 item 去讀取,最後會建出這顆樹。很有可能,你會想寫 recursive function 。

而在 Clojure 裡,想到 recursive function ,要想到兩種改善效能的方式:
(1) recur
(2) lazy-seq

其中,lazy-seq 的話,需要跟 cons 一起搭配使用,所以 (1) 的表示法相對有彈性。

Tuesday, August 15, 2023

malleable design

malleable design 又可以稱之為 evolvable design 。舉例子來說明的話:

  • Lisp Macro: 提供你一個 compiler plugin
  • Postgres UDF (user defined function) / Object system / foreign data interface
  • Protocol, atom semantic in Clojure

上述這些可以延展系統的接口設計,都可以讓後人巧妙地去增加原本的抽象層的廣度與深度。如果善用這些接口的話,日後使用系統的人,可以充分地享受 conceptual integrity 的好處,而不是得到一個層層疊床架屋才勉強構築出來的系統。

雖然我寫 Clojure 也好一陣子了,對於『延展 Clojure』的技巧,一直沒有充分地掌握。仔細想想的話,這個才是 Clojure 最 powerful 的技巧之一,真的要講的話,像大名鼎鼎的 Reagent 就是用這種技巧做出的 library 。使用 reagent 的感覺非常奇妙:一方面,你知道你在寫的東西,它的底層是 React 。另一方面,你又可以使用 Clojure 的高階 semantic ,直接用 @ operator 來取得 reagent 內部的資料。 

Thursday, June 29, 2023

background job queue library of Clojure

先談簡易的結論:
1. 想搭配 Postgres 使用 => 用 Proletarian
2. 想搭配 Redis 使用 => 用 Jesque 
3. 都不滿意的話,就再去 Java 的 ecosystem 找,總會找到勉強可以接受的 library 來用。


其實這個問題也滿多人討論的:
比方說,這一篇:有人在 ClojureVerse 詢問,在 Clojure 世界裡,相當於 Ruby 的 sidekiq 是什麼?


< 技術決策的理論 >

如果是我來思考這個議題的話,兩個重點:

1. Deployment 是否省事?
2. Library 提供給我的 interface 是否好用?直覺?不會太多儀式性的 code 要寫

DevOps 要省事的話,常常會不想用 Redis 。可以參考這一篇


由於使用 Redis 做為 durable storage 的 library 疑似發展比較久、也比較成熟,有可能反而是 Redis 組別的 Library interface 比較好用。類似的觀點來自這邊


< 技術決策的實務 >

Proletarian 與 Jesque 我都用過,library 給的感覺嘛,好像也沒有差很多。大概還是優先選 Proletarian ,畢竟我本人很怕 Deploy 那一段的 complexity。