Thursday, June 30, 2022

tmux, Clojure REPL, direnv, alias

開發 Clojure/ClojureScript 的程式時,我的習慣是會用 tmux 來開啟一個在背景的 repl 來做 development 

我常用的一些 shell alias 長成這樣子:

alias bbrepl="tmux new-session -d -s bbrepl 'bb nrepl-server 1667'"

alias drepl="tmux new-session -d -s drepl 'clj -M:dev:cider-clj'"

alias dfull="tmux new-session -d -s dfull 'clj -M:dev:shadow-cider-clj'"

alias dshadow="tmux new-session -d -s dshadow 'clj -M:shadow-cljs watch main'"


這個方式用了很久都沒有什麼問題,直到有一天,我發覺我需要所謂的 local repl 也就是說,我啟動的這個 repl ,它指定的 deps.edn 的 alias 是可以根據 project directory 來變動的。


本來,我設法用了 direnv ,很快就做到了,「可以讓 environment variable 隨著切換進入資料夾而掛載」。然而,其實我需要的是「可以讓 alias 隨著切換進入資料夾而掛載」。


最後我的作法是這樣子:
(1) 在專案的資料夾下,準備一個 .alias 的資料夾,裡頭放一些我要拿來當指令的 shell script ,比方說 lrepl

(2) 在專案的資料夾下,準備一個 .envrc 檔,裡頭寫了 
PATH_add .alias

Monday, May 2, 2022

在 mac 電腦安裝/使用 pyenv 

安裝 pyenv

step 1: 利用 homebrew 來安裝

brew update
brew install pyenv

step 2: 設置 zshell

echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zshrc
echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zshrc
echo 'eval "$(pyenv init --path)"' >> ~/.zshrc

pyenv 的基本操作指令

(1) pyenv versions
顯示現在 local 的電腦有哪些 python 版本可以使用
(2) pyenv install --list
顯示 pyenv 提供哪些版本的 python 可供安裝
(3) pyenv install 3.7.0 
安裝某特定 python 版本
(4) pyenv global 3.70 
將系統預設的 python 設定成 3.7.0
(5) pyenv local 3.7.0 
將當前 session 的 python 設定成 3.7.0

參考資料

Wednesday, April 20, 2022

variants

我之前去研究過 monad ,得到的心得是:「這個東西對我沒有什麼啟發性,因為我在 Clojure 裡,用 vector 或是用 map 就可以夾帶一些資訊了。」

然而,前兩天看了一個很久以前的 talk ,它在介紹 variants 這個概念。看完之後,我有了全新的觀點:不是這些 monad, variants 東西對我沒有用,是早就一直在使用了,或是被 library/DSL 半強迫地要我用了…。

https://www.youtube.com/watch?v=ZQkIWWTygio

這邊先做超快速地介紹:

(1) 什麼是 variants ?

[:tag value]

(2) 什麼是 recursive variants?

[:app [:op [:val 15]

               [:val 16]]]

基本上,hiccup 就算是一種 recursive variants。

(3) 誰用了 variants ?

Erlang 使用了 result variant 

Hiccup 也可以視為是使用 variant 

re-frame 也用了

還有很多時候,是該用,而沒有去用,所以就寫出爛 code 了 

Monday, April 11, 2022

Clojure macro 的妙用

何時該使用 Clojure macro 最適當的時機,一直是一個我覺得費解的問題。對我來講,macro 是一種 compiler plugin ,只有在 programming language 這個抽象層明顯不足的時候,才會用到。

終於,我找到了一個我覺得算是頗為合理的應用。在 antizer 這個 library 的 macro 應用,我覺得極為合理。

它應用的邏輯是這樣子:
要在 ClojureScript 使用 react ,比較好用的方式,是要使用 reagent component ,而不是直接使用 react component 。換言之,要先寫一點程式碼,先包一層。然而, Ant react library 裡卻有著大量的 react component 。所以要寫大量重複的程式碼。應用 macro 的話,就可以讓這些大量的程式碼只要寫一遍即可。



Thursday, February 3, 2022

利用 Datomic query 來取得 schema 的資訊

使用 datomic 時,有一些 query 算是輔助用的,但是非常方便,比方說,像是用來取得 datomic db schema 的 queries

實務上在開發的時候,常常會有 schema 的文件一時找不到,或是,剛好不確定,現在正在查詢的資料庫的 schema 長成什麼樣子。這一類的 query 就非常地有用。

;; find the idents of all schema elements in the system
(sort (d/q '[:find [?ident ...] :where [_ :db/ident ?ident]] db))

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