2017年12月3日 星期日

Redux in Go

就在昨天我完成了Go版本redux的主要功能
發文要附圖(誤),是連結: redux
首先redux是什麼呢?redux的起源與react有關,facebook推出一種叫flux的資料流架構,而如今redux憑借著簡單優雅的架構成為了主流的實作,它受到elm這隻程式語言的啟發而對flux做出修正
那麼flux是什麼呢?或者說,它的重點是什麼?flux試圖解決JavaScript程式長久以來面臨的問題:究竟是誰改變了狀態?
事實上這不只是JavaScript會面臨的問題,是所有具有多個改變狀態的原因的程式需要面對的問題
追蹤狀態的改變是如此的複雜,flux點出的關鍵在於我們不知道狀態存放的地點以及誰可以接觸到它,既然了解了問題點所在,我們就能問題提出解法
好吧!到了這裡,相信大家都已經了解flux存在的意義,那麼它面臨了什麼樣的新問題呢?
問題在於實作,flux由action dispatcher store view組成(但是view通常並非其實作所關注的,而是一種抽象的概念,指涉使用它的展示層)
store儲存資料,dispatcher分發事件,view觀察store中的資料對自己做出更新
使用者觸發action,dispatch就會觸發已註冊的那些callback,最終達到update store的效果
實際操作起來的問題是dispatcher會變得非常多,每次應用就會出現類似的程式
而這顯然與程式工程師們習慣不符,我們就是『懶』
那麼如何解決這樣的問題呢?redux參考函數式語言的一些概念,提出了
reducer(previousState, action) => newState
這樣的等式,更有趣的是你不能提供一個store初始狀態給redux,而是要給予一個個reducer組成store,每個reducer各自擁有initial state,store的初始狀態變由此決定
除此之外,redux也沒有dispatcher,它只有dispatch函數,所以我們不用管理事件會分發給誰,因為所有reducer都會收到
接著是subscribe函數,這個函數讓呼叫方的函數可以在dispatch時自動被執行,唯一的限制是裡面不能再度呼叫dispatch,這會無限遞迴
好了,最後我們進入正題,這個Go版的redux究竟是怎麼實作的呢?
首先是NewStore函數,它是我們這個版本的createStore,命名是依照Go的慣例
其回傳一個Store指標(我們當然不想要複製整個Store,其成本難以想像,JS的物件則是本來就不會複製(預設))
接受reducer型別作為參數
type reducer func(interface{}, Action) interface{}
reducer定義非常簡單,就是我們前面看到的reducer(previousState, action) => newState的樣子
Store定義如下
type Store struct {
        GetState map[string]interface{}
        reducers []reducer
        subscribes []func()
        atSubscribe bool
        mu sync.Mutex
}
reducers, subscribes無須解釋,GetState即是我們所有state存放的位置,這樣取名是為了呼叫時的清晰度
(其實想改掉了,這樣會被客戶端修改,其實失去了保證性)
mu在共時程式之中保證Dispatch會安全完成
atSubscribe則是保證Subscribe中不得呼叫Dispatch
func NewStore(r reducer, reducers ...reducer) *Store {
        s := &Store{
                GetState:    make(map[string]interface{}),
                atSubscribe: false,
        }
        s.newReducer(r)
        for _, r := range reducers {
                s.newReducer(r)
        }
        return s
}
將參數分成兩部份是因為這樣就不需要自己檢查參數(...散列可以為空之特性),而是由編譯器做出保證
除去上述的特殊設計,非常容易看出程式的邏輯,我們把一個個reducer綁上我們可愛的store
到這裡我們先看看實際案例
import "github.com/dannypsnl/redux"

func counter(state interface{}, action redux.Action) interface{} {
        // Initial State
        if state == nil {
                return 0
        }
        switch action.Type {
        case "INC":
                return state.(int)+1
        case "DEC":
                return state.(int)-1
        default:
                return state
        }
}

