- 伊藤真彦
- 約 2,900 文字
- 200 View
目次
フューチャー夏休みの自由研究連載の2回目です。
TIG DXユニットの伊藤真彦です。
夏休みの自由研究として、GoPlusを調査しました。
この記事ではGoPlusを紹介し、簡単な内部構造を調査します。
はじめに
私は現在Goを書いていますが、前職ではRubyでのWeb開発がメインでした。
Rubyは型定義を書く必要がありません、端的に言うと書きやすさに重きを置いています。
Wikipediaにも…
開発者のまつもとゆきひろは、「Rubyの言語仕様策定において最も重視しているのはストレスなくプログラミングを楽しむことである (enjoy programming)」と述べている。
と書いてあることからも、その設計思想が伺えます。
一方Goはコンパイル言語としての高速実行と書きやすさの絶妙なバランスを攻めている印象です。
C#を書いた経験もあるので、型があるのが自然という感覚も理解できますが、Rubyに慣れ親しんだ身からすると型定義や配列の取り扱いが少々面倒に感じます。
そもそも大抵の言語はRubyよりお堅いのではないでしょうか。
(どちらの言語が優れている、という主張は勿論ありません。)
そんなRuby愛を引きずっている私にとって衝撃的なニュースがありました。
GoPlusの存在です。
GoPlusとは
Goのスーパーセットにあたるライブラリです。
- 静的型付言語である
- Goと完全な互換性を持ってる
- スクリプト言語のようなスタイルで、データサイエンスにおいてGoより高い可読性を実現している
と、READMEに書いてあります。
Cに対するC++のようなノリで命名されているようですね。
GoPlusの記述例
HelloWorld
println("Hello, world!") |
拡張子は.gopです。
この1行だけで動きます、つまりpackageの定義やmain関数の記述が不要です。
素晴らしいですね。
配列、マップの取り扱い
l := [1, 2, 3] |
特にスライス(可変長の配列型)の宣言の感じがRuby時代を思い出します。
同じことをGoでやると、このような感じでしょうか
package main |
余談ですが、サンプルコードのprintln関数もGoPlusで新たに定義されています。
本家GoのPrint系の関数より柔軟で、型を意識せずに使えるように拡張されています。
他にも多倍長整数型・多倍長浮動小数点数型のサポート、配列操作の拡張、エラーハンドリングの簡易的な記法の実装など便利な機能が多数用意されています。
GoPlusのインストール
GoPlusを実際に使用する方法を紹介します。go getコマンドでGOPATH配下にライブラリをインポートします。
go get github.com/qiniu/goplus |
GOPATH配下にgoplusディレクトリが生成されるので移動します。go installコマンドでパッケージをインストールします。
cd $GOPATH/src/github.com/qiniu/goplus |
インストールするとgopコマンドが利用できるようになります。
※執筆時点ではα版であるため、代わりに4種類のコマンドがインストールされます。
- qrun: Similar to gop run
- qfmt: Similar to gop fmt
- qexp: Similar to gop export
- qgo: Similar to gop go
試しにqrunコマンドを使用します。qrunはgop run、つまりGoのgo runに相当するファイル実行コマンドです。
拡張子がgopのファイルを実行できます。
実際にprintln("Hello, world!")1行だけが書かれたファイルを実行できました。
GoPlusをGoにインポート
GoPlusの拡張機能をGoのライブラリとしてインポートするような使い方も可能です。
package main |
GoPlusのハックを研究
仕事で使うかはともかくGoPlusは面白いライブラリだと感じました。
今回はGoPlusがどのような手法でGoを柔軟な言語に拡張しているのか調査します。
ようやく自由研究の始まりです。
qrunコマンドでファイルが実行できる仕組み
go runに相当するqrunコマンドを見てみましょう。
コマンドの実装はこちらです。
いたって普通のGoのスクリプトです。
つまり、go installコマンドでインストールしたGoの実装が各種コマンドの正体です。
qrunのmain関数内部で、拡張子gopのファイルを解析、実行することで、gopファイル本体にはmain関数や諸々のおまじないが不要になっていたんですね。
GoPlusは既に5年に渡り開発が行われています(initial commitが2015年でした)。
そのため複雑な実装が組みあがっていますが、頑張って要点だけでも解析してみます。
func main() { |
まずは標準ライブラリのflagを用いて、コマンドライン引数を格納しています。
つまりflag.Arg(0)で引数で渡したファイル名.gopが取得できます。
試しに何もファイル名を指定せずqrunコマンドのみを実行するとflag.NArg() < 1がtrueになり、実装の通りのエラーメッセージが出力されます。
target, _ := filepath.Abs(flag.Arg(0)) |
ファイル名にパスを組み合わせたtargetがparser.ParseGopFiles()に渡されます。
ここでgopファイルとしての解析が始まるようです。
cl.CallBuiltinOp = exec.CallBuiltinOp |
解析した結果の実行はNewBuilder、NewPackageなどの関数内部で行われています。
全てを解析しようとすると終わりが見えません、5年分の重みを感じます。
構文解析の仕組み
GoPlusスクリプトの構文解析はcl.NewPackage()で行われるようです。
func NewPackage(out exec.Builder, pkg *ast.Package, fset *token.FileSet, act PkgAct) (p *Package, err error) { |
上記のように複雑な分岐を経由しますが、各経路での前処理を終えたらcompileStmt()で実際に構文解析が行われます。
その先頭のcompileExprStmt()内部のcompileExpr()までトークンを解析して送り込みます。
func compileExpr(ctx *blockCtx, expr ast.Expr) func() { |
こちらのswitch文まで辿り着いたら、解析結果に応じたコンパイルが走ります。
興味があればqrunから構文解析までの処理の流れを詳しく探索してみてください、私はそろそろ限界です。
試しに解析結果が*ast.SliceExprだった場合の動き、compileSliceExpr()を見てみます。
func compileSliceExpr(ctx *blockCtx, v *ast.SliceExpr) func() { // x[i:j:k] |
独自の実装が山盛りですね…再帰的にcompileExpr()が呼び出されるところも迷宮ポイントを高めています。
キリがないので今回はこの辺りで調査を終了したいと思います。
GoPlusのこれから
GoPlusは執筆時点では月間90件に近いプルリクエストがマージされ、絶賛開発中の状態です。
α版を乗り越えgopコマンドが動き出す日は来るのか、乞うご期待です。
追記
記事の公開前日に確認したところ、バージョン0.7.1よりgopコマンドが採用されていました!
おめでとうGoPlus。
私は偉いので記事公開の1か月前に概ね書き終わっていたのですが、その間にもGoPlusは大きく進化していました。
追記執筆時点での最新の実装(v0.7.4)ではREPL(対話型インタプリタ)の実行機能が追加されたようです。
さらにはvscode用プラグインの開発もスタートしました。
今後の成長がますます楽しみになりました。