ʕ ゚皿゚ ʔ GolangのASTを可視化するツールを作った
はじめてのGo Runtime。
ということで、GoのAST(抽象構文木)を可視化するツールを書いた。
yuroyoro/goast-viewer · GitHub
goast.yuroyoro.net にデモがある。
go/astパッケージを使うと、GoのソースコードからAST(抽象構文木)を得ることができる。
あとはこれをAngulerJSとか使ってみて可視化してみただけ。
ソースコードをアップロードするか、入力してparseボタンを押すと、右側にASTが展開される。マウスオーバーするとASTのnodeに該当するコードが選択状態になる。
以下の手順でインストールできるます
$ go get -d github.com/yuroyoro/goast-viewer $ cd $GOPATH/src/github.com/yuroyoro/goast-viewer $ make install
GoよりAngulerJSの方が難しかったʕ ゚皿゚ ʔ
オマエらはもっとObject#tryの便利さについて知るべき
arr = [ ["foo", "", "bar"], nil, ].sample arr.try(:reject, &:blank?) #=> [“foo”, “bar”]
要activesupport.gem
tryと関数合成は本体に入れて欲しい
ʕ ゚皿゚ ʔ GolangのWeb Application Frameworkを色々試してみてもいいかしら?
うちのメロンちゃんはLv.117です。
Golangで、簡単なWebアプリケーションをいくつかのフレームワークを用いて作成してみた。
サンプルアプリケーションは、こんな感じのPhotoギャラリーアプリケーションで、画像URLを入力すると追加される。
PureというCSSフレームワークのサンプルから拝借した。
yuroyoro/golang_webapp_framework_samples · GitHub
今回試したのは、net/httpパッケージ、Martini、 Revel の3つ。
net/http編
net/httpパッケージでサーバーを書くのはとても簡単だ。以下のように、http.HandleFuncにpathのパターンと処理する関数を登録して、http.ListenAndServeで待ち受けるportを指定すればいい。
func main() { http.Handle("/css/layouts/", http.StripPrefix("/css/layouts/", http.FileServer(http.Dir("views/css/layouts")))) http.HandleFunc("/", indexHandler) http.HandleFunc("/save", saveHandler) http.ListenAndServe(":5050", nil) }
http.HandleFuncに登録する関数は、このように http.ResponseWriterとhttp.Requestを受けるようにしておく。200 OK hello worldを返す関数はこんなん。
func indexHandler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) // headerへ200 OKを設定 io.WriteString(w, "hello, world!\n") // response bodyへwrite
html/template
Golangには、builtinのテンプレートエンジンとしてhtml/templateが提供されている。今回のサンプルアプリケーションでは、これを用いてレスポンスを生成している。以下は、"/"を処理するindexHandler関数。
type Body struct { First models.Photo Photos []models.Photo } func indexHandler(w http.ResponseWriter, r *http.Request) { page, err := strconv.Atoi(r.URL.Path[1:]) if err != nil { page = 0 } // databaseから登録されているphotoを読み出す body, err := loadBody(page) if err != nil { panic(err) } // html/templateでレスポンスを生成して http.ResponseWriterで書き出す indexTemplate.Execute(w, body) } func init() { indexTemplate = template.Must(template.ParseFiles("views/index.html")) }
indexTemplateは、あらかじめinit関数で"views/index.html"というファイルを読み込んで生成してある。以下は、今回のサンプルで用いているものからの抜粋。
{{range $index, $photo := .Photos }} {{with $photo}} {{if eq $index 4 5 }} <div class="photo-box pure-u-1 pure-u-med-1-2 pure-u-lrg-2-3"> <a href="{{.URL}}"> <img src="{{.URL}}" > </a> <aside class="photo-box-caption"> <span>by <a href="https://twitter.com/{{.Author}}">@{{.Author}}</a></span> </aside> </div> {{else}} <div class="photo-box pure-u-1 pure-u-med-1-2 pure-u-lrg-1-3"> <a href="{{.URL}}"> <img src="{{.URL}}" > </a> <aside class="photo-box-caption"> <span>by <a href="https://twitter.com/{{.Author}}">@{{.Author}}</a></span> </aside> </div> {{end}} {{end}} {{end}}
テンプレート内部では、{{ ... }} で値の展開や分岐や繰り返しを指定する。` {{range $index, $photo := .Photos }}` は、'indexTemplate.Execute(w, body)'で渡したBody構造体のメンバーであるPhotosを元に繰り返しを行う、という意味。
O/R全裸
Golangには、Databaseを扱うための標準的なAPIとしてsql - The Go Programming Languageが提供されている。今回のサンプルではsqltite3を使うので、go-sqlite3をdriverとして用いる。
O/R全裸としては、gorpがよいという話なので使ってみた。go-sqlite3, gorpともにgo getで入れておくこと。
go get github.com/mattn/go-sqlite3 go get github.com/coopernurse/gorp
gorpの使い方については、「Big Sky :: Go言語向けの ORM、gorp がなかなか良い」を見て貰うのが手っ取り早い。
今回のサンプルでは、"models/photos.go"にDatabaseへのアクセスを行う処理を切り出してある。
package models import ( "database/sql" "errors" "github.com/coopernurse/gorp" _ "github.com/mattn/go-sqlite3" ) var DatabaseFile = "photos.db" type Photo struct { Id int64 URL string Author string } func InitDb() (*gorp.DbMap, error) { db, err := sql.Open("sqlite3", DatabaseFile) if err != nil { return nil, err } dbmap := &gorp.DbMap{Db: db, Dialect: gorp.SqliteDialect{}} dbmap.AddTableWithName(Photo{}, "photos").SetKeys(true, "Id") err = dbmap.CreateTablesIfNotExists() if err != nil { return nil, err } return dbmap, nil }
InitDb関数で、databaseをopenしている。Databaseのテーブルにmappingする構造体としてphotoを用意し、gop.DbMap構造体にAddTableWithName関数を用いてmappingを登録している。カラム名と構造体メンバのmappingは基本CoCだが、メンバにtagを指定することでカスタマイズできる。
func (p Photo) Save() error { dbmap, err := InitDb() if err != nil { return err } defer dbmap.Db.Close() // Insert dbmap.Insert(&p) if err != nil { return err } return nil } func LoadPhotos(page int) ([]Photo, error) { dbmap, err := InitDb() if err != nil { return nil, err } defer dbmap.Db.Close() if page < 0 { return nil, errors.New("invalid page number") } limit := 8 offset := page * limit var photos []Photo _, err = dbmap.Select(&photos, "SELECT id, url, author FROM photos ORDER BY id DESC LIMIT ? OFFSET ?", limit, offset) if err != nil { return nil, err } return photos, nil }
Databaseへのinsertはdbmap.Insertに構造体を渡すだけ。dbmap.SelectでSQLを渡すと、結果をmappingした構造体のスライスで取得できる。便利。
testing (builtin)
これで、サーバーとテンプレートとモデルがそろったので、アプリケーションとして動作する形になった。が、すこし寄り道して、テストについても書いておこうと思う。
Golang組み込みのテスティングフレームワークとして、testingパッケージが提供されている。これは本当に単なるテスティングフレームワークの元となるライブラリで、素のままで使うのはちと辛みがある。が、何事も基本から。
"models/photos.go"に対してのテストを"models/photos_test.go"に書く。
package models import ( "fmt" "testing" ) func TestSave(t *testing.T) { DatabaseFile = "photos_test.db" dbmap, err := InitDb() if err != nil { t.FailNow() } defer dbmap.Db.Close() err = dbmap.TruncateTables() if err != nil { t.FailNow() } photo := Photo{ URL: "http://example.com", Author: "yuroyoro", } photo.Save() var photos []Photo _, err = dbmap.Select(&photos, "SELECT id, url, author FROM photos ORDER BY id ASC ") if err != nil { t.FailNow() } if len(photos) != 1 { t.Error("Photo.Save() failed") } if photos[0].URL != photo.URL || photos[0].Author != photo.Author { t.Error("Photo.Save() failed") } }
go testコマンドで、"*_test.go"というファイル内でTestから始まる関数が実行される。assert的なのは用意されてなくて気合いのif文とt.Error関数でテストを書いていく。筋力が必要。
実行するとこんな感じになる。
=== RUN TestSave --- PASS: TestSave (0.01 seconds) === RUN TestLoadPhotos --- PASS: TestLoadPhotos (0.02 seconds) === RUN TestModels Running Suite: Models Suite =========================== Random Seed: 1402814295 Will run 5 of 5 specs • Ran 5 of 5 Specs in 0.061 seconds SUCCESS! -- 5 Passed | 0 Failed | 0 Pending | 0 Skipped --- PASS: TestModels (0.06 seconds) PASS ok github.com/yuroyoro/go_shugyo/nethttp/models 0.143s
testing (GoConvey)
GoConveyというテスティングフレームワークを試してみる。BDDっぽい雰囲気。
`go get -t github.com/smartystreets/goconvey`で入れる。
package models import ( "fmt" . "github.com/smartystreets/goconvey/convey" "testing" ) func TestConverySave(t *testing.T) { Convey("Insert", t, func() { DatabaseFile = "photos_test.db" dbmap, err := InitDb() if err != nil { t.FailNow() } defer dbmap.Db.Close() err = dbmap.TruncateTables() if err != nil { t.FailNow() } photo := Photo{ URL: "http://example.com", Author: "yuroyoro", } photo.Save() var photos []Photo _, err = dbmap.Select(&photos, "SELECT id, url, author FROM photos ORDER BY id ASC ") if err != nil { t.FailNow() } So(len(photos), ShouldEqual, 1) So(photos[0].URL, ShouldEqual, photo.URL) So(photos[0].Author, ShouldEqual, photo.Author) }) }
`So(len(photos), ShouldEqual, 1)`とかは、rspecとかに慣れてる人にはとっつきやすいのではないか。
GoConveyにはWebUIが付属しており、goconveyコマンドで起動するとlocalhost:8080でこんな画面が出てくる。ファイルの変更を検知して、自動的にテストを実行してくれる簡易CIのようだ。
testing (Ginkgo)
今度は、GinkgoというBDDテスティングフレームワークを試す。assertionライブラリとしてgomegaというパッケージに切りだされている。
go get github.com/onsi/ginkgo/ginkgo go get github.com/onsi/gomega
ginkgo bootstrapでtestのひな形が作成される。 テストコードはこんな感じになる。rspec風だが、func()の連打が辛み。
package models_test import ( "fmt" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/coopernurse/gorp" "github.com/yuroyoro/go_shugyo/nethttp/models" "testing" ) func TestModels(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Models Suite") } var _ = Describe("Photo", func() { var ( dbmap *gorp.DbMap err error ) BeforeEach(func() { models.DatabaseFile = "photos_test.db" dbmap, err = models.InitDb() if err != nil { panic(err) } err = dbmap.TruncateTables() if err != nil { panic(err) } }) AfterEach(func() { dbmap.Db.Close() }) Describe("Insert", func() { It("should inserts new record", func() { photo := models.Photo{ URL: "http://example.com", Author: "yuroyoro", } photo.Save() var photos []models.Photo _, err = dbmap.Select(&photos, "SELECT id, url, author FROM photos ORDER BY id ASC ") if err != nil { Fail("Failed to load records from database") } Expect(len(photos)).To(Equal(1)) Expect(photos[0].URL).To(Equal(photo.URL)) Expect(photos[0].Author).To(Equal(photo.Author)) }) }) })
ginkgo -vでテスト実行。
ozaki@mbp-2 ( ꒪⌓꒪) $ ginkgo -v ............Running Suite: Models Suite =========================== Random Seed: 1402820295 Will run 5 of 5 specs Photo Insert should inserts new record /Users/ozaki/dev/go/src/github.com/yuroyoro/golang_webapp_framework_samples/nethttp/models/models_suite_test.go:61 • ------------------------------ Photo LoadPhotos when given page 0 should returns first page /Users/ozaki/dev/go/src/github.com/yuroyoro/golang_webapp_framework_samples/nethttp/models/models_suite_test.go:87 • ------------------------------ Photo LoadPhotos when given page 2 should returns last page /Users/ozaki/dev/go/src/github.com/yuroyoro/golang_webapp_framework_samples/nethttp/models/models_suite_test.go:101 • ------------------------------ Photo LoadPhotos when given page 99 should returns empty /Users/ozaki/dev/go/src/github.com/yuroyoro/golang_webapp_framework_samples/nethttp/models/models_suite_test.go:110 • ------------------------------ Photo LoadPhotos when given page -1 should returns error /Users/ozaki/dev/go/src/github.com/yuroyoro/golang_webapp_framework_samples/nethttp/models/models_suite_test.go:119 • Ran 5 of 5 Specs in 0.054 seconds SUCCESS! -- 5 Passed | 0 Failed | 0 Pending | 0 Skipped PASS Ginkgo ran in 1.521415799s Test Suite Passed
テスティングについてはここまで。ほかにも色々あるので、「go言語のテスティングフレームワークについて — さにあらず」などを参考にサレタシ。
Martini編
さて、前置きが長くなったが、GolangでのWeb Application FrameworkであるMartiniを試してみる。
日本語のドキュメントもある。
net/httpで書いたサンプルを移植する。ソースコードはこちら。
package main import ( "./models" "fmt" "github.com/codegangsta/martini" "github.com/codegangsta/martini-contrib/render" "net/http" "strconv" ) func main() { m := martini.Classic() m.Use(render.Renderer()) m.Use(martini.Static("views")) m.Get("/", func(w http.ResponseWriter, r *http.Request, render render.Render) { page, err := strconv.Atoi(r.URL.Path[1:]) if err != nil { page = 0 } body, err := loadBody(page) if err != nil { panic(err) } render.HTML(200, "index", body) }) m.Post("/", func(w http.ResponseWriter, r *http.Request, render render.Render) { url := r.FormValue("url") author := r.FormValue("author") if url == "" { render.Error(500) return } if author == "" { render.Error(500) return } fmt.Printf("Save Photo(%s, %s)", url, author) photo := models.Photo{ URL: url, Author: author, } photo.Save() render.Redirect("/", 302) }) m.Run() }
みてのとおり、Sinatra風だ。m.Getやm.Postに関数を登録している。net/httpと異なる点として、render.Renderを利用してステータスコードや、テンプレートのレンダリングを実行している。
登録する関数は実は任意の引数、戻り値にすることが可能で、Martiniはリフレクションを用いて引数や戻り値の解決をしている。上記のコードで引数で受けているrender.Renderは、Martiniの持つDI機能によって、リフレクションで動的に解決されている。
以下はサンプルより。戻り値としてステータスコードとbodyの中身を返している。
m.Get("/", func() (int, string) { return 418, "i'm a teapot" // HTTP 418 : "i'm a teapot" })
他にも、引数に構造体を受け取る関数を登録すると、リクエストパラメータを構造体にmappingしてくれたりするらしい。このあたりのリフレクションを用いた黒魔術が「Go的ではない」という理由で批判されていたりもする。
Revel編
次は、Revelを試してみる。MartiniがSinatraだとすると、RevelはRailsに相当する感じ。コードはこちら。
revelコマンドで色々と出来るようになっている。
ozaki@mbp-2 ( ꒪⌓꒪) $ revel ~ ~ revel! http://revel.github.io ~ usage: revel command [arguments] The commands are: new create a skeleton Revel application run run a Revel application build build a Revel application (e.g. for deployment) package package a Revel application (e.g. for deployment) clean clean a Revel application's temp files test run all tests from the command-line Use "revel help [command]" for more information.
アプリケーション生成
revel new myappでアプリケーションのひな形ができる。Rails風。
ozaki@mbp-2 ( ꒪⌓꒪) $ revel new myapp ~ ~ revel! http://revel.github.io ~ Your application is ready: /Users/ozaki/dev/go/src/myapp You can run it with: revel run myapp
ディレクトリ構成はこうなっている。app以下にcontroller/viewsがある。
ozaki@mbp-2 ( ꒪⌓꒪) $ tree . . ├── app │ ├── controllers │ │ └── app.go │ ├── init.go │ └── views │ ├── App │ │ └── Index.html │ ├── debug.html │ ├── errors │ │ ├── 404.html │ │ └── 500.html │ ├── flash.html │ ├── footer.html │ └── header.html ├── conf │ ├── app.conf │ └── routes ├── messages │ └── sample.en ├── public │ ├── css │ │ └── bootstrap.css │ ├── img │ │ ├── favicon.png │ │ ├── glyphicons-halflings-white.png │ │ └── glyphicons-halflings.png │ └── js │ └── jquery-1.9.1.min.js └── tests └── apptest.go
revel run myappでサーバー起動。
ozaki@mbp-2 ( ꒪⌓꒪) $ revel run myapp ~ ~ revel! http://revel.github.io ~ INFO 2014/06/15 16:34:46 revel.go:320: Loaded module static INFO 2014/06/15 16:34:46 revel.go:320: Loaded module testrunner INFO 2014/06/15 16:34:46 run.go:57: Running myapp (myapp) in dev mode INFO 2014/06/15 16:34:46 harness.go:165: Listening on :9000
昔のPlayっぽいアレだ。ヒィッ...。
ルーティング
さて、実装だが。まずはconf/routesにルーティングの定義を書く。
# Routes # This file defines all application routes (Higher priority routes first) # ~~~~ module:testrunner GET / App.Index GET /photos/* Photos.Index POST /photos Photos.Save # Ignore favicon requests GET /favicon.ico 404 # Map static resources from the /app/public folder to the /public path GET /css/*filepath Static.Serve("public/css") # Catch all * /:controller/:action :controller.:action
あれ、なんかPlayっぽい……。'/hotels/:id 'のようにpathからparameterを切り出す機能もある。routesの定義は型安全である必要がある。つまり、controllerに対応するメソッドがないとエラーになる。うーんPlay……。
controller
さて、controller側の実装だが。生成されたapp/controllers/app.goを見てみると、Appという構造体がある。これがアプリケーションで使うcontrollerのベースとなるもので、各controllerはこのAppを構造体埋め込みで埋め込んでおくことで、フレームワークが提供する様々な機能を利用することができるようだ。
package controllers import "github.com/revel/revel" type App struct { *revel.Controller GorpController } func (c App) Index() revel.Result { return c.Render() }
今回のアプリケーションでは、Gorpを用いたDatabaseへのアクセスやトランザクションを提供するGorpControllerを用意してAppに埋め込むことで、Database関連の機能をcontrollerに追加している。
package controllers import ( "database/sql" "github.com/coopernurse/gorp" _ "github.com/mattn/go-sqlite3" r "github.com/revel/revel" "github.com/revel/revel/modules/db/app" m "github.com/yuroyoro/go_shugyo/revel_sample/app/models" ) var ( Dbm *gorp.DbMap ) func InitDB() { db.Init() Dbm = &gorp.DbMap{Db: db.Db, Dialect: gorp.SqliteDialect{}} Dbm.AddTableWithName(m.Photo{}, "photos").SetKeys(true, "Id") Dbm.TraceOn("[gorp]", r.INFO) err := Dbm.CreateTablesIfNotExists() if err != nil { panic(err) } photos := []*m.Photo{ &m.Photo{URL: "http://24.media.tumblr.com/d6b9403c704c3e5aa1725c106e8a9430/tumblr_mvyxd9PUpZ1st5lhmo1_1280.jpg", Author: "Dillon McIntosh"}, } for _, photo := range photos { if err := Dbm.Insert(photo); err != nil { panic(err) } } } type GorpController struct { *r.Controller Txn *gorp.Transaction } func (c *GorpController) Begin() r.Result { txn, err := Dbm.Begin() if err != nil { panic(err) } c.Txn = txn return nil } func (c *GorpController) Commit() r.Result { if c.Txn == nil { return nil } if err := c.Txn.Commit(); err != nil && err != sql.ErrTxDone { panic(err) } c.Txn = nil return nil } func (c *GorpController) Rollback() r.Result { if c.Txn == nil { return nil } if err := c.Txn.Rollback(); err != nil && err != sql.ErrTxDone { panic(err) } c.Txn = nil return nil }
具体的なcontrollerの実装は、こんな感じになった。Photo構造体にAppを埋め込んでいる。
package controllers import ( "fmt" "github.com/revel/revel" "github.com/yuroyoro/go_shugyo/revel_sample/app/models" "github.com/yuroyoro/go_shugyo/revel_sample/app/routes" ) type Photos struct { App } func (c Photos) Index(page int) revel.Result { records, err := models.LoadPhotos(c.Txn, page) if err != nil { panic(err) } fmt.Println(records) first := records[0] photos := records[1:] return c.Render(first, photos) } func (c Photos) Save(photo models.Photo) revel.Result { photo.Validate(c.Validation) if c.Validation.HasErrors() { c.Validation.Keep() c.FlashParams() return c.Redirect(routes.Photos.Index(0)) } err := c.Txn.Insert(&photo) if err != nil { panic(err) } return c.Redirect(routes.Photos.Index(0)) }
RevelのcontrollerもMartini同様、関数の引数へのData-Bindingの機能を提供している。レスポンスについては、c.Renderでapp/views/template以下から適切なファイルが選択されてレンダリングされるようになっている。テンプレート自体は、html/templateをRevelが拡張したもので、ほぼ同じように使うことができる。
まとめ
駆け足で、GolangでのWeb Application作成を3つのフレームワークを用いて紹介してみた。他にも、Martiniへのカウンターとしてのhttps://github.com/codegangsta/negroni:Negroniや、静的ファイルもバイナリにまとめてることができるKochaなどがある。
個人的には、Revelはちょっと重量級でMartiniくらいがちょうどよいが、今後もSinatraとRailsみたいな位置づけでそれぞれ使い分ける感じになるのではなかろうか。
以下、参考URL。
- VimでGoのコードを書くときにやっておきたいこと - Qiita
- build-web-application-with-golang/ja/ebook/preface.md at master · astaxie/build-web-application-with-golang · GitHub
- New to Go, trying to select web framework : golang
- Go言語によるwebアプリの作り方
- On Go's Web Application Ecosystem - The Changelog
- Go言語における埋め込みによるインタフェースの部分実装パターン - Qiita
- Go言語のTips — そこはかとなく書くよん。
- Go の interface 設計 - Block Rockin’ Codes
- Goで関数型プログラミング - Qiita
- Gorilla, the golang web toolkit
- mattn/go-sqlite3 · GitHub
- coopernurse/gorp · GitHub
- Big Sky :: Go言語向けの ORM、gorp がなかなか良い
- template - The Go Programming Language
- Codelab: Webアプリケーションを書いてみよう - golang.jp
- Go の Test に対する考え方 - Qiita
- go言語のテスティングフレームワークについて — さにあらず
- testing - The Go Programming Language
- smartystreets/goconvey · GitHub
- Ginkgo
- Go言語のOS X上でのGDBデバッグ環境構築 - unknownplace.org
- Ochiailab Tips: OSX Mavericks にしたら gdb と gcc が消えた時の対処法
- http://blog.handlena.me/entry/20110812/1313104407
- build-web-application-with-golang/ja/ebook/11.2.md at master · astaxie/build-web-application-with-golang · GitHub
- debugging - Golang: Cannot get gdb working for Go programs using C libraries - Stack Overflow
- Issue 5251 - go - symbols not found by gdb when using cgo (osx) - The Go Programming Language - Google Project Hosting
- The Revel Web Framework for Go
ʕ ゚皿゚ ʔ GolangからLevelDBを使う
cgoとLevelDBを使って、タイトルのとおりのものを作ってみた。頑張ればRiakとかInfluxdbみたいなのを書けるかもナー。
ʕ ゚皿゚ ʔ cgo楽しいおシーゴォー
コードはすべてGithubにある。
yuroyoro/leveldb-go-sample · GitHub
なお、この実装はあくまで個人的な練習で作ったものなので、まともにLevelDBをGoから使うならばInfluxdbでも使ってるlevigoがおすすめ。
LevelDBはあらかじめinstallしてある想定。 mac osxなのでbrew install leveldbで入った。
cgoでLevelDBをwrapする
まずは、cgoを使ったLevelDBの簡単なwrapperを用意する。単にLevelDBを使うだけなら、感覚的にはsqlite3みたいに、leveldb_openでopenして得られるleveldb_t構造体を使ってputやgetを呼び出し、終わったらcloseすればいい。
leveldb.go から抜粋。
package main // #cgo LDFLAGS: -lleveldb // #include <stdlib.h> // #include "leveldb/c.h" import "C" import ( "errors" "unsafe" ) // C Level pointer holder type LevelDB struct { CLevelDB *C.leveldb_t Name string } // Open LevelDB with given name func OpenLevelDB(path string) (leveldb *LevelDB, err error) { cpath := C.CString(path) // convert path to c string defer C.leveldb_free(unsafe.Pointer(cpath)) // allocate LevelDB Option struct to open opt := C.leveldb_options_create() defer C.leveldb_free(unsafe.Pointer(opt)) // set open option C.leveldb_options_set_create_if_missing(opt, C.uchar(1)) // open leveldb var cerr *C.char cleveldb := C.leveldb_open(opt, cpath, &cerr) if cerr != nil { defer C.leveldb_free(unsafe.Pointer(cerr)) return nil, errors.New(C.GoString(cerr)) } return &LevelDB{cleveldb, path}, nil }
上記のOpenLevelDBで、leveldb_openでdatabaseを開く。optionは色々指定できるのだが、サンプルなのでcreate_if_missingだけ指定している。
// Put key, value to database func (db *LevelDB) Put(key, value string) (err error) { opt := C.leveldb_writeoptions_create() // write option defer C.leveldb_free(unsafe.Pointer(opt)) k := C.CString(key) // copy defer C.leveldb_free(unsafe.Pointer(k)) v := C.CString(value) defer C.leveldb_free(unsafe.Pointer(v)) var cerr *C.char C.leveldb_put(db.CLevelDB, opt, k, C.size_t(len(key)), v, C.size_t(len(value)), &cerr) if cerr != nil { defer C.leveldb_free(unsafe.Pointer(cerr)) return errors.New(C.GoString(cerr)) } return } func (db *LevelDB) Get(key string) (value string, err error) { opt := C.leveldb_readoptions_create() // write option defer C.leveldb_free(unsafe.Pointer(opt)) k := C.CString(key) // copy defer C.leveldb_free(unsafe.Pointer(k)) var vallen C.size_t var cerr *C.char cvalue := C.leveldb_get(db.CLevelDB, opt, k, C.size_t(len(key)), &vallen, &cerr) if cerr != nil { defer C.leveldb_free(unsafe.Pointer(cerr)) return "", errors.New(C.GoString(cerr)) } if cvalue == nil { return "", nil } defer C.leveldb_free(unsafe.Pointer(cvalue)) return C.GoString(cvalue), nil }
put/getはこんな感じ。C.CStringでkeyやvalueをCの*charに変換しているが、これはmemcpyが走る(ハズ?)なので効率的ではない。levigoの実装では、[]byteに変換してunsafe.Pointerでgoのbyte列のpointerをC側に渡す実装になっているようだ。
net/httpでRESTっぽいガワをつける
goを勉強した事がある人なら誰しもがnet/httpを使った簡単なkvsを書いたことがあるはず。今回はバックエンドをLevelDBにして、net/httpでREST的なガワをつけてみる。
まずはBackend側の実装。 backend.go から。
package main import ( "log" ) type Backend struct { db *LevelDB putch chan putRequest delch chan delRequest quitch chan bool } type putRequest struct { key string val string } type delRequest struct { key string } func NewBackend(dbname string) (backend *Backend, err error) { db, err := OpenLevelDB(dbname) if err != nil { return } log.Printf("LevelDB opened : name -> %s", dbname) backend = &Backend{ db, make(chan putRequest), make(chan delRequest), make(chan bool), } return } func (backend *Backend) Start() { go func() { for { select { case putreq := <-backend.putch: backend.db.Put(putreq.key, putreq.val) log.Printf("Backend.Put(%s, %v)\n", putreq.key, putreq.val) case delreq := <-backend.delch: backend.db.Delete(delreq.key) log.Printf("Backend.Delete(%s)\n", delreq.key) case <-backend.quitch: close(backend.putch) close(backend.delch) close(backend.quitch) log.Printf("Backend stoped") return } } }() log.Printf("Backend started") } func (backend *Backend) Shutdown() { backend.quitch <- true } func (backend *Backend) Get(key string) (val string, err error) { return backend.db.Get(key) } func (backend *Backend) Put(key, val string) { backend.putch <- putRequest{key, val} } func (backend *Backend) Delete(key string) { backend.delch <- delRequest{key} }
NewBackendで先ほど用意したwrapperを使ってLevelDBをopenして、goroutine経由でput/deleteするように実装している。本来ならば、goroutineをsyncして同期っぽいAPIにするべきなのだろうが、面倒なのでサボっている。
次に、net/httpを使ってhttpを処理するserver側。 server.go から。
package main import ( "flag" "io/ioutil" "log" "net/http" ) func main() { // parse command line arguments dbname := flag.String("name", "./testdb", "Open the database with the specified name") addr := flag.String("addr", ":5050", "listen address") flag.Parse() // open database and start backend backend, err := NewBackend(*dbname) if err != nil { log.Fatal("can't open database", err) } backend.Start() defer backend.Shutdown() // listen and serve http http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { switch r.Method { case "GET": HandleGet(w, r, backend) case "POST", "PUT": HandlePut(w, r, backend) case "DELETE": HandleDelete(w, r, backend) } }) log.Printf("Server listening on : %s", *addr) http.ListenAndServe(*addr, nil) } func HandleGet(w http.ResponseWriter, r *http.Request, backend *Backend) { key := r.URL.Path[len("/"):] val, err := backend.Get(key) if err != nil { w.WriteHeader(http.StatusInternalServerError) return } if val == "" { w.WriteHeader(http.StatusNotFound) return } w.WriteHeader(http.StatusOK) w.Write([]byte(val)) } func HandlePut(w http.ResponseWriter, r *http.Request, backend *Backend) { key := r.URL.Path[len("/"):] defer r.Body.Close() val, err := ioutil.ReadAll(r.Body) if err != nil { w.WriteHeader(http.StatusInternalServerError) return } backend.Put(key, string(val)) w.WriteHeader(http.StatusCreated) } func HandleDelete(w http.ResponseWriter, r *http.Request, backend *Backend) { key := r.URL.Path[len("/"):] backend.Delete(key) w.WriteHeader(http.StatusNoContent) }
起動時の引数でdbのpathとportを貰ってlisten。httpでGET/POST/PUT/DELETEをそれぞれ処理している。見たとおりの実装で、特に難しいことはしていない。
Dockerfile
Dockerfileもあるので、手元でdockerが動けば動作させてみることができる。Dockerhubでimageを配布しようとしたのだが、なぜかpendingのままでimageをbuildしてくれない( ;゚皿゚)。
ʕ ゚皿゚ ʔ Golangからv8を使う
cgoとlibv8を使って、タイトルのとおりのものを作ってみた。頑張ればnodeみたいなのをgoで書けるかもナー。
ʕ ゚皿゚ ʔ cgo楽しいおシーゴォー
基本的な方法は以下の記事にあるとおりだが、v8のバージョンが上がっていたりするので、多少の手直しをしてある。
Embedding V8 Javascript Engine and Go | Brave New Method
コードはすべてGithubにある
yuroyoro/golang_v8_Embedding_sample · GitHub
まず、libv8を使う以下のようなwrapperをc++で用意して、cgoから使えるようにしておく。
#ifndef _V8WRAPPER_H #define _V8WRAPPER_H #ifdef __cplusplus extern "C" { #endif // compiles and executes javascript and returns the script return value as string char * runv8(const char *jssrc); #ifdef __cplusplus } #endif #endif // _V8WRAPPER_H
#include <v8.h> #include <string.h> #include "v8wrapper.h" using namespace v8; char * runv8(const char *jssrc) { // Get the default Isolate created at startup. Isolate* isolate = Isolate::GetCurrent(); // Create a stack-allocated handle scope. HandleScope handle_scope(isolate); // Create a new context. Handle<Context> context = Context::New(isolate); // Enter the context for compiling and running the hello world script. Context::Scope context_scope(context); // Create a string containing the JavaScript source code. Handle<String> source = String::New(jssrc); // Compile the source code. Handle<Script> script = Script::Compile(source); // Run the script to get the result. Handle<Value> result = script->Run(); // The JSON.stringify function object Handle<Object> global = context->Global(); Handle<Object> JSON = global->Get(String::New("JSON"))->ToObject(); Handle<Function> JSON_stringify = Handle<Function>::Cast(JSON->Get(String::New("stringify"))); Handle<Value> args[] = { result }; // stringify result Local<Value> json = JSON_stringify->Call(JSON, 1, args); // Convert the result to an UTF8 string and print it. String::Utf8Value utf8(json); // return result as string, must be deallocated in cgo wrapper return strdup(*utf8); }
runv8(char)は、引数の文字列のjavascriptをv8で実行し、結果の値をJSON.stringifyした結果の文字列を返す。
なお、サンプルなのでエラー処理はしていない( ;゚皿゚)。
で、こいつを適当にmakeする。以下のmakefileはosx用
V8_INC=/usr/local/Cellar/v8/3.19.18.4/include V8_LIBDIR=/usr/local/Cellar/v8/3.19.18.4/lib/libv8.dylib CC=g++ CFLAGS= -I$(V8_INC) -I/usr/include -lv8 -dynamiclib -o $(TARGET) SOURCES=v8wrapper.cc OBJECTS=$(SOURCES:.cc=.o) $(V8_DYLIB) TARGET=libv8wrapper.dylib all: $(TARGET) $(TARGET): $(OBJECTS) $(CC) $(CFLAGS) $< -o $@ clean: rm $(TARGET) $(OBJECTS)
あとは、cgoでこのv8warpperをlinkして使えばいい。
package main // #cgo LDFLAGS: -L. -lv8wrapper -lv8 -lstdc++ // #include <stdlib.h> // #include "v8wrapper.h" import "C" import ( "encoding/json" "fmt" "unsafe" ) func RunV8(script string, result interface{}) error { // convert Go string to nul terminated C-string cstr := C.CString(script) defer C.free(unsafe.Pointer(cstr)) // run script and convert returned C-string to Go string rcstr := C.runv8(cstr) defer C.free(unsafe.Pointer(rcstr)) jsonstr := C.GoString(rcstr) fmt.Printf("Runv8 json -> %s\n", jsonstr) // unmarshal result err := json.Unmarshal([]byte(jsonstr), result) if err != nil { return err } fmt.Printf("Runv8 Result -> %T: %#+v\n", result, result) return nil }
RunV8では、'encoding/json'を利用して、runv8の戻り値である文字列のJSONを、goの値にunmarshalする。
以下のように文字列でscriptをRunV8関数に渡せばinterface型で結果を取得できる。
package main import ( "fmt" ) func main() { scripts := []string{ "null", "true", "123", "457.78", "[10, 20, 30]", "'Hello, World'", "new Date()", `obj = {"foo": [1, 2], "bar": {"baz": true, "hoge": "fuga"}}`, } for _, s := range scripts { fmt.Printf("Script -> %s\n", s) var res interface{} RunV8(s, &res) fmt.Printf("Result -> %T: %#+v\n\n", res, res) } }
実行結果
Script -> null Result -> <nil>: <nil> Script -> true Result -> bool: true Script -> 123 Result -> float64: 123 Script -> 457.78 Result -> float64: 457.78 Script -> [10, 20, 30] Result -> []interface {}: []interface {}{10, 20, 30} Script -> 'Hello, World' Result -> string: "Hello, World" Script -> new Date() Result -> string: "2014-06-11T08:58:43.951Z" Script -> obj = {"foo": [1, 2], "bar": {"baz": true, "hoge": "fuga"}} Result -> map[string]interface {}: map[string]interface {}{"foo":[]interface {}{1, 2}, "bar":map[string]interface {}{"baz":true, "hoge":"fuga"}}
このように、JSONをstructにmappingさせることもできる。
package main import ( "fmt" ) type Foo struct { Foo []int `json:"foo"` Bar Bar `json:"bar"` } type Bar struct { Baz bool `json:"baz"` Hoge string `json:"hoge"` } func main() { script := `obj = {"foo": [1, 2], "bar": {"baz": true, "hoge": "fuga"}}` var result Foo fmt.Printf("Script -> %s\n", script) RunV8(string(script), &result) fmt.Printf("Result -> %T: %#+v\n", result, result) }
実行結果
Script -> obj = {"foo": [1, 2], "bar": {"baz": true, "hoge": "fuga"}} Result -> main.Foo: main.Foo{Foo:[]int{1, 2}, Bar:main.Bar{Baz:true, Hoge:"fuga"}}
「関数型Ruby」という病(6) - 関数合成と文脈、Proc#liftとProc#>=、そしてモナ
前回から一年以上が経過しているけど、最近lambda_driver.gemに機能を追加したので、そのことについて書こうと思う。
Rubyで、モナ……っぽい関数合成を実装した話だ。
Rubyで関数合成とかしたいので lambda_driver.gem というのを作った - ( ꒪⌓꒪) ゆるよろ日記
関数合成
関数合成については以前に書いたので、こちらを見て欲しい。
「関数型Ruby」という病(2) - 関数合成 Proc#compose - ( ꒪⌓꒪) ゆるよろ日記
おさらいをしておくと、関数合成とは、 関数gと関数fから、g(f(x))という関数hを新たに作り出すことだ。
(g ∘ f)(x) = g(f(x))
関数gと関数fの合成関数g ∘ fに引数xを渡した結果は、関数gにf(x)の結果を渡したものと等しい。つまり、このような操作である。
f = lambda{|x| x + 1 } g = lambda{|x| x * x } # 合成関数g ∘ f h = lambda{|x| g.(f.(x)) }
これを図にするとこんな感じ。 上記の例の合成関数h: g ∘ fに引数 3を与えた場合。
lambda_driver.gemでは、この関数合成をProc#>>で行うことができるようになっている。
# Proc#>>で合成 h = f >> g h.(3) # => 16
Proc#>>の実装は単純で、以下のようになる。
class Prco def >>(g) # 「自分の計算結果を引数の関数gへ渡す」Procオブジェクトを返す lambda{|x| g.call(self.call(x)) } end end
関数合成と計算の失敗
このように、とても便利な関数合成だが、以下のような状況だと少し困ることがある。
f = lambda{|arr| arr.first } # f: 引数のArrayの最初の要素を取り出す g = lambda{|x| x + 1 } # g: 引数に1を加算 h = lambda{|x| x * 2 } # h: 引数を2倍 # f, g, hを合成 i = f >> g >> i i.([3,5]) # => 4
関数fは、引数に配列を取って最初の要素を返す。関数gはfの結果に1を加算する。関数hはさらにその結果を2倍する。単純だ。
この3つの関数を合成した物が関数i。図にするとこうなる
では、関数iに空配列[]を渡すとどうなるか?
# []を渡すと i.([]) # => NoMethodError: undefined method `+' for nil:NilClass
関数fはnilを返し、関数gはnilに1を加算しようと+を呼び出してエラーになっている。
図ではこうなる。
ここで、関数がnilを返した場合はその計算は失敗したと仮定する。よって、関数gと関数hでは、引数がnilであるかチェックするように変更する。
# g, hに引数がnilかチェックを追加した g = lambda{|x| return nil if x.nil?; x + 1 } h = lambda{|x| return nil if x.nil?; x * 2 } i = f >> g >> h i.([]) # => nil
例外も発生せず、めでたしめでたし……ではない。関数gとiにそれぞれnilをチェックする処理が重複して実装されているのでDRYではない。合成する関数がもっと多くなった場合は面倒だ。
できればこのnilをチェックする処理を共通化したい。
関数合成に細工する
関数を合成するときに、「nilかどうか判定する処理」を間に挟むようにすれば、個々の関数にわざわざnilチェックを実装せずともよい。
以下のように関数合成時に細工を行うProc#>=を実装する。
class Proc def >=(g) lambda{|x| res = self.call(x) # 計算結果がnilならば、後続の関数gを呼び出さずにnilを返す return nil if res.nil? g.call(res) } end end
これで、Proc#>=を使って細工された関数合成を行うことで、計算が途中で失敗した場合は以降の計算を打ち切るようにできる。
f = lambda{|arr| arr.first } g = lambda{|x| x + 1 } h = lambda{|x| x * 2 } # Proc#>=で合成する i = f >= g >= h i.([3,5]) # => 8 i.([]) # => nil
これは、図にするとこのようなイメージである。
合成する関数がどれだけ増えようと問題がない。
j = lambda{|x| x * 3 } # 新たに関数jを合成 k = f >= g >= h >= j k.([3,5]) # => 24 k.([]) # => nil
こんどこそめでたしめでたし。
文脈付き関数合成
Proc#>=によって、関数合成の際に細工をすることで、「途中で計算が失敗したら打ち切る関数合成」を実現できた。
では、nilチェックのような「細工」を任意に指定できるようにしてはどうだろうか?
たとえば、「計算の途中結果をputsで標準出力に吐く」関数合成をしたいとする。
そのために、どのような「細工」をするかを設定するProc#liftメソッドを用意しよう。
class Proc def lift(ctx) # 引数の「細工」を行うProcオブジェクトをインスタンス変数に設定しておく @ctx = ctx # 自身の>=メソッドを定義する(特異メソッド) def self.>=(g) lambda{|x| # ctxに、合成する関数gと、自身の計算結果を渡して処理を「細工」する @ctx.call(g, self.call(x)) }.lift(@ctx) # liftの戻り値のProcオブジェクトも同様にliftしておく end self end end
少々トリッキーな実装なので解説すると、Proc#liftメソッドは細工を行うProcオブジェクト(ctx)を受け取る。
このProcオブジェクト(ctx)は、第一引数にProc#>=メソッドで渡された合成先の関数g、第二引数に合成元の関数fの計算結果であるxを受け取るようにしておく。
liftメソッド内では、特異メソッドとしてProc#>=メソッドを定義する。Proc#>=はインスタンス変数として記憶してあるctxに、合成する関数gと、自身の計算結果を渡して処理を「細工」するようなlambdaを返す。
なお、続けて>=で合成をチェーンできるように、戻り値として返すlambdaも同様に`lift`しておく。
これで準備はできた。
「計算の途中結果をputsで標準出力に吐く」細工を行うctxは、以下のように書く。
ctx = lambda{|g,x| # 引数の関数gを呼び出す res = g.call(x) # 結果を出力する puts "g(#{x}) -> #{res}" res }
では、実際に上記のctxをProc#liftメソッドに渡して、できあがった合成関数を呼び出してみよう。
# Proc#liftで関数合成に細工する i = f.lift(ctx) >= g >= h i.call([3,5]) # g(3) -> 4 # ctxから出力 # g(4) -> 8 # ctxから出力 # => 8
関数gと関数hの呼び出しの後に、標準出力へ結果が出力されていることがわかる。
これは、図にするとこのような感じだ。
先ほどの「nilならば計算を途中で打ち切る」細工は、ctxを以下のように定義すればいい。
ctx = lambda{|g, x| # nilならばgを呼び出さずにnilを返して後続の計算を打ち切る return x if x.nil? g.call(x) }
これで、先ほどと同じように動作する。
i = f.lift(ctx) >= g >= h i.call([4,8]) # => 10 i.call([]) # => nil
この細工を行う関数合成は、前回、前々回の内容を一般化したものだ。
「関数型Ruby」という病(4) - Applicativeスタイル(的ななにか) - ( ꒪⌓꒪) ゆるよろ日記
「関数型Ruby」という病(5) - Object#tryはMaybeモナドの夢を見るか? - ( ꒪⌓꒪) ゆるよろ日記
モナ……
さて、Proc#liftで「細工」を指定することで、様々な細工を関数合成に施すことができるようになった。
ここで、もう一度図を見直してみよう。
先ほどの「nilならば計算を途中で打ち切る」細工は、「失敗するかもしれない計算」という【文脈】上で関数合成が動作しているように見える
「標準出力に吐く」細工は、「結果を出力しながらする計算」という【文脈】上で関数合成が動作しているように見える。 あるいは、関数合成の下に「細工」が【配管】されているように見える。
「細工」を設定するメソッドを`lift`と名付けたのは、実は関数合成を【文脈】上に「持ち上げる」という意味を込めているからだ。
ここで上げたほかにも様々な文脈が考えられる。「外部から環境を与えられる文脈」「複数の結果を組み合わせる文脈」「非同期で計算する文脈」など……。
あれ、それってモナ……。おっと誰か来たようだ。
実際、モナ……則どころかモナ……の形すらしていない(returnもbindもない)のでモナ……ではないのだが、よくわからないと評判のアレも実はこういう配管をやるためのデザインパターンの一種である、と捉えると必要以上に恐怖を覚えずとも済む。
Proc#ymsr
なお、このProc#liftは拙作lambda_driver.gemに実装されており、liftは別名`ymsr`にaliasされている。
Aliased Proc#lift to Proc#ymsr · 953d5d9 · yuroyoro/lambda_driver · GitHub
git push 進捗
git remote rename origin 進捗 git commit -m '進捗ダメです' git push 進捗
便利