func main() {
        store := redux.NewStore(counter)
        store.Subscribe(func() {
                fmt.Printf("Now state is %v\n", store.GetState["counter"])
        })
        store.Dispatch(redux.SendAction("INC"))
}
我們居然可以GetState["counter"]!?
這就是用在newReducer中的魔法了
func (s *Store) newReducer(r reducer) {
        s.GetState[getReducerName(r)] = r(nil, Action{})
        s.reducers = append(s.reducers, r)
}
應該很容易能看出,我們找出reducer的參考名稱,並用此名稱作為鍵值,用nil調用reducer(Action在這裡不重要,想一下Go版reducer的定義中必須在nil時回傳initial state)
然後將reducer放入我們的大殺器reducers中(!?)
getReducerName實作如下
func getReducerName(r reducer) string {
        fullName := runtime.FuncForPC(reflect.ValueOf(r).Pointer()).Name()
        return fullName[strings.LastIndexByte(fullName, '.')+1:]
}
我們先用runtime API取得指標指向的函數,再存取其名稱,這時我們會得到完整的名稱(套件.參考名稱)
所以去除套件部份之後就是我們想要的部份了
做成helper函數的原因是之後更新state時也需要這個函數,DRY,很好

接著我們看Dispatch的實作
func (s *Store) Dispatch(act *Action) {
    s.mu.Lock()
    if s.atSubscribe {
        panic(`you're trying to invoke Dispatch inside the subscribed function`)
    }
    for _, r := range s.reducers {
        funcName := getReducerName(r)
        s.GetState[funcName] = r(s.GetState[funcName], *act)
    }
    // we call subscribed function after state updated.
    s.atSubscribe = true
    for _, subscribtor := range s.subscribes {
        subscribtor()
    }
    s.atSubscribe = false
    s.mu.Unlock()
}
可以看到if atSubscribe程式就會崩潰,這樣能夠阻止想做蠢事的正常呼叫(但是你阻止不了硬要用recover卻不處理這個問題的人)(目前這部份有bug,事實上我們會先遇上deadlock而不是panic,雖然結果正確但是失去錯誤訊息的提示)
我們對操作上鎖
所以程式能夠安全的在共時程式中執行,而狀態更新上鎖也算是正常的設計
然後我們執行那些subscribe進來的函數,注意註冊的函數不能有參數,因為我也不知道要傳什麼參數給你,那很不合理對吧
而最後Subscribe的實作索然無味,就只是將我們可愛的函式們放入註冊函數集合中
func (s *Store) Subscribe(subscribetor func()) {
    s.subscribes = append(s.subscribes, subscribetor)
}

最後總結是為了Go的一些特性我們得要做出取捨,例如原版中,只有一個reducer的情況只需要使用getState就能得到狀態,但是我統一使用函數名稱做存取,因為Go不允許多載函數,而動態參數列亦不太適合(我們得在參數數量超過1時panic,0時檢查reducer的數量,都很麻煩)

另外state型別為interface{}的部份,這並不會造成任何問題,因為state只在reducer中被使用,因此強制轉型並不會造成問題

謝謝觀看,歡迎提出改進意見

2017年11月30日 星期四

Go object orient

Go語言可以說是獨樹一幟,它採取了比較有趣的語法來表現物件
將資料與方法分離,類似於C語言
type Foo struct {
        Name string
}

func (f *Foo) SetName(newName string) {
        f.Name = newName
}
可以看出其結構的簡潔
事實上你可以看做第一個參數為Foo指標的函數,只是Go有語法糖可以讓我們用 . 運算子存取方法
有趣的地方在於你還可以接收Foo型別而非Foo指標型別,如此一來便會複製呼叫物件後執行函數,但這通常並非常見作法,沒有理由時請用指標

而struct的建構有一個特點,如果指定名稱,則剩餘欄位將自動補零值(Go對此做盡可能保證)
因此標準風格也建議總是指定名稱
Foo {
        Name: "foo",
}
這就是所謂的指定名稱
!注意跨行必須有逗號o

Go有所謂的interface,這是一種特殊的型別,它接受實作它的型別作為右值
而所謂的實作就只需要寫一個一樣的方法
type FooI interface {
        SetName(string)
}
Foo有實作此介面,就是這麼簡單
值得注意的是,Go語言的新手往往習慣性的宣告struct與interface
但其實這是不需要的
因為Go接受類似C的匿名struct,而interface亦同(也可以說臨時struct,端看你習慣的稱呼)
重要的是,往往該結構只使用一次而已,宣告全新的型別是污染命名空間,尤其在測試程式中經常遇到此種清況
簡而言之,Go提供了一個異於傳統的設計思維,如何取捨就看你的智慧了

2017年11月18日 星期六

Swift -- extension skill, impl policy base design

