Saturday, January 29, 2022

我的 Clojure 學習之路

我在想,如果分享一下,我學習 Clojure 的數個階段,說不定其它想要學 Clojure 的人也會覺得參考我的學習之路有點用處?

我大概把自己的程度,分成下列的四個階段。

  1. Before knowing Clojure
  2. Clojure Basic 
  3. Tried a lot of libraries  
  4. Selected Libraries 

階段 1:  < Before knowing Clojure >

在我使用 Clojure 之前,我就已經使用過許多不同的程式語言:C/C++, PHP, Shell Script, Java, JavaScript, Python, Golang。此外,函數式程式設計大概也有一定的基礎。依賴注入的問題,也算是有常在思考了。只是,我從來沒有想到過,寫程式時產生的 90% 的困惑,都可以在一個語言裡直接找到解答。

階段 2: < 學習 Clojure 的 Basic >

這個階段,主要是做了四件事:

  1. 寫了不少的 4clojure 的練習題
  2. 看了 Clojure for Brave and True
  3. 搞定了 Editor integration 
  4. 開發了一個 Clojure web application ,但是,前端還是用 javascript 。

階段 3: < Tried a lot of libraries >

在這個階段,我為了想要更精進,積極地使用各種 libraries 。其實我曾經看過 Rich Hickey 談過一個概念,「想要成為高手,應該要選擇一個 domain problem ,深入研究,然後,透過了解問題、解決問題、透徹地思考,從而成為真正的高手。」不過,Rich Hickey 的教誨,我把它先放一邊,我認為,那個建議是給『接近他那個水準的人使用的』,我的話,先追求廣度就好。

於是,在這個階段,我嘗試了:

  1. Datomic
  2. ClojureScript
  3. Tachyons CSS (這個不是 Clojure ,但是,是 Clojure 社群的人推荐我學的。)

這個時期,我開發 web application ,依然主要使用 Luminus 。

階段 4: < Selected Libraries >

因為在 Gaiwan 工作的關系,我使用的 libraries 會有老闆的建議。( 還是說,是規定?) 總之,這個時期我相當幸運,在 Gaiwan 工作,雖然說,工作本身並不簡單:開發工作總是有時程的壓力,而且常常要學新的 libraries 。但是,有任何疑問,隨時可以問 Arne Brasseur 。

總之,在這個階段訓練了一陣子之後,我學到了幾件重要的事:

1. 開發軟體,要先搞定 walking skeleton 

2. 有一些太複雜的 domain problem ,如果覺得開發複雜的 API library ,自己日後還是會用得很痛苦的話,可能要考慮,把 API library 做成 hiccup DSL ,然後使用 tree walk interpreter 

3. 有一些 java interoperation 的問題,如果程式很難寫,可能要考慮「剝皮接枝法

經過了這個階段的訓練,我發現自己開發 web application ,開始不想依賴 Luminus 了。依賴 Luminus 真的很省事,很多困難的 technical decision 都讓 Luminus 的作者幫我決定就好。但是,我已經學過夠多的東西了,這些決定我可以自己做了。 


如果剛好是程度處於我上文階段 2 或是 3 的朋友,這邊有一些我整理出來的連結。


如果已經想要走入階段 4 的朋友,這邊有 LambdaIsland recommended Clojure libraries
- Dependency management: Clojure CLI (avoid lein or boot)
- System configuration: Aero
- System state management: Integrant (avoid component)
- Routing: Reitit
- HTTP Server: conservative choice is Jetty. Strongest contenders are
Immutant-web and Aleph. We're keeping a close eye on Pohjavirta (avoid
http-kit)
- Relational Database Access: next-jdbc + honeysql
- Testing: clojure.test + Kaocha
- Editor: Corgi (formerly lesser-evil) or VS Code + Calva
- Datalog databases: conservative choice is still Datomic, keeping a close eye
on Crux, Asami, Datahike, Datalevin
- Schema validation: Malli (alternatively stick to Spec 1, but with spec being
eternally in alpha it seems wise to hold off)
- ClojureScript tooling: figwheel-main or shadow-cljs
- URI: lambdaisland/uri
- Http Request: hato at the backend, and lambdaisland/fetch at the frontend.
- Time: java.time at the backend, and lambdaisland/deja-fu at the frontend
- Log: SLF4J + Logback + pedestal.log at the backend, and lambdisland/glogi at the frontend.

