Tuesday, February 21, 2017

clojure vim 開發環境設置 --- rainbow-parentheses, cljfmt

clojure 搭配 vim 的開發環境設置中,有一項特別的 syntax highlight  ,算是其它語言比較不需要,但是 lisp 家族的語言特別需要的特色:彩虹括號 rainbow parentheses



安裝了這個 vim 的 plugin 之後,寫出的 clojure 的程式碼,插號就會由內而外地照彩虹來變色。而且連中括號的顏色也可以一起變化。




安裝 vim 的 rainbow parentheses
cd ~/.vim/bundle && git clone https://github.com/kien/rainbow_parentheses.vim.git

然後,在 ~/.vimrc 檔內,加入所需要的設置:
let g:rbpt_colorpairs = [
    \ ['brown',       'RoyalBlue3'],
    \ ['Darkblue',    'SeaGreen3'],
    \ ['darkgray',    'DarkOrchid3'],
    \ ['darkgreen',   'firebrick3'],
    \ ['darkcyan',    'RoyalBlue3'],
    \ ['darkred',     'SeaGreen3'],
    \ ['darkmagenta', 'DarkOrchid3'],
    \ ['brown',       'firebrick3'],
    \ ['gray',        'RoyalBlue3'],
    \ ['black',       'SeaGreen3'],
    \ ['darkmagenta', 'DarkOrchid3'],
    \ ['Darkblue',    'firebrick3'],
    \ ['darkgreen',   'RoyalBlue3'],
    \ ['darkcyan',    'SeaGreen3'],
    \ ['darkred',     'DarkOrchid3'],
    \ ['red',         'firebrick3'],
    \ ]

let g:rbpt_max = 16
let g:rbpt_loadcmd_toggle = 0
au VimEnter * RainbowParenthesesToggle
au Syntax * RainbowParenthesesLoadRound
au Syntax * RainbowParenthesesLoadSquare
au Syntax * RainbowParenthesesLoadBraces

另一方面,要人工處理程式碼的對齊,其實也是很麻煩的事。寫過 golang 用過 gofmt 之後,寫新的語言也會不自覺開始找對應的東西。所幸, clojure 也有 cljfmt 。  

安裝方式:
  1. 安裝 vim-fireplace
  2. 安裝 cljfmt 
  3. 安裝 vim-cljfmt 
想要在 vim 內打 :Cljfmt 就可以自動排版、關閉檔案存檔時也可以自動排版的設置,必須在  ~/.lein/profiles.clj  寫入
:dependencies [[cljfmt "0.5.6"]]
此外,必須是在 leiningen  「使用者域」的設置檔已經寫入之後,才啟動 leiningen repl 。因為不同於 gofmt 是獨立的程式,cljfmt 是需要透過 clojure 的 REPL 來啟動的。

Monday, February 20, 2017

clojure vim 開發環境設置 --- 整合 REPL

開發 clojure ,不免要做的事情自然是不停地調用 REPL 來測試自己寫的函數是否如預期般地執行,而 vim 可以安裝 plugin ,讓這個調用 REPL 的步驟完全留在編輯器內執行。這個開發方式又稱為 live development 。

要設置好這個開發環境,首先要準備幾樣東西:
  1. clojure 的 nREPL
  2. vim 8
在 vim 裡安裝合適的 plugin :
cd ~/.vim/bundle
git clone git://github.com/tpope/vim-fireplace.git
啟用方式:
  1. 在 clojure 的專案資料夾,開啟一個 shell ,啟動 nREPL。指令是 lein repl
  2. 在同一個資料夾,開啟另一個 shell ,啟動 vim 。如此一來, vim 的 fireplace plugin 就會自動與 nREPL 連接了。
文字檔不在 clojure 專案資料夾內時,fireplace 的啟用方式:
  1. 還是一樣啟動 nREPL。指令是 lein repl
  2. 如果文字檔並不是 clj 結尾。需要先讓 vim 知道它是 clojure 的 source code ,下指令
     :set filetype=clojure
  3. 連線到現在已經啟動的 nREP  ,例如下指令
     :Connect nrepl://127.0.0.1:41767