經過幾個禮拜的思考,我認爲jon hoffman先生說得有理,我們應該傾向簡單的設計,而Swift不是C++,因此不應該試圖模仿
回到Poilcy的設計上,我們最初的期望是:在編譯期選擇型別,並且透過隱式約束,而不是顯式
先看看C++的案例
struct SQLServer {
    void connect();
    // ...
}
struct MySQL {
    void connect();
    // ...
}
class DB<DBImpl> {
public:
    void connect() {
        DBImpl().connect();
    }
}
我們建構泛型DBImpl的實例並且呼叫其方法,我們知道C++的樣板會在"使用"時展開並檢查
因此我們不會有執行期(runtime)成本,且使用時檢查此點非常重要,這是我們之後會在Swift遇到的難題

那麼我們要如何在Swift中實現一樣的能力呢?
首先我們要了解到Swift並非在使用時檢查,而是宣告時檢查,這只得剛才的程式寫法完全無法編譯通過:首先編譯器會告訴你DBImpl沒有建構式(在C++,如果你沒有宣告建構式,編譯器會幫你寫一個),接著編譯器認為DBImpl沒有connect方法
我最開始的解決方案是利用constraint讓編譯器得到這些資訊
protocol DBPolicy {
    init()
    func connect()
}
class SQLServer : DBPolicy {
    required init() { ... }
    func connect() { ... }
    // ...
}
class MySQL : DBPolicy {
    required init() { ... }
    func connect() { ... }
    // ...
}
class DB<DBImpl: DBPolicy> {
    func connect() {
        DBImpl().connect()
    }
}
可是這樣一來,程式就變得沒有彈性,而且這樣搞,寫多型不就好了(我們只享受到一點效能優化,但是沒有得到最重要的彈性)

接下來我學習到where clauses這個特性,我終於找到了正確的實作方式
class SQLServer {
    required init() { ... }
    func connect() { ... }
    // ...
}
class MySQL {
    required init() { ... }
    func connect() { ... }
    // ...
}
class DB<DBImpl> { ... }
extension DB where DBImpl == MySQL {
    func connect() {
        MySQL().connect()
    }
}
這個方案與我們所想像的差不多,確實符合了隱式約束以及編譯期選擇
但是卻有很明顯的重複性,我們有幾個Policy就需要extension幾次,且所做的事都差不多,只能說Swift不是C++,XD

2017年11月12日 星期日

學習做一個編譯器應該怎麼開始?

我個人的學習經驗是先去找一個你用過的語言,例如C++或是Go之類的
然後找由它們實作的編譯器原始碼觀察,大致應該都能找到 lexer parser codegen(or execute-enigne)這些部份
接著找本基礎書籍來看(我推薦龍書或是Parr的程式語言實作模式)
一一對照實際程式跟理論怎麼接合的,一般來說在書裡通常只找得到殘缺的實際案例
所以找到一個優秀的實作非常重要,我認為Go本身的原始碼就非常值得閱讀
另外也有一些比較簡單的實作(如果你害怕一個大規模的編譯器很難追蹤跟看懂)
例如用Go實作的gisp,用Rust的dyon
以上專案都值得看過
Lexer的部份,我認為實作BrainFuck是非常適合的練習,我也有用Nim做了一個
因為狀態比較少,適合你專注於整體概念上,而不是被複雜的細節繞到暈頭轉向
Parser的部份我認為還是手刻比較方便,並不是說我不用工具,事實上我通常用工具
不過你是要學習,我認為工具對這階段來說並沒有意義
這部份可以嘗試寫出四則運算式的解析器
func parseDefine() *VarDefine {
    name := lex.Get()
    if name.Typ == ID {
        if lex.Get().Typ == ASSIGN {
            return &VarDefine{
                name: name,
                expr:  parseExpr(),
            }
        }
   }
}
上面的函式可以觀察出如何透過遞迴調用產生AST
AST? 我聽到你的疑問了
所謂AST就是抽象語法樹的意思,透過複數的AST互相包含(上述的變數宣告就包含運算式)抑或連貫(變數宣告就是連貫的)成為一支原始程式的中間表述法,進而簡化翻譯為目標語言的難度
因為我們可以宣告方法處理AST資料結構,然後迭代處理整個AST樹
例如函數的AST可能可以這樣定義
type FunctionAst struct {
    name string
    functionType TypeAst
    statements StatementsAst
}
func (f *Function) Codegen(module Module) {
    f := createFunction(module, name, functionType.Codegen(module))
    block := statements.Codegen(module)
    Insert(f, block)
}
可能會像這樣,不斷深入下一層的AST,直到完成翻譯工作
至於優化工作,那又是新的篇章了,期待下次介紹

