Saturday, December 30, 2017

microservices prerequisites

這兩天看了一篇文章, Enough with the microservices ,文章的大意是講,作者反對成長中的公司,盲目地將公司的軟體架構改成「微服務」,因為帶來的損害大過優點。

文章的最後,介紹了 Martin Fowler 的「微服務的先決條件 (microservices prerequisites) 」。即,要把公司的軟體架構改成微服務,要先做哪些事? 並且提到,對於許多公司,光是完成前兩項,大概就可以解決 90% 以上的問題,並不需要真的做到五項都完成,完整地把軟體架構改成微服務。

先決條件清單如下:

  1. 清理應用程序。確保應用程序具有良好的自動化測試套件,並使用了最新版本的軟件包、框架和編程語言。
  2. 重構應用程序,把它拆分成多個模塊,為模塊定義清晰的API。不要讓外部代碼直接觸及模塊內部,所有的交互都應該通過模塊提供的API來進行。
  3. 從應用程序中選擇一個模塊,並把它拆分成獨立的應用程序,部署在相同的主機上。你可以從中獲得一些好處,而不會帶來太多的運維麻煩。不過,你仍然需要解決這兩個應用之間的交互問題,雖然它們都部署在同一個主機上。不過你可以無視微服務架構裡固有的網絡分區問題和分佈式系統的可用性問題。
  4. 把獨立出來的模塊移動到不同的主機上。現在,你需要處理跨網絡交互問題,不過這樣可以讓這兩個系統之間的耦合降得更低。
  5. 如果有可能,可以重構數據存儲系統,讓另一個主機上的模塊負責自己的數據存儲。



Thursday, December 28, 2017

Clojure 函數與 Design Pattern

23 個 Design Pattern 一直是我覺得很不容易精通的東西。還好,  Peter Norvig 也提了他的看法,認為 Design Pattern 在 functional programming language 裡並不是很必要的東西,因為 FP 讓 function 成為 first class citizen ,會讓 Design Pattern 大幅簡化。

這邊還是記錄一下,在寫 Clojure 時,哪些 Design Pattern 會被 Clojure 的核心函數大幅簡化的。
1. Factory Pattern
    用 partial 即可
2. Visitor Pattern
 用 defmulti 即可
3. Observer Pattern
 用 add-watch 來寫的話,會簡單許多

Thursday, December 7, 2017

database and system design

最近要換工作了。換工作前,依然還沒有學完/學通公司的資深工程師 Mike 的 skills。只好先把他以前寫在 slack 的 comments 拷貝出來。

==================================================
1. 一張 table 的資料,一定只有一個順序存在「硬碟」中

2. Clustered Index,是一個用資料規則來決定資料在「硬碟中」的順序的方法

3. 當需要進行範圍查詢時,Clustered Index 可以利用到「硬碟」的循序存取效能

4. 不是有索引,資料庫的查詢最佳化就會使用,當資料量不多時,資料庫會直接使用 Full table scan

5. 就點查詢而言,WHERE 的條件在 Clustered Index 比 Secondary Index 還快,因為 Leaf Node 就是 Data Block

6. 資料庫效能是在「新增、修改、刪除」與「查詢」之間取捨 --- 註:前同事 Mike 對 relational database 的理解沒有錯。然而,在 Martin Kleppmann 的 Turning the database inside out with Apache Samza 這個 talk 裡,他探討了一種 system architecture,透過 event sourcing 與 CQRS ,可以達成不需要為了 Read 或是 Write 的效能而做出 trade-off。

7. Secondary Index Leaf 是 Clustered Index Key 是 MySql InnoDB 特有的實作方式

8. MySql 選擇了讓 Secondary Index 「新增、修改、刪除」容易,但查詢付出代價

9. MySql 是查詢最佳化很蠢的資料庫,意思要用很多索引,所以 InnoDB 實作 Secondary Index Leaf 到 Value of PK,在多個索引的情況下,PK 的順序不影響 2nd Index 的 Leaf Node 的值

10. 在那一篇引起戰文的為何用 MySql 取代 PostgreSql 的文章(UBer)中,關於索引的部份,他們在不斷更新資料的前提下,再加上有一堆索引,所以使用 MySql,更新的成本比 PostgreSql 低

11. 在 MVCC 的比較上,當 Transaction Log 與 Data Block 放同一個磁碟子系統時,
 假設 Read(50%) 與 Write(50%),PostgreSql 可能比 MySql 好太多,因為減少了大量的 Random Access

=============
資料庫先求正確,再求效能,不正確的資料會拖累效能,甚至無法 Tuning,因為隨著時間,相依的程式碼太多。

資料庫資料的修正與程式的 Patch 不是同樣成本的事,讓資料嚴謹至少佔了系統品質的 60%。程式出錯,還有資料庫把關。

