Yet Another JSON な json5をgolangでparseできる奴を作った
このエントリは Go Advent Calendar 2014 の記事です。
さて、僕はJSONSchemaで疲弊してたんですが、主に疲弊してた理由としてはJSONの表現力不足(ケツカンマ許さない、コメントかけない等)とJSONSchema Validatorで不正な値があった時の情報量の少なさでして、JSONの表現力不足に関しては皆さんYAML使ったりして色々解決してるとは思います。
そこで自分もYAMLではなく、JSON5使って解決しようと考えてgolangで実装してみました。
JSON5の詳細に関してはこちらのエントリを参考にされると良いかと思います。
普通にjson5をインストールする
インストールは簡単。
$ brew tap yosuke-furukawa/json5 $ brew install json5
そんで、こんな感じのjson5ファイルを用意して、
// This is json5 demo // json5 can write comment in your json { key : "Key does not need double quote", // json specific "of" : "course we can use json as json5", trailing : "trailing comma is ok", }
デモ:
$ json5 -c example.json5
{
"key": "Key does not need double quote",
"of": "course we can use json as json5",
"trailing": "trailing comma is ok"
} ってな感じにちゃんとしたjsonが返ってくるようになります。
これでコメントも書けてケツカンマにも対応して、キーをダブルクオートで括る必要がないJSON5が使えるようになりました。
プログラマブルに使うなら
$ go get github.com/yosuke-furukawa/json5/encoding/json5
ってしてから
package main import ( "encoding/json" "fmt" "github.com/yosuke-furukawa/json5/encoding/json5" "os" ) func main() { var data interface{} dec := json5.NewDecoder(os.Stdin) err := dec.Decode(&data) if err != nil { fmt.Println(err) } b, err := json.MarshalIndent(data, "", " ") if err != nil { fmt.Println(err) } fmt.Println(string(b)) }
こんな感じで使います。
ただまだブロックコメントやマルチ行文字列に対応してないので、その辺は今後実装していきます。
Farewell Node.js (翻訳)
「visionmedia、Node.js辞めるってよ」って事で、今回はこの話の翻訳ですね。
Farewell Node.js — Code adventures — Medium
最近のnode.jsはホントTJ Fontaine のリーダー就任から始まってNode.jsでできたエディタであるAtomがreleaseされたり、gemのモジュール数をnpmのモジュール数が抜いたり、socket.io v1.0が出たりと色々あるんですが、今回の話は飛び抜けて衝撃的だったなぁと思ってます。
一応知らない方のためにvisionmediaについて説明しておくと、以下のモジュールは全てvisionmedia製です。
- express (Web Applicaiton Framework)
- mocha (Testing Framework)
- jade (hamlライクなtemplate engine)
- stylus (meta css)
- component (フロントエンドのパッケージマネージャ)
- koa (Web Application Framework)
- co (yieldをwrapしたフローマネージャ)
- commander (CLI作成ツール)
これらのライブラリは一度は聞いたことあるんじゃないでしょうか。
彼はnode.jsの初期からNode.jsのライブラリやツールを作り続けていて、Node.jsコミュニティに多大な貢献をしています。彼の動きというのはnode.jsの業界だけじゃなく、Web業界に大きな影響を与えています。
そんな彼がなんでNode.jsを去るのか、翻訳してみたので最後まで読んでみてください。
さようなら Node.js
node.js landから僕は去ります
僕は今日まで、Node.jsとproductionにおいて長い間戦ってきた。そして不幸なことにこれ以上楽しめなくなってきてしまった、少なくともこのメッセージを最後に公式な別れの挨拶とさせてもらう!あと重要なことだけど、僕のライブラリをメンテしてくれるメンテナーを募集してます!
Nodeはうまく物事を進められる、だけど突き詰めた結果、最近の僕が惹かれるソフトウェアにとっては正しいツールではなくなってしまった。僕はまだNodeをweb siteで使うことを計画しているけど、もし誰かがメンテナーになってくれるなら是非僕に教えて欲しい。あなたのGitHubアカウントとnpmユーザー名を書いて送ってくれ!僕が求めるのは既存のAPIのドラスティックな変更をしないでほしいってことだけだ、もしドラスティックな変更をするなら新しいプロジェクトを始めてくれたほうがマシだろう☺。
Koaは僕がメンテするプロジェクトの一つにはなるだろう、もちろんcoとその他のkoaフレンド達もだ。
聖杯 (The Holy Grail)
僕はCがずっと好きだった、だけどCで仕事する人はやりがいがある事もエラー起こしがちな事も両方知っている。C言語で作業することが最速だって確実に言えない限り、日々の作業においてC言語の選択を正当化するのは大変だ。C言語のシンプルであることを僕は常に賞賛してきた、しかし巨大な量のboilerplate無しで成長することは出来ないだろう。
分散システムで作業をすればするほど、使いやすさやロバスト性よりもパフォーマンスを好むNodeの方向性に対してフラストレーションが溜まってきたんだ。一週間くらい前から僕はGoで大きな分散システムを書きなおしてみた、そしたらどうだ、ロバストもパフォーマンスも改善され、そしてメンテナンスも簡単になった、それだけじゃなくてテストカバレッジもよくなった、それ以来ずっと同期的なコードがより素晴らしくシンプルに成立している。
僕はGoが聖杯(The Holy Grail)だって言ってるんじゃない、完全じゃないところもある、だけど今日までに存在している数ある言語の中でGoは僕にとってはすばらしいソリューションだったんだ。RustやJuliaといった"次世代の"言語は活用する場所が見つかり、成熟すればさらに素晴らしいソリューションになるだろうと確信している。
個人的には、Goに対して最も興奮したのはその開発スピードだ、彼らがしきりに2.0にしたがっているのを見るとワクワクするし、彼らは既に存在するものを壊し始めることに抵抗が殆ど無いんだ、素晴らしいことだよ。
修正:いくつかのMLにポストされた内容を誤解してたみたいだ、彼らは別にbreaking changesをしようとしているわけじゃないみたいだ。
これが真実だったなら良かったのに、もし言語から利益を得るなら僕は素早くものを壊す事が良いことだと信じているから。だけど僕は巨大で動作中の重たいシステムを作っているわけじゃないからね :D
なぜGoなのか?
Nodeは今でも良いツールだし、もしあなたにとってうまくいっているならそのことに対して心配することは何もないよ、でももしNodeがあなたを困らせているなら、そこから出て、他の手段を見つけるのを忘れちゃいけない。(プロダクトに対してGoを使って数時間作業してみたら僕はもう夢中になってたよ。)
もう一回言うけど、僕は決して「Goが完璧に良い言語だ」、「絶対に使った方がいい」、なんて事は言ってない、年齢の割に成熟しててロバストだからだ(大体Nodeと同じくらいの年齢だ)、型によるリファクタリングは楽しくてシンプルだし、Goのツールはプロファイルやデバッグするには素晴らしい、さらにコミュニティは強力な規約、豊富なドキュメント、フォーマッタ、ベンチマークとAPI設計を持っている。
最初にGoを聞いた時、Goの標準ライブラリのいくつかは酷いものだと思った、Nodeの中にある極端なモジュール性に慣れすぎていたり、Rubyの腐った(過去の)標準ライブラリの経験をしてたからだ。Go言語に関わった後では標準ライブラリのほとんどが今日プログラミングをするために必要な(圧縮, json, IO, バッファリングされたIO, 文字列操作といった)本質的なものだという事がわかった。これらのAPIのまとまりはよく定義されていて強力だ。標準ライブラリを消費するだけで全体のプログラムを書きあげることができて、しかも非常に簡単だ。
3rd party Go packages
ほとんどのGoのライブラリはlook & feelが似ている、僕がこれまで見てきた3rd partyのコードのほとんどはこれまでのところ高品質だ。JavaScriptはスキルレベルの範囲が広いため、Nodeで良いライブラリを見つけるのは難しい。
Goのパッケージには中心となるレジストリは存在しない、だから同じ名前のパッケージが5個も6個も見つかる時がある。そういった時には混乱することもある。そのため、一番良いソリューションを決めるためにそれぞれ中身を読み込むことになるだろう。Nodeでは既に認められてるパッケージがある、例えば"redis", "mongodb-native", "zeromq"といったように、だからあなたはそれが一番良いものだと仮定して(中身を読むことなく、)その場所で立ち止まってしまうかもしれない。
Go vs Node
もしあなたが分散を仕事としているなら、Goの表現豊かな並行性がとても使いやすいって事がわかるだろう。僕らはNodeで似たような事を実現しようとして、generatorsをNodeで持ってきた、だけど僕の意見ではgeneratorsはGoの半分くらいしか方法を提供していないように見えるんだ。エラーハンドリングとエラーリポーティングを分割しないって事は贔屓目に見ても二流の解決策だと思う。より良いソリューションを知っていながら、僕はコミュニティが最適化するのに3年とか待ちたくなかった。
個人的な意見だけど、Goでのエラーハンドリングは優れているよ。Nodeは全てのエラーややっていることの判断に対して考えないといけないけど、そういうセンスを持っていれば素晴らしいと思う。だけどNodeの以下の様なやり方は失敗していると思う。
例えば:
- 冗長なcallbackが必要になる
- callbackを全くとらない事もできる (その場合は失われる)
- 責任範囲外のエラーも取れる
- "error" イベントで複数のエラーを取ることができてしまう
- "error" イベントを取らないと全ての事が地獄に落ちる
- "error" ハンドラを要求するかどうかが不確実
- "error" ハンドラがとてもあいまい
- callbacksがクソ
Goでは僕のcodeが実行された時には中ではそれが既に起こっていて、そのステートメントを(別のライブラリから)再実行することはできない。
Nodeでは違う、ルーチンが完全に終了されるまで、突然あるライブラリが複数回僕が設定したcallbackを実行する事もあればhandlerがclearされずにコードの再実行を引き起こす事だってある。
こういうのってliveのproduction codeに対して何が起きてるのか推測していくのが信じられないくらい難しいんだよね。なんでそんなことに悩んでたんだ? 他の言語ではこの痛みを経験する事は無いんだぜ。
Nodeの未来へ
僕はNodeが今後もうまくやってくれるってことに期待してる、たくさんの人々がNode.jsに投資してて、しかもポテンシャルを持ってる。ただ僕はJoyentやコア開発チームがユーザビリティにフォーカスする必要があるなと思ってる、パフォーマンスはアプリケーションが小さいならあまり意味が無いし、デバッグ、リファクタ、開発はまだ難しいと思っている。
実際に4-5年間、僕らは "Error: getaddrinfo EADDRINFO" みたいな曖昧なエラーを受け取ってきた。当たり前の事だけど、システムのコアをエンハンスするのに集中してると、こういうミスを起こしやすい、何度も何度もこんなようなことをユーザーは表現してきたけど(良くなった)結果を見たことがない。
Streamは壊れてて、callbacksはいい方法じゃないし、エラーは曖昧だし、ツールも良くない、コミュニティの規約のようなものはある、しかしGoと比べると何か欠けてるように感じてしまうんだ。web サイトやAPIやプロトタイプを作るとか、そういうタスクがあるなら僕はNodeをまだ使うだろう。もしNodeがこれらの基礎的な問題を解決してくれるなら残ってる適切な自身の問題を解決するチャンスだと思う。ほかの解決策がより良いパフォーマンスとより良いユーザーフレンドリーを持ってきてくれるんだったら、ユーザビリティとパフォーマンスによる議論は飛ばなくなるだろう。
これは個人的な誰かを攻めているわけじゃない、たくさんの本当に才能ある人達とNodeで一緒に仕事してきて、それでも僕はもう興味を持てなくなったっていうただそれだけの事なんだ。そのコミュニティの一部としてすごく有用な時間を過ごしてきた、そしてマジでcoolな人たちにもたくさんあってきた。
この話の教訓は、あなた自身の小さな思いを滞らせちゃいけないってことだ! 他のものも見ようぜ、あなたがもう一度プログラミングを楽しむことができるかもしれない。たくさんのイケてるソリューションがそこにはあるし、僕の間違いはそういう他のソリューションを試してみるのを待ちすぎてたって事だ☺。
感想
visionmediaの話はここまでで、ここからは僕からの個人的な感想です。
まず、この話が出た時にみんな色んな反応してて、一番多かったのはexpressとかmochaとかどうなるの?ってことでした。
一応答えておくと、もうすでにExpressもmochaもvisionmediaは積極的なメンテは行っておらず、別な人がメンテナとして活動してます。
componentもjadeもstylusも別な人が積極的にメンテしてます。なので、一応言っておくと既に使っているライブラリがメンテされるかどうかはvisionmediaの去就とは無関係ですね。
彼はゼロから使いやすくて革新的なライブラリ、ツールを作るのが得意な人物ですが、たくさんライブラリ、ツールを作りすぎててメンテナンスはあまり得意じゃないです。なので、ある程度まで大きくなるとどっか行ってしまう傾向があります。その代わりちゃんと別なメンテナを立ててくれるので、ヘビーに使われてるライブラリに関しては新しいメンテナが面倒を見ることでしょう。
visionmediaの言葉にもあったけど、visionmediaは最近分散システムでの仕事をSegment.ioってところでやってます。
これまではLearnBoostやCloudUpってところでWebアプリケーションを高速に作れるようにするのが彼の仕事でした。その結果生まれたのがexpress, mocha, stylus, jadeでしょう。
Segment.ioっていう会社はGoogleAnaylticsとかmixpanelとかの解析ツールを統合するツールを作ってる会社で、環境が変わるとやっていることは変わるしそれに対して別なソリューションが必要になってくるというのが非常によくわかります。
そして少し前の彼がNode.jsでかなり攻めてるツールを作ってて、
- yal (Yet Another Logger, 分散環境でも使えるfluentd ライクなロギングライブラリ)
- punt (UDPでメッセージングを行うライブラリ)
- node-actorify (TCPでactorライクにメッセージングを行うライブラリ)
こんな感じで分散システムでの仕事を中心にしていることがわかります。
正直なんでこんなの作ってんだろって思ってたんですが、やっぱりSegment.ioで必要なツールを作ってるんでしょう。
なので分散環境でNode.jsを使って戦えるツールを作っていたんでしょうが、Goという新しいソリューションに惹かれてやってみたらものすごく良かったという事だと思います。
彼のようなコミュニティに多大な影響を与えてくれる優秀なエンジニアには一つのところにとどまっていてほしくないので、僕としては彼がGoで起こすであろうたくさんのイノベーションが楽しみです。
あと、socket.io meetupでGuillermoとNode学園祭の話をしてたら、TJ FontaineとTJ Hallowaychuk(visionmedia)の2人でNode vs Goのディスカッションさせるとかマジで熱くない??とか言われたんで、二人共日本に来てくれるかかなり怪しいんですがアプローチだけはかけてみたいと思います。
Goで作ったアプリをherokuに上げるときのメモ
この前herokuにgolangのアプリを初めてデプロイしたんですが、絶対に忘れそうな手順だったので、次にやるときにここ見ればできるようにメモっておきます。
手順
1. .godirを用意する。
プロジェクトのルートにgo get先を記した.godirを用意します。
$ echo "github.com/yosuke-furukawa/goweb-sample" > .godir
2. Procfileを用意する。
$ echo "web: goweb-sample" > Procfile
go buildで作られるバイナリを実行する形式で指定しておく。
3. goのbuildpackを入れてheroku createする
$ heroku create -b https://github.com/kr/heroku-buildpack-go.git goweb-sample
heroku-buildpack-goを使う。
とまぁこんな感じのステップで公開できる。
mgo から mongo labを使うとき
MONGOLAB_URIからDB名とDBのuriをパースしてアクセスさせる必要があります。
僕の適当実装はこちら。
package tweets import ( "labix.org/v2/mgo" "os" "regexp" ) func getSessionAndDB() (*mgo.Session, *mgo.Database) { envuri := os.Getenv("MONGOLAB_URI") dburi := "localhost" dbname := "tweets" if envuri != "" { uriRegExp := regexp.MustCompile(`(.*)\/(\w+)`) connects := uriRegExp.FindStringSubmatch(envuri) dburi = connects[0] dbname = connects[2] } session, err := mgo.Dial(dburi) session.SetSafe(&mgo.Safe{}) if err != nil { panic(err) } return session, session.DB(dbname) }
ちなみにgoの依存関係を固定するにはgodepを使うのがいいかもしれないと思ったけど、godep何故かsaveがうまく行かず断念。
依存関係系のツール多いから一旦まとめてみたい。
Go弱の会でgo vs node.jsのパフォーマンスベンチを取った話をしてきた。 #gojaku
Go弱の会に行ってきました。今回はnitrous.ioの話、revelの話、Dartの話がありました。
僕も少しだけしゃべったので、その話を含めてやったことをまとめます。
僕が話した内容
Go弱っぽく、Webアプリを作成し、そのベンチマークを取ってみました。
作ったアプリ
angularjs と goweb(RestFul server) と mgo (mongo driver) でツイッターっぽいアプリを作ってみました。
Goweb製(heroku) :
http://goweb-angular.herokuapp.com/app/#/
githubはここでアップしてます。
GitHub :
https://github.com/yosuke-furukawa/goweb-sample
ちなみに比較対象のkoa製の方もherokuにアップされてます。
Koa製 (heroku):
ベンチ結果
GET結果:

POST結果:

平均 :
| system | GET(reqs/sec) | POST(reqs/sec) |
|---|---|---|
| goweb(go) | 1829 | 1491 |
| koa(node.js) | 1332 | 470 |
結果として、GETにおいてもPOSTにおいてもgolangの勝利、下馬評通りの結果になりました。
とはいえ、node.jsもGETにおいてはgolangとそこまで変わらないレスポンスを返しているし、POSTには差はありますが、秒間470リクエストはそこまで悲観するような結果ではないかなと。
golangが速すぎますね。
おまけ
今回のベンチマークにはboomっていうgolang製のベンチマークツールを使いました。
boomはapache benchとやれることはほとんど同様なんですが、POST、PUT、DELETEが簡単に投げられるため、RESTFul APIと相性が良いので、積極的に使っていきたいですね。
boom 使い方まとめ
# install
$ go get github.com/rakyll/boom
# GET 100 requests 10 connects
$ boom -n 100 -c 10 http://0.0.0.0:9090/tweets
# POST 100 requests 10 connects
$ boom -n 1000 -c 100 -T "application/json" -m POST -d '{"body":"abc"}' http://0.0.0.0:9090/tweets
Go弱の会でgolangデビューしてきた。 #gojaku
やっとgolangデビューしました。
とは言っても、一回Google Developers Daysで一回入門して、なんか書いたことはあったんですが、その時以来、全く書いてなくて、再入門した形になります。
せっかくなので、Go弱の会でやったことをまとめます。
Go install
Goのインストールから始まりました。
本当はgoenvとか使うといい感じに入れられるのかもしれないんですが、まずはスタンダードなインストールをする事に。
公式サイトからインストールするかbrewを使うか。
一応、brewを使って入れてみました。
$ brew install go
※ go 1.1が入らない!とか思ったらbrew updateのし忘れだった事は秘密、忘れないようにしましょう。
$ brew update $ brew install go
忘れずに、GOROOT、GOPATHを設定。
以下のexportをzshrcに追記。
export GOROOT="/usr/local/Cellar/go/1.1.1" export GOPATH=$HOME/go export PATH=$PATH:$GOPATH/bin
- GOROOT : golangインストールルートフォルダ
- GOPATH : ユーザーのワーキングディレクトリ
GOROOTの記述をミスしたらgoの標準モジュールが読み込めなくなってしまったので、標準モジュールへのパスを通す意味もあるんだと思います。
GOPATHのbinにコンパイル後のバイナリができるので、そこにパスを通しておけば実行が簡単になります。
How to write go codeを基にgoの初歩的な使い方を学ぶ。
How to Write Go Code - The Go Programming Languageを基にgoのHello Worldを書いていきます。
まずは初期フォルダを作成します。
$ mkdir -p $GOPATH/src/github.com/user $ mkdir $GOPATH/src/github.com/user/hello
作成した $GOPATH/src/github.com/user/hello フォルダの下にhello.goファイルを作成し、内容を以下のようにしておきます。
package main import "fmt" func main() { fmt.Printf("Hello, world.\n") }
作成したら、goのバイナリを作成。
go install github.com/user/hello
go installはこれでもいいみたいですね。
$ cd $GOPATH/src/github.com/user/hello $ go install
go installに失敗する方は GOROOT と GOPATH の設定を再確認したほうが良いかもしれません。
GoのHello Worldを実行
$ $GOPATH/bin/hello Hello, world.
GOPATH/binをPATHに通している人は以下の方法でも実行出来ます。
$ hello Hello, world.
次にパッケージの記述方法を学ぶ
newmathフォルダを作成し、hello.goからそのモジュールを呼び出してみます。
$ mkdir $GOPATH/src/github.com/user/newmath
そのフォルダの下にsqrt.goを作成し、以下の内容にします。
// Package newmath is a trivial example package. package newmath // Sqrt returns an approximation to the square root of x. func Sqrt(x float64) float64 { z := 1.0 for i := 0; i < 1000; i++ { z -= (z*z - x) / (2 * z) } return z }
作成し終えたら、go buildでモジュール化します。
$ go build github.com/user/newmath
hello.goからnewmathモジュールを使用する。
package main import ( "fmt" "github.com/user/newmath" ) func main() { fmt.Printf("Hello, world. Sqrt(2) = %v\n", newmath.Sqrt(2)) }
書き終えたらgo install でhelloモジュールをinstallして再実行。
$ go install github.com/user/hello $ hello Hello, world. Sqrt(2) = 1.414213562373095
こんな感じで出ればモジュールの作成と利用は成功です。
とまぁこんな感じでHow to Write Go Code - The Go Programming Languageのステップを全て実施。
残りの時間で Tour of Goを実施。
残りの時間でTour of Goを実施。大体 #43のfibonacciを全員で解くところまでやって終了。
GoのFibonacciはこんな感じで解きました。
package main import "fmt" // fibonacci is a function that returns // a function that returns an int. func fibonacci() func() int { x, y := 0, 1; return func() int { x, y = x+y, x; return x; } } func main() { f := fibonacci() for i := 0; i < 10; i++ { fmt.Println(f()) } }