另一個很值得參考的 libraries list ,則是 JUXT 的 Clojure Radar 。 Radar 列了很多的項目。
比方說:
- ClojureScript interactive tool: Devcards
- Database migration Tooling:  Ragtime

剝皮接枝法

問題的 Context: 我用 Clojure 去呼叫 Slack java API ,在 client side 取得 Slack 的 message event。Slack java API 傳回的 message 都是使用 java object 的形式來傳回。

Clojure 有兩組 API 可以處理這種問題。如果只需要取得第一層的 property ,就用 core library 的 bean 即可。

https://clojuredocs.org/clojure.core/bean

但是,如果要把重重嵌套的物件,遞迴地轉成重重嵌套的 Clojure hash map ,就得使用耗時耗記憶體的 from-java

https://github.com/clojure/java.data

由於考慮到 execution efficiency ,所以我就自己手刻轉換的函數來轉換。(為了效能,必要的開發??) 然而, Arne Brasseur 則提出了一個不同的解決方案,我稱之為「剝皮接枝法

- 由於 Slack java API 它其實是把本來是 json 形式的 data ,用 java class 包裝起來,讓 java 程式容易(?)調用而設計的。

- 好的 java program 通常會有一些 factory 的設計,Slack 的 library 也是如此,這些都是可以置換 implementation 的 software seam

- 基於上述兩項事實,我們可以放棄從既有的 Slack java API 來做 integration 。剝掉一層 java API 的 skin ,往下挖一層,直接挖到 Slack java API 的下層,透過 web socket 傳輸 json 資料的程式碼。然後,透過 Clojure 的 web socket library 去串接網路與 json 資料。取得 json 之後,直接轉成 Clojure hash map 。

用這個方式的話,可以同時滿足機器效能與長期開發效率的要求。

;;;;;;;;;;;;;;;;;;;;; 結論 ;;;;;;;;;;;;;;;;;

上頭一大串是平庸的 software developer 我,對這一則簡短的 twitter 的解釋…

Much of Clojure development is peeling back layers and layers of Java libraries to get to the bit that actually matters, then wrapping that in a tiny API layer over plain data. 

Thursday, January 6, 2022

如何讓 html5 video tag 可以 responsive ,即隨頁面大小產生變化

寫法:
at html
<video class="video-js"> ... </video>

at css
.video-js {
width: 100%;
height: auto;
}

解釋
To make the container responsive, the trick is to explain to the browser how both axis should behave according to their environment (the container). Here I have chosen the horizontal axis to respond to the container width and the vertical axis to automatically resize according to that and the aspect ratio (which is what we normally want). Setting both to fixed (hard-coded width and height) or fluid values (auto) would not work: the first would respect your settings at all costs and the latter the video size or a default browser value, but both would ignore the context where the element is placed.

寫 cronjob 要注意的事

1. cronjob 通常是以不同的 shell 來運行,所以會有不同的 path 變數、環境變數、與起始的 working directory 。
2. 如果要得知上述的資訊,可以寫一個簡易的 script ,裡頭用 set 指令來取得所有的環境變數。
3. 可以透過修改 /etc/crontab 這個檔案,來修改預設 cronjob 的 shell 與 path 變數
4. 應該要先刻意把 cronjob 設定成每分鐘執行一次,來做測試與除錯
   * * * * *  script_name.sh > /tmp/cron_debug.log 2>&1