資料庫嚴謹的代價是效能,但它是可調效的,資料出錯,通常是前端先發現,然後要一層層檢查是哪裡出錯,是查詢有錯、還是新增修改有錯、還是前端有錯。甚至可能只有正式機才會有資料範例,此時就可能要在正式機上除錯。

==============
資料庫有兩本書就用了
The Art of SQL
Database: Principles, Programming, and Performance

Tuesday, November 28, 2017

SQL insert after join table

最近寫 SQL 的時候,遇到了有趣的「寫入」問題。問題如下:

有三張 table
table host 有 id, hostname
table grp   有 id, grp_name
table grp_host 有 grp_id, host_id => 這張表用來記錄 grp 和 host 之間的 relation

需求是:要寫入 grp_host 這張表,但是,原始資料的 grp 與 host 的 relation ,是用字串來記錄的,也就是 (string, string) 這樣子的 tuple。

總之,這個寫入的合理作法,要用一點小技巧:
建立 temporary table,然後 insert into ... select ,還有,要包在一個 transaction 裡頭!





Saturday, November 4, 2017

postgreSQL 常用指令

這兩天因為要把自己寫的 web app 布署到 amazon EC2 ,也試用了一下 postgreSQL ,比想象中的好用一些。

在 ubuntu 安裝:
sudo apt-get update
sudo apt-get install postgresql postgresql-contrib

linux shell 模式下常用指令:
sudo -i -u postgres         // 切換 postgres 的使用者身分,用來操作資料庫
createuser -P  peter        // 使用 createuser 這隻指令來設定一個使用者 peter ,並且提示設定密碼
createdb         peterdb    // 使用 createdb 這隻指令來建立一個資料庫 peterdb
dropuser         peter     
pg_dump --column-inserts --data-only -d [database_name] -t [table_name]  > file.sql    // 備分資料庫

連進資料庫:
psql                             

又或是在連進資料庫之後,建立使用者 enzo/資料庫 plenish / 給予權限:
CREATE DATABASE enzo;
CREATE ROLE plenish WITH LOGIN PASSWORD 'plenish';
GRANT ALL ON DATABASE enzo TO plenish;

連線後常用指令:
\l                                  // 列出所有的資料庫
\d                                 //  列出所有的資料表
\d+ [tablename]                    //  顯示 table 的 schema
\c  [databasename]               //  切換其它的資料庫
ALTER SEQUENCE users_id_seq RESTART WITH 1000;    //  將 user_id_seq 這個 sequence 設定成從 1000 開始起跳。

< 範例 1 >  (可用於 luminus 專案開發)
create database mydb;                             
create user        myuser     with encrypted password  'mypass';
grant all privileges on database mydb to myuser;
// 建立特定的使用者、資料庫、並且給予完整的權限

< 範例 2 >
alter user <username> with encrypted password '<password>';

< 範例 3 >
alter user <username> createdb;

Tuesday, October 31, 2017

讀網路上的技術文章有感

身為中文的使用者,偷懶的時候,還是會傾向先讀一下中文的資料。此外,國外的一些進階者寫的東西有時候相對比較難、多用了專有詞彙,我又會偷懶看一般人寫的心得。

然後就出現了一些問題:以訛傳訛

比方說像 microservices 。我看了很多文章都把 microservices  的重點放在 docker 、放在 container ,而不是 database 的獨立性:『每個微服務應該有自己獨立的持久性資料儲存,而且這些資料只能透過微服務本身的 API 來存取。』這些搞錯重點的文章居然還一大堆,無形中也助長了人的無知、誤了好多人。

此外, concurrency model 也是一個例子。Golang 很紅。然後就會有一些介紹 Golang 的文章錯誤地把 Erlang 的 Actor model 講成說這跟 Golang 的 CSP model 是一樣的、差不多的。這也是差很多啊…。

Friday, October 27, 2017

要不要用 cronjob ?

要不要使用 cronjob ,我覺得這是一個設計的問題:

我最近在重構的一隻 Script ,它做了下列幾件事:
(1) 每分鐘執行一次,依賴 cronjob 來啟動
(2) 每次執行,它都會呼叫數個外部的 http API

如果考慮布署的方便性,其實我可以考慮把 cron 的部分,直接在 script 裡做掉。這樣子 Devops team 就不用設定 cronjob ,Devops 的成本會比較低。此外,cronjob 因為是系統共用的,有時候運氣不好,就被改掉了。

另一方面,如果是讓 script 自行做 scheduling 的話,我就必須考慮 exception handling 。因為呼叫 API ,有時候網路如果斷掉,就會產生 exception 。產生 exception 的話,有時候整個程式就 crush 掉了,甚至連下一週期也受到波及。然後,我想有想到 Erlang 的設計就是每個 process 都是完全獨立的。任何一個 process 就算 crush 掉也沒有關系。照這個想法的話,用 cronjob 來跑的 script ,每一次的執行也都是獨立的 執行環境,確實也比較簡單。

