Sunday, July 29, 2018

Thinking in Data (Stuart Sierra)

Thinking in Data

(*) Data Oriented Programming
 
總結: 
(a) 寫程式解真實世界的問題,要用 Data 來對真實世界建模。
(b) 如果要驗証程式的正確性,將程式內部的 Data 加以「視覺化」(visualize) ,常常會比 unit test 還管用。
(c) Make your data visible!


1 用 Data 來對真實的世界建模。
  => 容易除錯。
  => 比物件好太多了

2. 物件 (Object) 適合用來建模的東西,往往是完全在電腦內部的東西。
   stack, queue, GUI, widgit 
   註: Clojure 的 vector, list, set, hash-map ,就是使用 Object 來建模。Why? 因為是電腦內部的東西,一方面效能很重要、另一方面需要 Polymorphism

3. Examine Each Step
   對有使用 reduce 函數來除錯的技巧
   (a) 把 reduce 換成 reductions ,如此就可以看到運算過程的每一個 step
   (b) 使用 pprint

4. Everything is a Map
   一種抽象思考:
   Collections
   This ..  | maps from ... | to ...
   ---------------------------------
   Map     | Keys              | Values
   Set       | Values            | Values
   Vector  | Integers         | Values
   List      | first/rest         | Values/List
 
5. Add Keys with Impunity

(assoc stuart :business/role :developer)
 
用 map 來對真實世界的東西建構,隨時可以加入新的東西。
如果要避免 key 的 collision ,可以對 key 加上 namespace

6. 一個函數的 argument 超過三個的時候
   => 用 map 來把 arugment 包裝起來是更好的作法。

   (defn full-name [person]
     (let [{:keys [first-name last-name]} person]
       (str first-name " " last-name)))

7. defrecord, defprotocol 這些通常是用來處理需要高效率或是 Polymorphism 處理的情況、
   然而,十之八九,用 map 來建模就已經很夠用了。

8. 封裝(Encapulation)實作細節這個技巧,被應用的時候,我們會無法看到 data representation
   然而,將 representation 隱藏起來是有代價的。
   (a) 效能
   用同一個介面去輕易地使用不同的物件,會有不同的效能屬性,有時被誤用,很傷效能。
   (b) 實作的難度
   (c) 「網路傳輸/程式之間的傳輸」仍然依賴於 representation
   也因此,任何我們透過隱藏 data representation 得到的好處,往往會被扺消,一旦我們的程式會接觸到外部世界。
 
   What I suggest, instead of encapsulating your representation, let your representation be visible and think about them. Choose the representation that best fit a particular module, a program, or a component that your are dealing with. Then, you establish the boundary between the components, where representation can change. We change representation all the time. That is what computer can do more than anything else.

 
9. Data Literals feature
   (*) Isolate literal representation from what's in memory
   不同的記憶體格式,可以共用同一種「printed data representation」

   (java.util.Date.)
   ;;=> #inst "2012-03-03T22:50:21.656-00:00"
   (java.util.GregorianCalendar)
   ;;=> #inst "2012-03-03T22:50:21.656-00:00"

(*) 應用 data representation 的技巧來撰寫函數

1. Isolate Components
對一個 state 要經過多次的轉換,但是每一次的轉換,只修改它的一部分狀態,可以用下列的 pattern 來實作。

(defn complex-process [state]
  (-> state
    (update-in [:part-a] subprocess-a)
    (update-in [:part-b] subprocess-b)))


2. Isolate side effects
下列的程式很難測試
;; Bad
(defn complex-process [state]
  (let [result (computation state)]
    (if (condition? result)
      (launch-missile)
      (erase-hard-drive))))


相對比較容易測試的版本
;; Better
(defn complex-process [state]
  (assoc state :analysis (computation state)))

(defn decision [state]
  (assoc state :response
         (if (condition? (:analysis state))
            :launch-missile
            :erase-hard-drive)))

(defn defend-nation [state]
  (case (:response state)
    :launch-missile (launch-missile)
    :erase-hard-drive (erase-hard-drive)))


3. 由 data 思維,推導出 naming convention
A function which...       |  has a name that is...   |  return...
-----------------------------------------------------------------
Computes a value         |  noun: what it returns  |  value
-----------------------------------------------------------------
Create a new thing        |  noun: what it creates  |  value
-----------------------------------------------------------------
Gets input from ouside |  "get-noun"                  |   value
-----------------------------------------------------------------
Affects outside              |  verb: what it does       |  nil
-------------------------------------------------------------------

(*) Summary
(a) Make data visible
(b) Make functions composable
(c) Isolate computation decisions from side effects
(d) Establish boundaries
    - from computation world to side effect world
    - from one data representation to different data representation