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。
Thursday, September 7, 2017
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 辦得到。
(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 辦得到。
Labels:
eval,
macro,
reflection
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 裡了。
考慮如下的例子:
要傳遞的資料,有「時間」的資料型態。而中間的傳輸格式,需要使用 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 裡了。
Labels:
clojure,
edn,
json,
python,
read_marco
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 特性的應用。
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 文字檔
開始用了之後,就發現 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
# 重新整理 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
安裝方式:
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
其它功能:
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
Subscribe to:
Posts (Atom)