上網查了一下:似乎也有其它人跟我有類似的疑問? 解法也很妙,還分成兩類:
(1) ofelia 的解法是,重新實作一個類似的 cron 的程式。但是適合跟 docker 合作。
(2) supercronic 的解法則是,實作一個適合在 docker 內部執行的 cron

Thursday, October 26, 2017

Multiple bindings were found on the class path

最近在使用 clojure 來開發和 Apache spark 相關的程式,有使用到 org.apache.spark/spark-core_2.10 這個 library 。但是,等到我要測試的時候,一啟動 lein repl 之後,問題就出現了,

vagrant@owl-docker:~/workspace/src/github.com/clo/october/intowow$ lein repl
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/home/vagrant/.m2/repository/org/slf4j/slf4j-log4j12/1.7.5/slf4j-lo
g4j12-1.7.5.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/home/vagrant/.m2/repository/ch/qos/logback/logback-classic/1.1.3/l
ogback-classic-1.1.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory]
nREPL server started on port 37442 on host 127.0.0.1 - nrepl://127.0.0.1:37442

這個問題是這樣子,我的 clojure 專案因為是套用 luminus ,所以預設使用的 log library 是 logback 。但是不知道引入了什麼 library 之後,classpath 裡又多了一個 org.slf4j/slf4j-log4j12 。而這個又很不巧地先被引入了。所以最後專案使用的 log library 就變成使用 org.slf4j/slf4j-log4j12 而不是 logback 了。

這個問題,我研究了一陣子之後,想了一個解法:
(1) 先準備 lein-deps-tree 這個 leiningen plugin
(2) 執行之後,就可以看到專案的 dependency graph 。

果然,跟我預想的相差不遠,我最後引入的 library 不小心包含了 org.slf4j/slf4j-log4j12
最後,我的 project.clj 的其中一行修改成這樣子:
[org.apache.spark/spark-core_2.10  "1.1.0" :exclusions  [org.slf4j/slf4j-log4j12]]

引入 spark 的同時,不要引入它自帶的 log library

Sunday, October 15, 2017

抽象層滲漏的解決方案 --- 容許客製化的抽象層

「抽象層滲漏法則」(The law of leaky abstraction) 這個詞彙,在我寫這篇 blog 的時候,中文的維基條目還沒有創建出來,所以我的參考資料主要都是來自英文的資料。根據我查的資料,這個法則是因為 Joel Spolsky 寫了文章討論它,所以變得很流行。然而,我覺得真正有趣的部分是英文維基條目裡的一小段話。

An earlier paper by Kiczales describes some of the issues with imperfect abstractions and presents a potential solution to the problem by allowing for the customization of the abstraction itself.

「容許對抽象層的再客製化」是抽象層滲漏的可能解法?這是什麼概念?

(1) Lisp 直譯器可以視為是控制電腦的抽象層,而且這個抽象層是容許客製化的。Lisp 最著名的 macro 功能,就可以讓 Lisp 的使用者自訂自己需要的 syntax 。 換言之,Lisp 直譯器是可客製化的抽象層。Paul Graham 對於沒有 Lisp macro 的程式語言,下了如下的註解:  abstraction that not powerful enough

(2) edn 格式容許使用自訂自己需要的資料型態,並且嵌入自訂的 parser 。所以這個抽象層也是可以客製化。在 edn 的介紹文件中,它用了不同的方式解釋為什麼需要 edn ,原文大概是說,如果一個格式沒有包含足夠的資料型態,使用這個格式的程式就勢必得自行實現這些型態的特殊處理 (抽象層滲漏)  ,所以就會造成 context-dependency

(3) PostgreSQL 的 stored routine 和 user defined types 也是容許使用者訂定自己的特殊規格。這個資料庫也是一個可以客製化的抽象層。

由這個概念去思考之後,我覺得將軟體的實作設計成「容許客製化的抽象層」,算是滿重要的概念。有了這種概念,才容易設計出成熟、好用的產品。

類似的例子還有許多,比方說:
  • Emacs, Atom 編輯器容許 plugin
  • Chrome 可以安裝 plugin
  • C 語言可以嵌入組語
  • Linux kernel module
  • Redis 也有 Redis module
  • Excel 可以嵌入 VBA

session-based authentication v.s. JWT

最近做的一個 side project ,我用 clojure 的 buddy 模組,來實現使用者認証 (Authentication) 的功能。 clojure 的 buddy 模組的一個長處,是支援 JWT (JSON Web Token) ,也因為如此,我特別仔細研究了一下,到底傳統的 session-based authentication 與新的 JWT 有什麼分別?

