;; 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})))))
程式人蔘
github static page 版的 blog: https://humorless.github.io/
Wednesday, November 22, 2023
Clojure 取代 if 的技巧 (3) --- Atom validator
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。