fireplace 的常用指令如下:
求值
  1. 隨便選一個 expression 做 repl     移動游標到程式碼上,然後  :Eval 。快速鍵是 cpp 
  2. 指定一段區間的程式碼做 repl      :1,3Eval
  3. 啟動 Clojure Quasi-REPL        cqq
  4. 只打開 Clojure 的 Quasi-REPL   cqc 
在 quickfix 視窗顯示 docstring 或是 source code 
  1. 快捷鍵  K   。        對應 repl 的 (doc ) ,   也可以用 :Doc
  2. 快捷鍵  [d  。        對應 repl 的 (source ), 也可以用 :Source 
在文件裡穿梭 
    1. 快捷鍵  [ ctrl + d  。 跳躍到符號定義的那一行,即使是定義在 jar 檔中。
    2. 快捷鍵  gf  。         語義是 go to file 。 對於 namespace 會產生作用。
    重新載入程式碼
    1. 讓 REPL 重新載入現在的 namespace                                         :Require
    2. 讓 REPL 重新載入現在的 namespace 及其使用的 namespace        :Require!
    程式碼提示 (Omnicomplete)      <C-x><C-o>

    啟動 ClojureScript    :Piggieback [build-id]   // 此處的 build-id 是 keyword 形式
    啟動 ClojureScript 的 REPL 之後,要先
    1.  npx shadow-cljs watch app
    2. 開啟 browser

    Wednesday, February 15, 2017

    推荐 clojure 語言 --- simple, concise, consistent

    最初是看了 Paul Graham 推荐 Lisp ,然後工作上因為 Riemann monitoring tool 是用 clojure (Lisp 的一種 dialect) 寫的,一時興起就決定來研究這個傳說中「駭客的秘密武器」的語言家族。學習的過程就是重複、交替做三件事:
    1. 讀 clojure 的線上書籍、文件,寫語言的特性、語言的觀念 ---重點關注在 clojure 的文章。
    2. 寫一些不痛不癢的小習題。 4clojure.com 有 150 多題的練習題。
    3. 讀一些和 clojure 相關、又不直接談 clojure 的文章。比方說「教 Lisp 的文章」「Lisp 的歷史」「Rich Hickey 的一些 slide 」
    後來研究了所謂的 language oriented programming, bottom-up approach 等等應用 macro 來做 meta-programming 的高級應用技巧。 當我和朋友聊這個的時候,朋友給了一個回應:「嗯,也許 Lisp 語言真的是最適合做 meta-programming 的語言,但是你上一回寫 meta-programming 是什麼時候?」

    於是,我心裡的大哉問就變成了:「對於自身不太使用 meta-programming 技巧的程式設計師,clojure 語言是否依然可以帶來生產力的提昇?」會問這個問題是因為之前有看過一篇文章講為什麼「函數式編程」可以提高程式設計師的生產力,文章中所論述最重要的理由,因為「高階函數」與「惰性求值」這兩項特性可以提高程式碼模塊化的程度。函數式編程的特性,現在許多的語言都加入了,那 clojure 如果不討論 meta-programming 的話,它的表達能力 (expressiveness) 還會比一些更主流的語言 python, Ruby 等也有函數編程特性的語言來得更強嗎?

    那我們來看一段程式碼吧: flatten 函數,可以用來「壓平」樹狀資料結構。
    例如:
    > (flatten [1 2 [3 [4 5] 6]])
    (1 2 3 4 5 6)
    接下來,有趣的事就是,如果我們去看 flatten 的 source code 。

    啥! flatten 居然是用 tree-seq 來定義的!為了這件事,我還多看了一下,clojure 哪些內建的函數也是用 tree-seq 來定義? 結果 file-seq 和 xml-seq 也都是用 tree-seq 來定義。而 tree-seq 是一個高階函數,接受三個參數:一個是用來接收「定義分支的函數」、一個是用來接收「展開分支子樹的函數」,一個是用來接收「樹資料結構」。

    「樹」這樣子的資料結構、對應的「走訪方式」,例如: tree-seq。在其它的語言,往往沒有「極簡」的實現。像 python 的話,自然也會有「樹」相關的走訪函式庫、「樹」的資料結構實現。卻比較多是物件導向式的、或是每一家發展不同風格的作法。這一類的問題:標準函式庫較為一致、外部函式庫較不一致的問題,我認為是語言表達能力受限制的主因之一。

    要深入討論語言表現能力的話,我認為這關系到程式語言設計的一致性 (consistency)。以 python 為例, python 在函數式編程領域,後期發展的語法傾向是用 list comprehension 來取代 filter, map, reduce 之類的函數。這個很自然,因為用 list comprehension 的表現能力更強,寫 python 的使用者可以在記憶最少的語法的前提之下,得到最大的表達能力。

    於是這就回到了一個很有趣的議題,程式語言的設計,其實必須考慮人類認知能力的限制。以 C++ 語言為例,C 的語法、語件導向的語法、template 的語法,三種語法都大不相同。tempalte 尤其複雜。複雜的語法導致了使用者學習時難學、使用者上手之後也難用,因為語法冗長。而 Lisp 的括號語法,最初來自於偷懶,因為 Lisp 的作者還沒有為它設計合適的語法,所以就先用語法樹 (syntax tree) 來湊合著用,卻因此讓 Lisp 語言成為了有最簡單 (simple) 語法的語言,同時也是語法最一致 (consistent)的語言,進而可以讓使用者寫出簡潔 (concise) 的程式。

    Tuesday, January 24, 2017

    從 open-falcon 的 transfer 模組來理解 RabbitMQ

    我初次看 RabbitMQ 的文件時,覺得這個東西似乎滿利害的。可是,心裡卻有很大的困擾,到底要怎麼用啊? 何時會需要呢?被這樣子的問題困擾了很久。直到最近重新去想 open-falcon transfer 的 source code 之後,才覺得心裡的迷團解開了~。

    open-falcon 的 Transfer 的架構圖,大概是類似左圖。Agent 將 metric 送進 Transfer 之後,會將 metric 分別放到兩個 queue ,一個是準備之後要送到 Judge 的資料。另一個是準備之後要送到 Graph 的資料。 當 Transfer 要跟外部的模組連線時,它用了 connection pools 的設計,跟 Judge 還有 Graph 都建立了多條的連線。因為當 Agent 多的時候, Agent 上報的 metric 數量會非常多。要讓 queue 可以不會被塞爆,其中一種方式就是透過 connections pool 建立多條連線來加快 queue 清空的速度。


    上述的 Transfer 從某些角度來說,可以說是應用了兩種 RabbitMQ 的使用情境:

    (1) connections pool 可以視為是 Work queues 的應用情境。因為每一條 connection 其實也可以想象成是獨立的 consumer ,或是稱之為 worker 。

    (2) Transfer 內部針對要送到不同的外部模組而分別建立兩個獨立的 queues 的作法,可以視為是 Publish/Subscribe 的應用情境。在這個情境裡, 負責連線到 Judge 和 Graph 的程式碼就可以視為是獨立的兩個 consumer 。

    Sunday, January 15, 2017

    choose the programming language

    記憶中,大學的時候,趨勢科技舉辦的百萬程式大賽。系上有兩隊去參加。其中一隊是號稱為 F4 的隊伍,成員都是系上公認實作能力一等一的學長。另一隊則是比較像是雜牌軍。然而,參賽結果卻相當令人意外,F4 第一關就中箭落馬,雜牌軍則是過關斬將到最後一關,而且有得到名次。後來,聽雜牌軍隊講他們成功的要件: 用 C# 而不是 C++ 來實作。這樣子的作法會有下列的好處: (1) 實作起來比較快。 (2) 當遇到沒有寫好的部分,只是跳出 exception ,而不是程式直接 crash 。

    上述的故事,在我日後的職場生涯偶爾想起。從這個故事中得到的啟發是:
    「選擇正確的 technology stack 有時候對於軟體公司的影響,不輸給選擇好的員工。」

    選擇 technology stack ,這個題目還是有點困難,先從「選擇 programming language 」這個題目開始吧。如果是小公司、小團隊、該如何選擇合適的 programming language 呢?從我的觀點的話,我會認為,應該優先考慮四件事:
    1. productivity
    2. learning curve
    3. core library & third party library
    4. performance
    第一件事是「程式語言的生產力」,這件事很多人都已經知道了。然而,有寫過的人總是有基於自身經驗的認知,沒有寫過數種語言的人則主要依賴書本上的數據。也因此,這邊我用 lines of code 來比較,因為我傾向相信某個論文提出的觀點,「相同的時間內,程式設計師可以寫的總行數是類似的。」也因此程式語言所決定的生產力與它的 lines ratio 極為相關。下圖取自 code complete 一書,圖中的 lines ratio 是指實現同樣的功能為前提之下, lines ratio =  C語言需要行數除某一程式語言需要的行數。而 Paul Graham 在他的推荐 Lisp 文章 (Revenge of the Nerds) 中,表示 Lisp 的 lines ratio 一般而言是 7~ 10 ,如果面對複雜的問題、且使用進階的技巧,甚至可以到達 20 。
    LanguageStatements ratio[35]Lines ratio[36]
    C11
    C++2.51
    Fortran20.8
    Java2.51.5
    Perl66
    Smalltalk66.25
    Python66.5

    第二件事是「學習曲線」。學習曲線陡峭的程式語言,也就意味著該語言的使用者會變少。對公司的立場來說,也就意謂著不容易找合適的工程師。舉個例子,就我自己學習 clojure 語言 (Lisp 的方言) 的經驗來說,確實不那麼容易學,因為很多觀念都不存在於過去的經驗。從 TIOBE 來看的話, Lisp 語言曾經一度是排行前 20 的程式語言,應該是因為學習曲線等因素,始終是小眾的語言。

    第三件事是「函式庫支援」。這個要素其實跟「生產力」也類似。畢竟,如果主要選用的程式語言,沒有合適的函式庫支援時,往往軟體就是要透過使用多種程式語言來完成,又增加了複雜度和維護成本。

    最後一件事才是「效能」。對於效能一事,我本來一直有一種誤解,覺得「程式語言之間」會有效能的差異。然而,一回我查了網路的文章,才發覺這個來自經驗的想法不太正確。程式語言和它的效能是脫勾的。效能是跟語言的實作 (implementation) 才有關系。所以,要探討 Java 語言的執行效能,重點要去探討 JVM 的實作。而一些流行的 scripting language 為何要特別設計出可以編譯到 JVM 的版本?重點是在於,做出可以在 JVM 執行的版本,比起直接將該語言做成 native machine code 省事太多,同時,JVM 又已經「夠快」了,有著幾乎不輸給 native machine code 的效能。這邊特別提一下,很多人也跟我一樣,對 JVM 有著誤解,以為 JVM 很慢,跟一般的 scripting language 所用的 engine 或是 VM 差不多的效能。這個也是來自經驗的誤解。 JVM 只是啟動比較慢,而這一點是有技巧可以克服的。

    Sunday, January 8, 2017

    [open-falcon] proc.num/name=crond

    又被運維人員問了,我答不出來的問題:「為什麼明明用 ps 指令去查,只有一隻 crond 的 process ,但是看監控項對時間畫出的圖形,卻明顯的是 2、3~10 不等的數字。

    運維人員把他下的指令給我看:
    ps -ef | grep crond
    我也看不出任何問題。


    沒有辦法,只好去追 open-falcon 的源碼。在源碼中: agent 計算名稱為 $name 的進程個數的方式,是到 /proc/%d/status 找, status 檔裡頭 name 為 $name 的檔案個數。

    於是我下了一個指令:
    [root@foo ~]# find /proc -name status -exec grep -l crond {} \;
    /proc/7811/task/7811/status
    /proc/7811/status
    /proc/8853/task/8853/status
    /proc/8853/status

    再下一個指令來看好了
    [root@foo ~]# pstree | grep crond
         |-crond-+-crond---bash---sleep
         |       `-crond---sh---fwrsync-+-fwrsync---sleep

    Saturday, January 7, 2017

    函數式編程 (functional programming) -- 語言的雜交

    不知道大家有沒有跟我一樣類似的經驗?想要了解 functional programming 的時候,會看到一些句子:「Lisp 語言受到 lambda calculus 的影響」「lambda calculus 是 Turing complete 。」「 Closures are poor man's objects and vice versa. 」然後,心裡冒出了許多困惑。

    比方說:
    (*) 那 lambda calculus 跟 functional programming 有什麼關系?為什麼需要去討論 turing complete ?
    (*) 那既然 functional programming 也可以產生跟 object oriented programming 一樣的 object ,那為什麼要取名為 closures。

    關於這一類的疑惑,在我學習 Lisp 語言的時候,終於漸漸地一一澄清了。而這一類困惑的源頭,我認為,來自於「語言的雜交」。近來的語言發展趨勢,許多主流語言都轉向了加入函數式編程的特色。然而,由於考慮向後相容性,加入函數式編程的特色的同時,並沒有移去本來屬於 OOP 的特色,於是就導致了用既有編程思想去理解函數式編程的困惑。

    首先,C++ 語言、 Java 語言,這一類語言,本來是基於 Von Neumann 架構,由組合語言加以抽象化,而發展出來的語言設計,骨子裡,它的運算就是 state transfer 。如果把一些高級的特性一一拿去,讓它們退化,退化的過程也許是 Java 先退化成 C++ ,最後退化成 brainfuck 。這時候,我們來仔細檢驗一下 brainfuck ,真的就只能做 state transfer ,已經沒有任何函數呼叫的語法,而它依然是一種 Turing complete 的程式語言,換言之, Java 做得到的事,它也辦得到。

    然而,如果某一種語言,比方說 Java 8 已經同時擁有完整的函數式語言的特性,它其實可以有另一種退化的方向, Java 先一一移除物件導向的特性,最後連 loop, goto, pointer 等特性也一一移除。最後也可以變成某種程式語言,只有 lambda calculus 的語言特性,於是,依照教科書上說的,它依然是一種 Turing complete 的程式語言。但是,沒有 goto, 沒有 loop ,這樣子的語言要怎麼做出計算呢?當函數式編程語言用極簡的形式來呈現時,我們更容易看出來,它計算的本質不是 state transfer ,而是 stateless transformations of immutable things, where things can be both data and functions, or even partially applied functions.

    在理解了 lambda calculus 和 Turing machine 的計算本質性的差異之後,才能理解,同樣是函數,在 Von Neumann programming paradigm 之下的函數,它主要是用來做為程式碼模組化的功能,減少行數。真正的計算,發生在變數之間的狀態轉換。而在 functional programming paradigm 之下的函數,它必須是「無狀態的變換」 (stateless transformation) ,它本身就是真正的計算發生之所在。

    更進一步,既然在 functional programming paradigm 之下,計算應該要用「無狀態的變換」來達成。程式語言自然要設計數種語法,其可以用來生成「無狀態的變換」。比方說: closure等語法適合生成容易客製化的 first order functions ,這些 first order function 又可以做為「變換」來使用 。既然要做「無狀態的變換」,程式語言自然要設計,可以達成高效能又可以無狀態的資料結構,例如: immutable data structure 。