JWT 和 session-based authentication 相同的地方是:browser 送 request 到 server 時,都在 http request 裡增加了一些資料,讓 request 變成 stateful。不同的地方是: session-based authentication 夾帶資訊的方式是用 cookie 來攜帶 session-id ,使用者的資訊是存放在 server side 的 session 裡頭,安全性比較高。 JWT 夾帶資訊的方式,是在 http header 裡放了 token ,而 token 內容就直接存放了加密過的「使用者資訊」。也因些, JWT 的作法,並不需要 server-side session ,集群 (cluster) 也因而得到了更好的「水平擴展性」(horizontal scalability)

參考資料

但是,實務上,大部分的情況,也可以不去使用 JWT 。因為就算是 session-based authentication 也有很簡單的方式就可以支援超大量的使用者。主要的作法有兩個:一種是把 session 放在 redis 裡,這樣子多台的 application server 就可以透過 redis 來取得使用者的 session 資料。 另一種作法,是在前端的負載均衡器,設定所謂的 sticky session 。讓負載均衡的分配 http 請求,可以讓同一個 session 的請求,永遠被分配到同一台的設備上。

在 12 factor app 的觀點, JWT 是比較好的作法, sticky session 是最糟的作法。但是,從 security 的觀點, session-based authentication 有比較安全一些。JWT 的話最好要搭配 https

Thursday, October 12, 2017

在 Golang 裡使用類似 Clojure 的 with-redefs 技巧

在寫 Golang 的 unit test 的時候,我覺得很不方便的一點就是沒有辦法像 clojure 一樣,利用 with-redefs 將 symbol 重新定義,讓本來有外部依賴的函數可以很快地被測試。但是,公司的資深工程師 Mike 教了我一招。

基本的概念就是這樣子:
1. 在 Golang 裡,可以被重新定義的東西就是 global variable 。
2. 如果要測的某個函數,它本身沒有寫好依賴注入之類的東西,又恰好呼叫了一個有 complex dependency 的函數。就把這個有 complex dependency 的函數用 global variable 來加上一層間接性。

Tuesday, October 3, 2017

curl, netcat, API testing

最近常常在測試一些同事寫的 API ,整理了一下自己覺得好用的小技巧:
(1) mock server
有時候想要看一下,自己生成的 curl 到底是產生怎麼樣的 http request ,產生怎樣的 body ,就需要先架一個簡單的 mock server 。

#如果需要看 http POST 會需要用 netcat
sudo nc -l 8000     #開啟一個 8000 port 的 tcp listener
# Python v2.7
python -m SimpleHTTPServer
# Python 3
python -m http.server 8000

(2) 快速生成 curl 指令
如果是對於既有的 web application 的話,可以利用 chrome 來取得。
Ctrl + Shift + i -> Network -> Copy as cURL 

(3) curl 常用的參數
-s  =>  silent, quiet mode 
-d =>  用來指定要放進 http request 的 body 的內容。
           用了 -d 選項之後,預設的 Content-Type 是 application/x-www-form-urlencoded
           如果要使用客制化的選項的話,要再加上 -H 參數。
-F =>  用了 -F 選項之後,預設的 Content-Type 是 multipart/form-data
           這個選項通常是用於「大量的資料」或是 binary data
-H => 用來指定 http  header 。常見的用法有:
           指定客戶端送出的資料格式                          -H 'Content-Type: application/json'
           告訴伺服器「客戶端想接受的資料格式」  -H 'Accept: application/json'

(4) 從 server side 的 source code 來理解
常見的 server side framework 可以處理的 http content type 通常是下列四種:
  • application/json
  • application/xml
  • application/x-www-form-urlencoded
  • multipart/form-data
(5) 由 server side 的 source code 來猜測, client side 的 curl 該如何產生?
若 server side 是用類似 parseForm, formValue 之類的函數來處理。curl 會是如下的形式:

curl -d 'title=標題&content=測試'  http://localhost:8000/

Tuesday, September 26, 2017

awk style 的 shell script

有一段時間我很喜歡寫一種特定形式的 shell script ,我自己稱之為 awk style shell script 。它通常是長成這個樣子:

這種風格的程式碼,我之前寫得很爽,因為覺得寫起來很快,字數、行數都少。後來回頭看的時候,雖然覺得有些優點、也有些缺點,卻也不知其所以然,最近終於想出原因了。

這種風格它有幾項特色:
1.  主要使用 awk
2.  awk 指令用 unix pipe 串接