Redux in go

剛才利用一點時間寫了一個Go版本的Redux
https://github.com/dannypsnl/redux
發現一些有趣的設計議題
我們先看到一個標準Redux程式(JS版本)
function counter(state = 0, action) {
  switch (action.type) {
  case 'INCREMENT':
    return state + 1
  case 'DECREMENT':
    return state - 1
  default:
    return state
  }
}

let store = createStore(counter)
store.dispatch({ type: 'INCREMENT' })
Redux設計思想是藉由回傳新的狀態取代改變手上的狀態來進行狀態管理
我們來看看Go版本(現在的實現)
func counter(state interface{}, act redux.Action) interface{} {
    switch act.Type {
    case "INC":
        return state.(int)+1
    case "DEC":
        return state.(int)-1
    default:
        return state
    }
}

func main() {
    store := redux.NewStore(counter)
    store.Dispatch(redux.SendAction("INC"))

    fmt.Printf("Now state is %v\n", store.GetState())
}
在API上我沒有特別設計,所以我發現兩個問題
第一,不能有型別,有點尷尬對吧
沒有型別我們就要非常小心的確保我們在做什麼,以避免錯誤的轉型
第二,我們沒有辦法設定初始值,這下就有趣了,目前我是先強制給0才能運作,這當然是不對的,下一步應該會調整這部份的API,當然還是會盡量保持與Redux本身相同

至於第一個問題,其實只是麻煩而已,只要回到定義Reducer的地方,我們還是能夠取得正確的型別資訊,當然如果Go擁有泛型,我們今天就不用這麼麻煩了
關於泛型,我覺得重點在於它在參數之前完成計算,就是最重要的特點
如果獲得泛型,確實能夠解決我們現在遇上的麻煩,如何匹配不同型別的state
但是也會帶來別的麻煩,當然這又是另一個故事了...

2017年10月24日 星期二

Vim replace

在Vim中
找出文字並修改不是一件複雜的事

你只需要
:Start, Ends/You want to replace/You want to get/g
:是進入命令模式
Start和End都是行號的代號,你可以用數字1, 2, 3...
也可以是特殊字元,例如$是最後一行

接著s///g是取代命令,s/你想取代的/你想顯示的新字串/g
字串使用正規語言進行比對
例如:
  [a-zA-Z]+
  \".*?\"
等等

最後,如果想要一次套用多個命令
使用 | 字元來分隔不同命令
例如:
  :1,$s/Care/Car/g | 1,$s/Circlew/Circle/g

2017年10月16日 星期一

實作http框架by Go ---- Rocket

最近吃飽閒閒就寫了一個Go的伺服器框架
靈感來自Rust的一個框架 rocket.rs

當然Go與Rust有許多的相異之處,例如Go完全沒有泛型,也沒有巨集等等
因此兩個框架大概只有皮像吧
Rocket for Go大致上看起來像是這樣(!!! 這是舊API)
import (
         "fmt"
         "github.com/dannypsnl/rocket"
)

var hello = rocket.Handler {
        Route: "/name/:name/age/:age",
        Do:    func(Context map[string]string) string {
                return fmt.Sprintf("Hello, %s\nYour age is %s", Context["name"], Context["age"])
        },
}

func main() {
        rocket.Ignite(":8080").
            Mount("/hello", hello).
            Launch() // Start Serve
}
對比一下Rust的原版
#![feature(plugin, decl_macro)]
#![plugin(rocket_codegen)]

extern crate rocket;

#[get("/<name>/<age>")]
fn hello(name: String, age: u8) -> String {
    format!("Hello, {} year old named {}!", age, name)
}

fn main() {
    rocket::ignite().mount("/hello", routes![hello]).launch();
}
看起來差不多

那麼這一切是怎麼實現的呢?
最開始我直接做了一個簡單的對應,把rocket.Handler轉換成Go http程式庫的HandleFunc能夠接受的格式中處理
大概像這樣
func (r *Handler) toHandleFunc() func(http.ResponseWriter, *http.Request) {
        return func(w http.ResponseWriter, req *http.Request) {
                 fmt.Fprintf(w, "%v\n", r.Do())
        }
}

