Friday, April 30, 2021

pagination 與 lazy sequence

在開發與 Slack API 整合的模組時,因為部分的 Slack API 會傳回超大量的資料,所以有設計分頁 (pagination) ,而因為需要取得所有的資料,所以設計「走訪所有分頁」的設計,用 loop/recur 來實作。

很巧的是,我後來看到類似的函數,我的老闆 Arne Brasseur 早就寫過了,但是,他是用 lazy-seq 來做。我比較我與他的實作之後,第一個念頭是去想:「是不是很多我本來用 loop/recur 來解的問題,其實也可以用 lazy-seq 來解?」

仔細思考之後,我覺得這個又回到了軟體開發的本質:『軟體開發的本質是對真實的世界建模 (modeling)』

pagination 與 lazy sequence 本來就是一體兩面的東西,都是在建模一個概念:「資料的總量很大,使用者可以先取第一段、視需要再取第二段、再取第三段…,你不需要一口氣就取回所有的資料。」從這個角度去思考之後,可以想到要使用 lazy-seq 去解 pagination 的題目也不奇怪了。

Wednesday, April 28, 2021

Clojure configuration library - aero

Clojure 的 aero library 有兩個我覺得超欣賞的功能:

  • 隱藏 secrets 的 best practice 
  • 可以自行延申 areo reader ,設計自己需要的 configuration semantic

其中,隱藏 secrets 的這個 idea ,算是挑戰了 12 factor app 裡的,「要把 secrets 放在 environment variables 」的 best practices 。我質疑 12 factor app 的這一項作法很久了,但是卻遲遲沒有找到好的替代方案,終於找到 areo library 了!

Sunday, April 18, 2021

Clojure 與 java beans

在 Java 的世界裡,要封裝資訊,常常不是使用 java.util.HashMap 或是 java.util.Arraylist ,而是使用 java bean 或是 POJO 。總之,如果遇到 Clojure 與 Java 要 interoperate 的時候,這些都是麻煩。

Clojure 的標準 library 已經提供了一個 bean 函數,可以轉換 Java bean 變成 Clojure 的 Map collection 。而,如果遇到多層的巢狀的 Java bean 時,就得考慮使用 java.data 這個 library 。裡頭有 from-java-deep 這個函數可以深層轉換。

Thursday, April 8, 2021

從 backend 傳送「初始化資料」到 frontend

開發 fullstack application 時,從 backend 要送資料到 frontend ,這是很常見的事,而這件事有三種不同的作法:

  1. API
  2. HTML 頁面初始化
  3. 編譯期初始化


通常就是讓 backend 提供 API ,然後 frontend 去送出 http request 來取得資料。然而,有一種情況:「 frontend App 要初始化的時候,就需要從 backend 取得幾個變數來做初始化,由於只有不到十個變數、資料量不大、資料也不會變化,似乎為此特別做一個 API 也太麻煩了。」

於是,對於上述的問題就可以考慮直接透過 html document 來夾帶資料的作法:

a. 當 backend 在生成 page content 時,在 hiccup format 的 html 裡加入下方這一行。注意:MIME type 要設定為 application/json,如此一來,瀏覽器就不會去執行它。

[:script#data-tag {:type "application/json"}
   (json/write-str {:key-a data-a :key-b data-b})]

b. 在 frontend 的 ClojureScript 裡,可以透過如下的程式碼來取得資料:

(defn init-config []
   (let [data (->> (js/document.getElementById "data-tag")
                        (.-innerHTML)
                        (.parse js/JSON))]
     (js->clj data :keywordize-keys true)))


還有一種情況是:「如果需要初始化的資料,根本一開始就只是放在一個靜態的檔案裡」這種情況,就可以考慮最後一種作法,讓 ClojureScript 利用 macro 在編繹期去讀取檔案。 

Tuesday, April 6, 2021

Clojure combinator

偶然看到一個 Eric Normand 的課程,它把 Clojure 的一些 high order functions 集中成一套課程,叫做 Clojure combinator 。哪些是 Clojure combinator 呢?有八個函數:

  • identity
  • constantly
  • complement
  • partial
  • fnil
  • comp
  • juxt
  • apply

妥善地運用這八個函數,可以讓語意更清晰。個人覺得 constantly 與 juxt 算是相對難學。
 
 constantly 是有一回,我要使用 alter-var-root 的時候,才發覺它的妙用。
 juxt 則是在三種情況下特別好用:
  1.  搭配使用 sort-by, group-by
  2.  修改既有的 hash map、生成新的 hash map --- 參考 lambdaisland 的 idiom 
  3.  取代 (comp vals select-keys) 
如果把上述的八個函數做個簡單的分類的話,我會分成四類:
1. 修改函數 (input arg 是函數, output 是 input arg 修改後得到的新函數) - fnil, complement, partial 
2. 組合函數 (input arg 含有超過一個函數, output 是將 input arg 重組而成的新函數) - comp, juxt 
3. 純生成函數 (input arg 可以不是函數,但 output 是一個新函數) - constantly
4. 非生成函數 (output 是某個結果,不是函數) - apply, identity 

在做了上述的這個分類之後,我有一個小小的新發現:其實組合函數還有一些不在這八個 combinator 裡的,分別是 some-fn 與 every-pred