Upload
masahiro-wakame
View
7.431
Download
1
Embed Size (px)
Citation preview
わかめ まさひろ @v vakame
TypeScript
Masahiro Wakame
DefinitelyTyped
appengine
photo from golang.org/doc/gopher/
encoding/json
play.golang.org/p/T9uO25D2xz
… type Game struct { ID int64 `json:"id" ̀ Title string `json:"title" ̀ Price int `json:"price" ̀ InDevelopment bool `json:"inDevelopment" ̀ ShippedAt time.Time `json:"shippedAt" ̀} func main() { game := &Game{ ID: 1, Title: "Splatoon", Price: 5700, InDevelopment: false, } b, _ := json.Marshal(game) fmt.Println(string(b))}
encoding/json… type Game struct { ID int64 `json:"id" ̀ Title string `json:"title" ̀ Price int `json:"price" ̀ InDevelopment bool `json:"inDevelopment" ̀ ShippedAt time.Time `json:"shippedAt" ̀} func main() { game := &Game{ ID: 1, Title: "Splatoon", Price: 5700, InDevelopment: false, } b, _ := json.Marshal(game) fmt.Println(string(b))}
手書き!? 正気か!?!?
めんどい “ 閉じるの忘れる typoる
jwg 作った//go:generate jwg -output model_json.go .package sample… // +jwgtype Game struct { ID int64 Title string Price int InDevelopment bool ShippedAt time.Time} func main() { game := &Game{ ID: 1, Title: "Splatoon", Price: 5700, InDevelopment: false, } jsonObj, _ := NewGameJsonBuilder().AddAll().Convert(game) b, _ := json.Marshal(jsonObj) fmt.Println(string(b))}
jwg = Json Wrapper Generator
//go:generate jwg -output model_json.go .package sample… // +jwgtype Game struct { ID int64 Title string Price int InDevelopment bool ShippedAt time.Time} func main() { game := &Game{ ID: 1, Title: "Splatoon", Price: 5700, InDevelopment: false, } jsonObj, _ := NewGameJsonBuilder().AddAll().Convert(game) b, _ := json.Marshal(jsonObj) fmt.Println(string(b))}
go generate 使う!
コメントにタグ書く(標準仕様などない!
生成したコード利用だ!
jwg 作った
自動生成!
type GameJson struct { ID int64 `json:"id,omitempty" ̀ Title string `json:"title,omitempty" ̀ Price int `json:"price,omitempty" ̀ InDevelopment bool `json:"inDevelopment,omitempty" ̀ ShippedAt time.Time `json:"shippedAt,omitempty" ̀}
楽
その他!type GameJson func (orig *GameJson) Convert() (*Game, error) type GameJsonBuilder func NewGameJsonBuilder() *GameJsonBuilder func (b *GameJsonBuilder) Add(info *GamePropertyInfo) *GameJsonBuilder func (b *GameJsonBuilder) AddAll() *GameJsonBuilder func (b *GameJsonBuilder) Convert(orig *Game) (*GameJson, error) func (b *GameJsonBuilder) ConvertList(orig []*Game) (GameJsonList, error) func (b *GameJsonBuilder) Marshal(orig *Game) ([]byte, error) func (b *GameJsonBuilder) Remove(info *GamePropertyInfo) *GameJsonBuilder type GameJsonList func (jsonList GameJsonList) Convert() ([]*Game, error) type GamePropertyDecoder type GamePropertyEncoder type GamePropertyInfo *JsonBuilder
*Property(De|En)coder *PropertyInfo
Web API作成用
play.golang.org/p/5wYA62Njvn
func (b *GameJsonBuilder) AddSite() *GameJsonBuilder { b.AddAll() b.Remove(b.ID) // IDは内部情報なのでいらない b.Price.Encoder = func(src *Game, dest *GameJson) error { if !src.InDevelopment { dest.Price = src.Price // 開発中じゃない時だけ価格を出すよ! } return nil } return b } func main() { game := &Game{ ID: 2, Title: "Secret of Yaba", Price: 9999, InDevelopment: true, } jsonObj, _ := NewGameJsonBuilder().AddSite().Convert(game) b, _ := json.Marshal(jsonObj) fmt.Println(string(b))}
{ "title":"Secret of Yaba”, “inDevelopment":true, “shippedAt":"0001-01-01T00:00:00Z" }
実行結果→
主張• コード生成 is 便利
• GoだとGenericsないしコード増えがち
• コンパイル時チェックの恩恵!
• 文字列で指定とか時代遅れだよね~
• 元コード→データ化→加工→生成!
• まずはソースコードを解析しないと!
正規表現で頑張る
http://play.golang.org/p/fsOl7CcjgB
package mainimport ( "fmt" "regexp") func main() { code := ` type Game struct { ID int64 Title string Price int InDevelopment bool ShippedAt time.Time } ` re:=regexp.MustCompile(`\s*type\s+([a-zA-Z]+)\s+struct\s+\{\n(?:\s*([a-zA-Z0-9]+)\s+([a-zA-Z0-9\.]+)\s*\n)*\s*}`) result := re.FindAllStringSubmatch(code,-1) fmt.Printf("%#v", result)}
ASTを活用する
• AST = Abstract Syntax Tree
• 本来はコンパイラ内部の中間表現
• ソースコードをデータとして使える!
• コード解析はライブラリに任せよう!
• 解析後のコード組立に専念できる!
Game struct → ASTtype Game struct { ID int64 Title string Price int InDevelopment bool ShippedAt time.Time}
ast = go/ast package
ast.GenDecl
Game struct → ASTtype Game struct { ID int64 Title string Price int InDevelopment bool ShippedAt time.Time}
ast = go/ast package
ast.GenDecl ast.TypeSpec
type ( A struct { Foo string } B struct { Bar string } )
こういう記法もある(怖い
Game struct → ASTtype Game struct { ID int64 Title string Price int InDevelopment bool ShippedAt time.Time}
ast = go/ast package
ast.GenDecl ast.TypeSpec ast.Ident
Game struct → ASTtype Game struct { ID int64 Title string Price int InDevelopment bool ShippedAt time.Time}
ast = go/ast package
ast.GenDecl ast.TypeSpec ast.Ident ast.StructType
Game struct → ASTtype Game struct { ID int64 Title string Price int InDevelopment bool ShippedAt time.Time}
ast = go/ast package
ast.GenDecl ast.TypeSpec ast.Ident ast.StructType ast.FieldList
Game struct → ASTtype Game struct { ID int64 Title string Price int InDevelopment bool ShippedAt time.Time}
ast = go/ast package
ast.GenDecl ast.TypeSpec ast.Ident ast.StructType ast.FieldList ast.Field type A struct {
Foo, Bar string }
こういう記法もある(怖い
Game struct → ASTtype Game struct { ID int64 Title string Price int InDevelopment bool ShippedAt time.Time}
ast = go/ast package
ast.GenDecl ast.TypeSpec ast.Ident ast.StructType ast.FieldList ast.Field ast.Ident
Game struct → ASTtype Game struct { ID int64 Title string Price int InDevelopment bool ShippedAt time.Time}
ast = go/ast package
ast.GenDecl ast.TypeSpec ast.Ident ast.StructType ast.FieldList ast.Field ast.Ident ast.Ident
ツール開発の流れ1. 処理対象(のstruct)を決める2. コード生成結果を手書きする
•名前を機械的に考えてつけよう3. 必要な俺形式のデータ構造を設計する
•ASTから取れるか?不足はないか?•型情報取れなくて辛いパターンある
4. 頑張ってAST取って変換して生成処理書く
Goコードの構造// generated by jwg -output model_json.go .; DO NOT EDITpackage sampleimport ( "encoding/json" "time" ) // for Gametype GameJson struct { ID int64 `json:"id,omitempty" ̀ Title string `json:"title,omitempty" ̀ Price int `json:"price,omitempty" ̀ InDevelopment bool `json:"inDevelopment,omitempty" ̀ ShippedAt time.Time `json:"shippedAt,omitempty" ̀}
PackageClauseImportDecl
TopLevelDecl
俺形式が必要な理由// generated by jwg -output model_json.go .; DO NOT EDITpackage sampleimport ( "encoding/json" "time" ) // for Gametype GameJson struct { ID int64 `json:"id,omitempty" ̀ Title string `json:"title,omitempty" ̀ Price int `json:"price,omitempty" ̀ InDevelopment bool `json:"inDevelopment,omitempty" ̀ ShippedAt time.Time `json:"shippedAt,omitempty" ̀}
正しいPackageClauseの生成には、
TopLevelDecl生成結果の把握が必要! etc..
jwgの場合// BuildStruct represents source code of assembling..type BuildSource struct { g *genbase.Generator pkg *genbase.PackageInfo typeInfos genbase.TypeInfos Structs []*BuildStruct} // BuildStruct represents struct of assembling..type BuildStruct struct { parent *BuildSource typeInfo *genbase.TypeInfo Fields []*BuildField} // BuildField represents field of BuildStruct.type BuildField struct { parent *BuildStruct fieldInfo *genbase.FieldInfo Name string Embed bool Tag *BuildTag} // BuildTag represents tag of BuildField.type BuildTag struct { field *BuildField Name string Ignore bool // e.g. Secret string `json:"-" ̀ DoNotEmit bool // e.g. Field int `json:",omitempty" ̀ String bool // e.g. Int64String int64 `json:",string" ̀}
genbaseのご紹介• 3つほどコード生成ツール作った
• 定形処理の存在に気がつく• AST読み込み
• 指定されたorタグ付きstructの収集
• import句の管理• コード組み立て・フォーマット• その他便利関数とかgithub.com/favclip/genbase
参考:typewriter
そして気合func (st *BuildStruct) emit(g *genbase.Generator) error { g.Printf("// for %s\n", st.Name()) // generate FooJson struct from Foo struct g.Printf("type %sJson struct {\n", st.Name()) for _, field := range st.Fields { if field.Tag.Ignore { continue } postfix := "" if field.WithJWG() { postfix = "Json" } tagString := field.Tag.TagString() if tagString != "" { tagString = fmt.Sprintf("`%s`", tagString) } if field.Embed { g.Printf("%s%s %s\n", field.fieldInfo.TypeName(), postfix, tagString) } else { g.Printf("%s %s%s %s\n", field.Name, field.fieldInfo.TypeName(), postfix, tagString) } } g.Printf("}\n\n") g.Printf("type %[1]sJsonList []*%[1]sJson\n\n", st.Name()) // generate property builder g.Printf("type %[1]sPropertyEncoder func(src *%[1]s, dest *%[1]sJson) error\n\n", st.Name()) g.Printf("type %[1]sPropertyDecoder func(src *%[1]sJson, dest *%[1]s) error\n\n", st.Name()) // generate property info g.Printf(` type %[1]sPropertyInfo struct { name string Encoder %[1]sPropertyEncoder Decoder %[1]sPropertyDecoder } `, st.Name()) // generate json builder g.Printf("type %sJsonBuilder struct {\n", st.Name())
↓ざっくり500行続く
Tips
• 埋め込みstructは敵
• 生成すべきコードがどんどん複雑に…
• fieldの型がstructだと絶望
• ASTだけでは型の詳細な情報がない• 生成コードないとコンパイル通らん
• Printfの %[1]s 記法マジ便利
I ♥ Pull Request
• よりGoらしい書き方できるよ!
• より効率の良い実装があるよ!
• Template使えよ!
• text/template は気に入らなかった…
• なんかないですかね?github.com/favclip
We are hiring!
•開発:テレビ朝日
• jwg, genbase 他 爆誕!
• http://www.favclip.com/
• appengine/go 開発者絶賛募集中!
疑問• ライブラリのリビジョン?
• jwg 非互換な変更していいのかしら
• embedしたstructのメソッド呼び奴
• 外側のstructがreceiverになってほし
• Generics欲しい気持ちが抑えられない