不過很快就發現原生的http程式庫並不支援動態路由(也就是/:name等可部份替換的路由)
最後敲定的解決辦法是用regexp套件進行路由的比較,以取得對應的Handler
因此我們要能夠在註冊程式取得路由的regex以及參數
func (r *Rocket) Mount(route string, h Handler) *Rocket {
 route += h.Route
 match, params := splitMountUrl(route)
 h.params = params
 r.matchs = append(r.matchs, match)
 r.handlers[match] = h
 return r
}
這就是Mount的完整實現,註冊路由由兩個部份組成,一個是Mount函式自帶的路由,另一部份是Handler擁有的路由
結合之後用splitMountUrl這個輔助函式切分出路由的regex模式以及參數
接下來將參數有哪些告訴handler
然後將regex模式放進全域中的regex模式陣列中
並且將regex模式與handler對應起來

那麼splitMountUrl究竟是如何實現的呢?
const legalCharsInUrl = "([a-zA-Z0-9-_]+)"

func splitMountUrl(route string) (string, []string) {
 var match string
 var params []string
 // '/:id' is params in url.
 // '/*filepath' is params about filepath.
 // '/home, data' is params from post method.
 for _, url := range strings.Split(route, "/") {
  if strings.HasPrefix(url, ":") {
   match += "/" + legalCharsInUrl
   params = append(params, url[1:])
  } else if strings.HasPrefix(url, "*") {
   match += "/.*?"
   params = append(params, url[1:])
   break
  } else if url != "" {
   match += "/" + url
  }
 }
 if match == "" {
  match = "/"
 }
 return match, params
}
這是追求簡單化的實作,非常沒有效率
但是暫時就這樣了
ps. 第三種對應我還未實作
首先用/字串切分路由,接著如果是:開頭的就是參數部份,我們就插入一個legalCharsInUrl字串
這個字串是我們能在路由中接受的字元集的regex表示法
那麼*則是路徑參數,那麼之後的路由都屬於路徑,因此不再處理
最後是!= ""是因為Split後的陣列會有空字串,這會導致錯誤,因此略過

接著我們看Launch的實作
func (rk *Rocket) Launch() {
 http.HandleFunc("/", rk.ServeHTTP)
 log.Fatal(http.ListenAndServe(rk.port, nil))
}
簡單到不行
我們提交唯一的HandlerFunction(原生http程式庫的handler)
然後啟動伺服器
最後來到匹配路由的核心部份
rocket.ServeHTTP
func (rk *Rocket) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 var match string
 for _, m := range rk.matchs { // rk.matchs are those static routes
  if m != "/" {
   matched, err := regexp.MatchString(m, r.URL.Path)
   if matched && err == nil {
    match = m
    break
   }
  } else {
   if m == r.URL.Path {
    match = m
   }
  }
 }
 fmt.Printf("Rquest URL: %#v\n", r.URL.Path)
 h := rk.handlers[match]
 matchEls := strings.Split(match, "/")
 Context := make(map[string]string)
 splitRqUrl := strings.Split(r.URL.Path, "/")
 j := 0
 for i, p := range splitRqUrl {
  if matchEls[i] == legalCharsInUrl {
   Context[h.params[j]] = p
   j++
  } else if matchEls[i] == ".*?" {
   Context[h.params[j]] = strings.Join(splitRqUrl[i:], "/")
   break
  }
 }

 fmt.Fprintf(w, h.Do(Context))
}
輪詢rocket中的matchs(還記得吧,我們在Mount中看過)
去除/是因為在正規表達式中它會匹配走所有路由,而那不是我們預期的行為
其餘路由如果成功(matched==true)匹配到request.URL.Path,就把match變數改成這個regex
因為接著我們要靠這個值取得對應的handler(rocket.handlers[match])

接著我們把match與r.URL.Path用Split切開,如果是match的子字串是 ':' 也就是(legalCharsInUrl)
那麼就把對應的splitRqUrl的值放入
例如:
match: /hello/name/:name/age/:age
r.URL.Path: /hello/name/danny/age/20
其Context就是
{
  "name": "danny",
  "age": "20"
}

至於 '*' 也就是 ".*?" 則是把接下來的整段路由都放入Context,這需要用Join恢復被切開的字串
最後將Context傳入Handler.Do並寫出其執行結果

這就是Rocket for Go用到的所有技術
最後這裡 => Rocket for Go
就是專案原始碼所在的位置