顯示具有 Golang 標籤的文章。 顯示所有文章
顯示具有 Golang 標籤的文章。 顯示所有文章

2018年1月1日 星期一

Refactor Redux-go

如標題所示,這次我要談論的是redux-go重構
在幾次的測試撰寫過程中,我發現舊版的redux-go有一些小問題
首先是Action物件建構不易,且無關的資訊不斷出現
何謂建構不易?我們必須用
redux.SendAction("type")
這樣彆扭的寫法來建構只有typeAction
然而需要Args時,卻要使用
&redux.Action{
    Type: "type",
    Args: map[string]interface{} {
        // ...
    },
}
這樣複雜的寫法,直接使用struct非常容易出錯,如果客戶端直接插值給Args,結果只會是panic
讓介面容易被使用,不容易被誤用
而且redux這個模組資訊完全是非必要的,所以我在v0.5.0將整個Action部份移到redux/action模組
這樣一來程式就變成了
import "github.com/dannypsnl/redux/action"

action.New("type")
非必要的資訊已被移除,現在我們可以專注於action
下一步是讓新增Args的介面容易使用,所以我在v0.5.1新增func (*Action) Arg(string, interface{})方法進入action模組
於是新增Args變成
action.New("type").
    Arg("key", value).
    Arg("key2", value)
這樣的流暢呼叫式

第二個部份是Store,大抵和Action問題一樣,所以亦將其移入store模組
所以客戶端程式就變成了
import "github.com/dannypsnl/redux/store"
import "github.com/dannypsnl/redux/action"

func main() {
    store := store.New(reducer...)
    store.Dispatch(
        action.New("type").
            Arg("key", value).
            Arg("key2", value))
}
注意省略概念外的程式

第三是DispatchC方法的部份(屬於Store)
原本我發現在效能較高的電腦上,序列式的計算subscribed function比共時化的程式更快
於是分成兩個API
但是經過檢討,我認為這只會造成困惑,且未來多核心能力上升,Go亦可能優化其排程器使實作更加高效,比起讓客戶端負擔測試程式效能的成本,直接採取共時版本的實作更加適合,因此採取這個作法(v0.6.0)

經過這次重構,我重新認識了Go的模組組織概念,還有誰來負擔決策成本的問題,是非常有趣的體驗

2017年12月24日 星期日

Getting Start in Go -- part 1

Many people want to learn Go. But how to start? That is a big problem.
Maybe you already heard a lot like: Go is good at concurrency with go-routine, channel brabrabra, but I think those things won't be most important things in Go.

I think the most important thing in Go is: Simple. Keep this in mind.
Why did I say that? Think about
public static void main(String[] Args)
Should we really need such like this?
With a link, I hope you can rethink the problem.
That twitter shows an unreadable error message from C++ compiler.
Why simple is the point, consider python. How quick is its development?
So I will tell you some good part of Go. And what's are you should avoid.

@ package system
import (
    "os"
    "fmt"
)
import something in Go is simple. Just with some string, but what rule them use?
It managed by path.
You need to set $GOPATH in your system at the beginning. And install process should set a $GOROOT, and they are the package location.
For example, if you have a GitHub project.
You should place it under: $GOPATH/src/github.com/your-account/project
[Good part]: In fact, because of mapping rule about domain & Go package system. We can help different people run up the project, without complex setting script(as you can see in C++ project, and more language can have)
ps. This benefit also on npm, cargo, nimble these package manager with a good package dependencies document.
Whatever, that is the way how Go deal with this problem.
After import, you will get a package name, that is final stuff of the path string
For example, import "github.com/dannypsnl/redux" will get redux.(My GitHub project, XD)
[Good part]: If you don't like the project name(maybe that is too long), or both packages have the same name, you can rename it.
import (
    "os"
    "fmt"
    rdx "github.com/dannypsnl/redux"
)
In this case, you will get rdx, instead of redux.

[Bad part]: Now, the problem comes, if you need to use a certain version of a package, but another project depended on another version of this package. How to solve it?
In fact, we don't have a good solution, it's also is the biggest problem in Go.
My way is to make sure the package I am using is using git to do version control.
Then write a build script for my project, check out the correct tag I want to use.
Not perfect, but work.

@ Variable
In Go, we have four-way to define a var.
func main() {
    var a int
    var a int = 1
    var a = 1
    a := 1
}
[!Warning]: you can not declared a four time in actual program.
1. define without an initializer, then go will init it for you.
2. define var with type and initializer
3. only initializer
4. short def
[Important]: short def can only using in function
[Good part]: I prefer to use short define in function, that is like python. And the better thing is you know that is definition or assignment.
[Bad part]: First way have a problem, in user-defined type, it usually work not good.
My suggestion doesn't use it.
The Second way is not good when the type can be easy for sure. But useful when it doesn't.
Third way useful when the type can easy to sure.

@ Loop
Go only have a loop syntax, that is "for".
> forever: 
for {
    // statements
}
Just without any condition.
> traditional for:
for i:=0; i<10; i++ {
    // statements
}
As you can find in C. [Important]: i++ is a statement, cannot be expression
> enumerate loop:
list := []int{ 1, 2, 3 }
for i, v := range list {
    // statements
}
i is index, v is value of list
If you enumerate map, then that will be key, value.
> while loop:
for ;$condition; {
    // statements
}
Put condition(i < 10) only is while loop.

@ gofmt
Go required the same format of the file.
[Good part]: Easy to find out the syntax error. The team works become easier at here.
[Bad part]: Maybe you just hate being controlled.

I will talk about others things next time. See you.

2017年12月17日 星期日

Generic with Go?

Hi, I found I had talk about Go with generic have a problem. And it caused by interface{}
Of course, interface is a powerful tool in Go. We can use it as contract to make sure some references must have some method.
But this concept could have some problem when it work with generic.
Consider this:
func X<T>(obj T) T {
    // impl
}
func X<Writer>(obj Writer) string {
    // impl has a little different with common T
}
It seems normal, we need specialize some generic for special type, right?
But the problem coming, Writer is a interface, so anything have Write(assume) method, will fall into this X, it's very awkward. We could accept we will execute the normal version.
Out of expected might be almost the worst thing in computer programming.

I have to say all trick can solve it is not good.
But if drop interface, it will be panic, and I also didn't think that will be good.
I have some suggests, don't specialize interface type, if you can.
If you use anything implement by generic, testing the use case you have to make sure it work as expected.

So just these, writing Go is a interesting trivial.

2017年12月4日 星期一

Go binding llvm

Use command
$ go get -d llvm.org/llvm/bindings/go/llvm
to get llvm project
Then
$ cd $GOPATH/src/llvm.org/llvm/bindings/go/llvm 
Then
$ mkdir workdir && cd workdir && mkdir llvm-build
Then go back to dir go/llvm
$ ./build.sh -DCMAKE_BUILD_TYPE=Release
I had installed Ninja, then build script use it to build system.
I am not sure if you didn't install it, can compile or not.(Maybe will invoke make)

After those steps.
You can create a main.go
package main

import (
    "fmt"
    "llvm.org/llvm/bindings/go/llvm"
)

func main() {
    m := llvm.NewModule("hello")
    fmt.Println(m)
}
Then go run the main file.
You will see some LLVM IR
It contains module information.
Thanks for see.

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月12日 星期日

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月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
就是專案原始碼所在的位置