仔細分析這種風格使用的程式語言的概念:
1. 每一行的 awk 指令,可以視為是函數呼叫,而且是「高階函數」(higher order function)  。是高階函數是因為 awk 指令都隱含了一個迴圈、每一行就是對應一次的迴圈操作。
2. 每一行的 awk 指令裡的部分運算內容,可以視為是傳遞進入高階函數裡的運算,換言之,它們可以視為是匿名函數
3. unix pipe 之間,是通過有特定格式「字串流」 (string stream) 來傳遞資料。 其『特定格式』是表格的形式,以斷行字元 (\n) 來分隔 row,以空白字元 (\s) 來分隔 column

有了上述的分析,要講出優點就容易多了:
1. 因為使用了高階函數的概念,要寫的 boilerplate code 就變少了,寫起來自然快。
2. 因為使用了匿名函數,一方面減少了 boilerplate code ,還省去了函數命名的心理負擔。
3. 使用有特定格式的字串流來傳遞資料,因為特定格式通常可以描述輸出所需要的完整資料,程式就可以變成「單向的資料流」。程式要復用的時候,通常就是截取資料流之中,可以復用的部分來復用。
此外,這種使用字串流的風格與 C style 的程式設計最大的差異點在於: 「C style 的程式設計風格是只要是不會被函數修改的資料,都盡量不要放進函數的輸入、輸出。」這是因為執行效能考量,減少不必要的拷貝。付出的代價則是,往往為了輸入輸出引數的一點點變化,要寫許多重覆的程式、或是套接 (adapter) 的程式碼,因而減少了開發效率。

4. Unix pipe 雖然不是惰性求值 (lazy evaluation) ,而是 buffered queue ,但是使用 unix pipe 寫出的 shell script 卻是惰性求值的風格:『不需要管資料的長度』。

另一方面,也有缺點,但是要克服缺點,就需要使用 python/clojure 之類的語言才容易克服了。
1. 每一行 (row) 的字串資料對應的 awk 指令,其實就是 functional programming 裡的 map/filter 操作。改成用 map/filter/reduce 來寫,表現力不會打折扣,可讀性還會提高。
2. 匿名函數如果要復用時,還是給它取個名稱比較好。
3. 使用特定格式的字串流來傳遞資料,不如使用 list 或是 hash map 來傳遞資料。後者的表現能力更強、而且還可以夾帶型別資訊

Thursday, September 21, 2017

快速測試 clojure library

在開發 clojure 的時候,有時候會想要快速地測試一個新的 library ,但是,只是快速地測試,卻得要為這個 library 做 jar 的下載、並且在 project.clj 裡設置路徑,也太麻煩了。有沒有比較簡單的方式呢? 可以用一個 leiningen 的 plugin 來辦到這件事。

<安裝>
修改 (~/.lein/profiles.clj) :
{:user {:plugins [[lein-try "0.4.1"]]}}
<實測>  測試 cheshire 這個 json library
vagrant@owl-docker:~/workspace/src/github.com/clo/test$ lein try cheshire
nREPL server started on port 53192 on host 127.0.0.1 - nrepl://127.0.0.1:53192
REPL-y 0.3.7, nREPL 0.2.12
Clojure 1.8.0
Java HotSpot(TM) 64-Bit Server VM 1.8.0_144-b01
    Docs: (doc function-name-here)
          (find-doc "part-of-name-here")
  Source: (source function-name-here)
 Javadoc: (javadoc java-object-or-class-here)
    Exit: Control+D or (exit) or (quit)
 Results: Stored in vars *1, *2, *3, an exception in *e

user=> (require '[cheshire.core :as json])
nil
user=> (json/generate-string {:foo "bar" :baz 5})
"{\"foo\":\"bar\",\"baz\":5}"
user=>

Datomic 學習/使用心得

之前在研究 object-relation impedance mismatch 的時候,發現了 Datomic 這個資料庫,據說這個資料庫可以有效地迴避這個 object-relation impedance mismatch ,於是我找到機會後就來用看看。

學習的過程大概是這樣子:
1.  下載 datomic-free 的 docker 來用,並且寫 clojure 程式將資料塞入資料庫。
2.  註冊 datomic  的網站,取得 datomic 的 web console
3.  上一個 learn datalog today 的網站練習 Datomic 的 Query 怎麼寫。

踩過的坑:
(1) group-by
本來一度以為,Datomic 沒有支援 group-by 的語法。後來仔細查才發現,原來是 implicit 。而且要搭配 :with 語句來控制 group-by 的行為。
範例:
    [:find ?i (avg ?r)
     :with ?u
     :where  [?e :movie/rating ?r]
                  [?e :movie/id ?i]
                  [?e :user/id ?u]]
相當於
select movie_id, avg(rating * 1.0) from RATINGS group by movie_id;

實際測試中,我用電影資料庫,放了 100000 筆的電影評分資料 (user id, movie id, rating)。結果用 Datomic query 做 group-by average 花費了 3000 milliseconds。但是 H2 database 卻只用了 500 milliseconds

( 註:做上述的實驗時,我對 Datomic 頗不熟,也因此沒有對 Datomic 設定 index 。然而, H2 database 卻有設定 index ,所以其實這個比較並不公平。由於電影資料的特性,其實可以考慮將 :/user/id:movie/id 這兩個 schema 都設定為 :db/unique )

(2) transact-async
本來我照範例是用 transact ,結果當有大量資料一開始要匯入時,資料庫就發生 timeout exception 了。查了論壇後,才發現因為 transact 函數內建的 timeout 是十秒。所以論壇上建議,遇到大量匯入或是資料庫在高負載時,要用 transact-async 。

If a system is under heavy load (e.g. an import job) then the default transaction timeout can be too short, and you should use transact-async so you can apply your own timeout logic.

Wednesday, September 13, 2017

clojure 實務技巧


  1. Application 讀取一般檔案
    使用 clojure.java.io/resource ,可以從 classpath 來讀取檔案。檔案放在 /resources 資料夾下即可。
  2. 「尋找 collections 符合某個條件的 item ,找到後傳回第一個」(find-first)
    stackoverflow 列舉了數種寫法 
    (a) filter/ first          -> 最容易理解
    (b) some
    (c) reduce/ reduced  -> 效能最好
  3. lein repl 啟動時發生 timeout
    解決方案: 在 project.clj 裡修改 repl 的 timeout
    :repl-options {
                 ;; If nREPL takes too long to load it may timeout,
                 ;; increase this to wait longer before timing out.
                 ;; Defaults to 30000 (30 seconds)
                 :timeout 120000
                 }
  4. 「讀取 linux 的 /proc 系統檔」
    需要使用一些特別的技巧,因為 /proc 下的檔案和一般的檔案不同,要先做一些轉換。
    (slurp (java.io.FileReader. "/proc/cpuinfo"))
  5. 將字串轉換成整數值
    儘量不要輕易地使用 read-string 這個函數。用比較單純的  (Integer/parseInt "123")
  6. here document
    clojure 沒有 here document 的語法。如果要使用的字串裡頭包含了太多需要逸脫 (escape) 的字元,官方建議的作法是把字串放在 /resources 下的檔案裡,用讀檔的方式取得。
  7. clojure 數值型態 suffix 的意義
    (type 1N)  => clojure.lang.BigInt
    (type 1)     => java.lang.Long
    (type 1.0)  => java.lang.Double
    (type 1M) => java.math.BigDecimal

Saturday, September 9, 2017

lexical binding v.s. dynamic binding

在 clojure 語言裡,變數可以是 lexical binding 也可以是 dynamic binding 。前者用於大多數的情況,因為這樣子寫,一眼就可以看出函數的依賴。後者多半用於一些 global 變數,而且是底層的函數才有在使用的 global 變數。這些 global 的變數,因為使用了 dynamic binding ,上層的函數就不需要一層又一層地去傳遞「變數的綁定」到最內層去。

範例在這邊:

javascript 的變數是採用 lexical binding 。但是, this 這個變數會隨著程式的運行,而指向不同的物件,this 顯然就是 dynamic binding 的變數。甚至 javascript 也提供了 bind 函數,可以用來明確地將函數與某個特定的物件做綁定。

有一種說法: 「Javascript 也可以算是一種 Lisp 的方言」。算不算是,自然是見仁見智。然而,如果我們從這個 lexical binding 和 dynamic binding 的角度來切入的話,確實是有特別的相似之處:因為都同時支持了兩種綁定方式。

Thursday, September 7, 2017

Data-driven programming 資料驅動程式設計

Data-driven programming ,出自 The Art of UNIX Programming 一書 。基本的概念是這樣子:

首先,人腦很不善長理解循序的「邏輯」。相反的是,人腦比較容易理解「資料」,怎樣算是資料呢?不管是表格、標記語言、巨集、樣板系統,這些都算是資料,都比循序邏輯容易理解。

於是,基於這個洞見,Unix 設計師使用它們的工具集:「高階語言、資料驅動程式設計、程式碼產生器、領域專用語言」來讓程式碼可以被極小化資料指定的規格所自動生成。

These insights ground in theory a set of practices that have always been an important part of the Unix programmer's toolkit — very high-level languages, data-driven programming, code generators, and domain-specific minilanguages. What unifies these is that they are all ways of lifting the generation of code up some levels, so that specifications can be smaller.

所謂資料驅動程式設計 (Data-driven programming) 和 OO 來做比較的話,主要有兩點不同:

在資料驅動程式設計裡,「資料」不僅只是物件的狀態,而是往往定義了程式的控制結構。 OO 的首要考量是「封裝」,而資料驅動程式設計首要的考量是「固定的程式碼」寫得愈少愈好。

In data-driven programming, the data is not merely the state of some object, but actually defines the control flow of the program. Where the primary concern in OO is encapsulation, the primary concern in data-driven programming is writing as little fixed code as possible.

書中是用 python 做為資料驅動程式設計的例子,但是 python 是 1990 才有的東西。

所以書裡有一段話描述了 1969 年的歷史:1969 年的 UNIX programmer 習慣於寫「語法解析器的規格」來生成「語法解析器」,好用來處理「標記語言」。因為做完語法解析器之後,剩下的工作就是對配置文件來做一般的「樹走訪」就可以完成了。要漂亮地解決問題,需要資料驅動程式設計的兩個階段來達成,而其中一個 (樹走訪) 建構於於另一個 (語法解析) 之上。

Unix programmers are very used to writing parser specifications to generate parsers for processing language-like markups; from there it was a short step to believing that the rest of the job could be done by some kind of generic tree-walk of the configuration structure. Two separate stages of data-driven programming, one building on the other, were needed to solve the design problem cleanly.

在 Data-driven programming 的概念下,程式不只是 Engine ,而且是 Data-programmable engine 。Data-driven programming 的經典實作品是 Ant 和 Interpreter。

Reflection/ Eval/ Lisp Macro

維基百科上的 Reflection 的定義:動態改變程式行為的能力,就算成是 reflection 。不過,實務上,大部分的高階程式語言的動態自我修改能力 (self-modify ability) ,我認為可以分成三個不同的等級。

(1)  Reflection
程式執行期間,利用外部來的字串來觸發函數。使用這個的話,可以減少一些重複的 switch 邏輯。
Clojure -> ns-resolve
Python  -> getattr
範例: // 現代的語言的標準函式庫幾乎都有類似的功能。
 
(2)  Eval
程式執行期間,增加程式本身 (program) 的功能。因為可以透過 Eval 定義新的函數。
 // Clojure, Python  辦得到。但是 Go 不行。

(3) Lisp Macro
程式執行期間,增加 interpreter 的功能。因為 macro 可以視為是一種 compiler plugin 。它可以增加程式語言本身的新的語法。此處所謂的新的語法,包含了 syntax 和 semantic 。 semantic 的部分,自然是改變了 interpreter 的行為才能辦到。
// Clojure, Hylang, Smalltalk 辦得到。

Wednesday, September 6, 2017

用 EDN 來取代 JSON

我在學習 clojure 的時候,有一個思考一陣子的問題:「程式語言之間的溝通傳輸的格式,要選用 JSON 還是 EDN 。如果使用 EDN 的話,會有什麼明確的好處嗎?」

考慮如下的例子:
要傳遞的資料,有「時間」的資料型態。而中間的傳輸格式,需要使用 rfc 3339
在 EDN 裡,時間可以直接儲存成 rfc 3339 的格式

 #inst "1985-04-12T23:20:50.52Z"

由於 EDN 已經有數種語言的實作可以用,下方是 python3 的範例。
如果傳送的資料格式是用 json 。而且傳遞的資料型態,恰好有一個是 rfc 3339 。 python 的 application logic 就會包含 (1)  json.loads() 和 (2) 一個特定的用來處理 rfc 3339 的邏輯。

與之相對的是,直接用 EDN format ,則是使用 edn_format.loads() 就可以搞定。

相差不多,只差一點點。後者可以讓 application logic 乾淨一點。因為減少了 context dependency 。所謂的 context dependency 就是指為了某些「json 沒有的 data type 」而花力氣去寫的 marshal/unmarshal logic 。

=================================================================

最後,那 EDN 到底跟 clojure 有什麼關系呢? EDN 的延伸資料型態 (extension elements),可以使用標記元素 (tagged element) 去表現。 比方說,rfc3339 就是一種內建的延伸資料型態。 uuid 則是另一種內建資料型態,長成這樣子: 

#uuid "f81d4fae-7dec-11d0-a765-00a0c91e6bf6"

使用者自訂的延伸資料型態,則是長成這個樣子:

#myapp/Person {:first "Fred" :last "Mertz"}

也因為格式的定義如此,每次要對 EDN 增加一種延伸的資料型態時,就是對 EDN 的 parser (每一種語言會有各自的實作) 寫出該種延伸資料型態的 parser plugin ,在 Clojure 的世界也可以稱之為 read macro ,再將這個 parser plugin 註冊到該種資料型態使用的標記元素之後, 這個 EDN parser 就可以無縫地運作。  marshal/unmarshal logic 也就可以完美地放進 parser 裡,不會混淆在 application logic 裡了。

What made lisp different?

Paul Graham 的文章 Waht made lisp different 真的好難懂。不過,我最近也寫過了 Clojure 的 macro ,疑似有理解一些了。

The whole language always available. There is no real distinction between read-time, compile-time, and runtime. You can compile or run code while reading, read or run code while compiling, and read or compile code at runtime.

這一段舉了四個例子來說明:
1. Running code at read-time lets user reprogram Lisp's syntax.
這是在講 Read macro 。
Common Lisp 的 read macro 是 reader 的 plugin ,可以註冊在 reader 裡。 而 Clojure 的話,內建了許多 read macro,但是不開放『自訂 read macro 』的功能。

2. Running code at compile-time is the basis of macros.
macro 可以視為是 compiler 的 plugin 。 在 Clojure 的例子是用 defmacro 。

3. Compiling at runtime is the basis of Lisp's use as an extension language in program like Emacs.
在 Clojure 的例子,使用 clojure.core/load , clojure.core/require 可以在執行期間啟動 compiler 。在 Riemann monitoring system 裡,就是利用 clojure.core/load 來讓 clojure 作為 Riemann 本身的 extension language 。而類似的 monitoring system 比方說像 Kapacitor 的話,則是還要特別定義一個專用的 extension language 。

4. Reading at runtime enables programs to communicate using s-expressions, an idea recently reinvented as XML.
在 Clojure 的例子,最接近的函數應該是 clojure.edn/read ,因為可以用於 untrusted source 。在 javascript 則是 JSON.parse() 。現代 web application 的前後端溝通所使用的 json 格式,也可以視為是一種 reading at runtime 特性的應用。

Wednesday, August 9, 2017

H2 Database

最近在用 clojure luminus framework 來開發,因為 framework 範例的 default database 是 H2 Database ,所以我就來用看看。 畢竟資料庫很多,我也該沒事多試看看非 Sqlite, MySQL 之外的 RDBMS 選項。

開始用了之後,就發現 java 的東西還真的有它很不錯的一些地方。比方說,H2 資料庫的 jar 檔並不大,約 2 MB ,也不用什麼安裝,下載下來就可以用了。使用五分鐘之後,立刻就可以發現的優點就是: 儘管 jar 不大,卻還是同時附上了 console 與 web 介面。這樣子算是很有親和力的資料庫了。

(*) 下載
       http://repo2.maven.org/maven2/com/h2database/h2/

(*) 啟動 h2 server 的指令
      java -cp h2*.jar org.h2.tools.Server
       或是
      java -cp h2*.jar org.h2.tools.Server -webAllowOthers
   
(*) 觀察所有可以用的指令
      java -cp h2*.jar org.h2.tools.Server -?
     
(*) 啟動 h2 shell 環境的指令
      java -cp h2*.jar org.h2.tools.Shell

(*) SQL 指令範例,讀入 TAB delimited 文字檔

Friday, July 21, 2017

作業系統和瀏覽器的 dns cache

公司做的 SAS 服務常常在更改 dns 對應的 ip ,所以我的 chrome 也常常會出現 dns 的錯誤。還是把常用的指令記錄下來好了:

# 重新整理 windows 7 的 dns cache (需要用系統管理員身分啟動 console)
net stop dnscache
net start dnscache

# 重新整理 chrome 的 dns cache
chrome://net-internals/#dns

# 重新整理 linux 下的 dns cache
service nscd reload

Friday, July 14, 2017

clojure vim 開發環境設置 --- vim-sexp

vim-sexp 是我最晚發現的好用的 plugin 。而且是一旦使用習慣,就覺得不可或缺。

安裝方式:
cd ~/.vim/bundle
# vim-sexp will maintain the balanced state of matched character
git clone git://github.com/tpope/vim-sexp-mappings-for-regular-people.git
git clone git://github.com/guns/vim-sexp.git
git clone git://github.com/tpope/vim-repeat.git
git clone git://github.com/tpope/vim-surround.git

會需要一口氣裝好幾個,是因為要裝了另外三個,有簡單易懂的 mapping 快捷鍵之後,才會容易上手。

Text object 選擇功能:
ae -> an element              dae   刪除所見的 element
af  -> a   form                   daf   刪除所見的 form

移動功能:
W     向前移動一個 sexp
B      向後移動一個 sexp 

其它功能:
dsf         delete surrounding form                  刪去包圍 form 的括號
cseb       surround element in parentheses     新增包圍 element 的括號
cse[       surround element in brackets
cse{       surround element in braces
>e    在同一階層的 s-expression 裡右移 element              (1 2 3)       =>   (1 3 2)
>)     slurp , 讓 s-expression 吃掉右邊外面的 element   (1 2 3) 4   =>   (1 2 3 4)
<)     barf    ,   讓 s-expression 吐出右邊外面的 element   (1 2 3 4)   =>  (1 2 3) 4

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 。