Generative Programming in Go

Generative Programming in Go

Cd3d2cb2dadf5488935fe0ddaea7938a?s=128

monochromegane

June 21, 2015
Tweet

Transcript

  1. Generative Programming in Go - GoCon 2015 summer - @monochromegane

  2. MIYAKE Yusuke (@monochromegane)

  3. GMO Pepabo, Inc.

  4. ♕ 3 GitHub Awards Go ranking in Japan :)

  5. Generative Programming

  6. ੜ੒తͳ Programming

  7. Generator + Template parse generate Parser Your App Generated Code

    AST Code use
  8. Attention, please.

  9. ✘ Generics Meta programming Dynamic generation

  10. parse Parser AST Code Go provides

  11. Generator + Template generate Your App Generated Code use We

    implement
  12. argen ActiveRecord Generator

  13. GoͰDataMapperͰ͸ͳ͘ ActiveRecordϥΠΫʹ DBૢ࡞͢Δ

  14. by Generative programming

  15. ߏ଄ମʹ`+AR`ϚʔΧʔΛ͚ͭΔ //+AR type User struct { Id int Name string

    Age int }
  16. ੜ੒͢Δ $ argen main.go $ tree . !"" main.go #""

    main_gen.go
  17. ߏ଄ମʹARϥΠΫͳϝιου͕ ௥Ճ͞ΕΔ db, _ := sql.Open("sqlite3", "foo.db") Use(db) u :=

    User{Name: "test", Age: 20} u.Save() //// INSERT INTO users (name, age) VALUES (?, ?); [test 20] User{}.Where("name", "test").And("age", ">", 20).Query() //// SELECT users.id, users.name, users.age FROM users WHERE name = ? AND age > ?; [test 20]
  18. Generative Programming ࢖͍υίϩ

  19. Case.1 Generics

  20. ೚ҙͷߏ଄ମʹڞ௨ͷϝιου Λ࣋ͨͤͭͭɺݸʑͷܕΛѻ͏ var u User u = User{}.First() var h

    Hoge h = Hoge{}.First()
  21. ೚ҙͷߏ଄ମʹڞ௨ͷϝιου Λ࣋ͨͤͭͭɺݸʑͷܕΛѻ͏ var u User u = User{}.First() var h

    Hoge h = Hoge{}.First() • Ωϟετͨ͘͠ͳ͍ • Genericsͷग़൪
  22. ᵆᴸᴸᴸᴸᴸᴸᴸᴸᴸᴸᴸᴸᴸᵪᴸᴸᵊ ᴺwhy no generics golangɹ ᴺݕࡧᴺ ᵎᴸᴸᴸᴸᴸᴸᴸᴸᴸᴸᴸᴸᴸᵲᴸᴸᵒ

  23. Let’s generate !

  24. ߏ଄ମʹରͯ͠ݸʑͷܕΛѻ͏ ڞ௨ͷϝιουΛੜ੒

  25. func (m User) First() (*User, error) { return m.newRelation().First() }

    func (r *UserRelation) First() (*User, error) { return r.Order("id", "ASC").Limit(1).QueryRow() } func (r *UserRelation) QueryRow() (*User, error) { row := &User{} err := r.Relation.QueryRow( row.fieldPtrsByName( r.Relation.GetColumns())...) if err != nil { return nil, err } return row, nil }
  26. see also: clipperhouse/gen // +gen slice:"All" type Person struct {

    Name string Present bool } gang := PersonSlice { {"Alice", true}, {"Bob", false}, {"Carly", true}, } here := func(p Person) bool { return p.Present } gang.All(here) // => false, Bob didn't make it
  27. Case.2 Performance

  28. ϦϑϨΫγϣϯΛ࢖ͬͨ൚༻ੑ ͸ύϑΥʔϚϯεͱҾ͖׵͑ // Useful! but... var u User db.First(&u)

  29. ϕϯνϚʔΫ // read SomeFunc(s.F) SomeFunc(reflect.ValueOf(s).FieldByName("F").Int()) // write s.F = 100

    reflect.ValueOf(&s).Elem().FieldByName("F").SetInt(100) $ go test -benchmem -bench . • Mac OSX(10.10.3) • CPU: 2.5GHz Core i5(2Core) • Memory: 8GB • Go: 1.4.2
  30. ߏ଄ମϑΟʔϧυʹର͢ΔΞΫηε time Alloced Bytes Allocs read field 0.38 ns 0

    B 0 allocs reflect 246 ns 16 B 2 allocs write field 0.36 ns 0 B 0 allocs reflect 199 ns 8 B 1 allocs
  31. ෳ਺ͷߏ଄ମϑΟʔϧυʹର͢ΔΞΫηε time Alloced Bytes Allocs read field 0.36 ns 0

    B 0 allocs reflect 583 ns 88 B 2 allocs write field 0.37 ns 0 B 0 allocs reflect 277 ns 8 B 1 allocs
  32. Let’s generate !

  33. ߏ଄ମʹରͯ͠ϑΟʔϧυ৘ใ औಘ༻ϝιουΛੜ੒

  34. func (m *User) fieldValueByName(name string) interface{} { switch name {

    case "id", "users.id": return m.Id case "name", "users.name": return m.Name case "age", "users.age": return m.Age default: return "" } } func (m *User) fieldPtrByName(name string) interface{} { switch name { case "id", "users.id": return &m.Id case "name", "users.name": return &m.Name case "age", "users.age": return &m.Age default: return nil } }
  35. see also: pquerna/ffjson //go:generate ffjson $GOFILE type Foo struct {

    Bar string } f := Foo{"bar"} b, _ := json.Marshal(f) fmt.Printf("%s\n", string(b)) // => {"Bar":"bar"} https://github.com/pquerna/ffjson
  36. Case.3 DSL

  37. ೚ҙͷߏ଄ମͷڞ௨ϝιου͸ هड़಺༰͕৑௕ʹͳΓ͕ͪ // far from the DSL... func (u *User)

    BeforeUpdate() (err error) { if u.Name != "abc"{ return fmt.Errorf("%s is invalid name.", u.Name) } if u.Age > 20 { return fmt.Errorf("%d is invalid age.", u.Age) } return nil }
  38. Let’s generate !

  39. ϝιου໊/ίϝϯτ/λά͔Β ఆٛΛิ׬ͯ͠ੜ੒

  40. func (u User) validatesName() ar.Rule { return ar.MakeRule().Format().With("abc").OnCreate() } func

    (u User) validatesAge() ar.Rule { return ar.MakeRule().Numericality().OnlyInteger().LessThan(20) } rules := map[string]*ar.Validation{ "name": m.validatesName().Rule(), "age": m.validatesAge().Rule(), } for name, rule := range rules { if ok, errs := ar.NewValidator(rule).On(on).IsValid(m.fieldValueByName(name)); !ok { result = false errors.SetErrors(name, errs) } } >
  41. Case. Bad

  42. ͳΜͰ΋Generate

  43. • ϝιουͷڞ௨Խɺଟଶੑͷ࣮ݱͳͲ͸ߏ଄ମͷຒࠐ (ҕৡ)΍interfaceΛ࢖ͬͯ΍Δ΂͖ • http://go-talks.appspot.com/github.com/lestrrat/ go-slides/2014-yapcasia-go-for-perl-mongers/ main.slide#27 • http://blog.monochromegane.com/blog/ 2014/03/23/struct-implementaion-patterns-in-

    golang/ • Generate͸࣮૷ଆ΋ར༻ଆ΋Ұख͔͔ؒΔ෼ɺෳࡶ ʹͳΓ͕ͪɻGenerateͰ΍Δཧ༝͕આ໌Ͱ͖ͳ͍ͷ Ͱ͋Ε͹࢖͏΂͖Ͱ͸ͳ͍
  44. Tips

  45. ίϚϯυϥΠϯ/go generate ͷ྆ํʹରԠ͢Δ

  46. from := os.Getenv("GOFILE") if from == "" { if len(args)

    > 0 { from = args[0] } else { os.Exit(1) } } $ argen main.go // go:generate argen
  47. defineΛ࢖ͬͯ templateΛ෼ׂ؅ཧ͢Δ

  48. {{template "Relation" .}} {{define "Relation"}} type {{.Name}}Relation struct { src

    *{{.Name}} *ar.Relation } {{end}} func (t Template) toDefine() string { return fmt.Sprintf("{{define \"%s\"}}%s{{end}}\n", t.Name, t.Text) } var relation = &Template{ Name: "Relation", Text: ` type {{.Name}}Relation struct { src *{{.Name}} *ar.Relation }`
  49. templateͰ ࣗಈ੔ܗͱ ΦʔτΠϯϙʔτ

  50. func writeWithFormat(file, template string, structs structs) ([]byte, error) { var

    b bytes.Buffer w := bufio.NewWriter(&b) write(w, template, structs) w.Flush() formatted, err := imports.Process(file, b.Bytes(), nil) if err != nil { return nil, err } return formatted, nil } func write(w io.Writer, tplText string, structs structs) error { t := template.New("t") t.Funcs(template.FuncMap{}) tpl := template.Must(t.Parse(tplText)) if err := tpl.Execute(w, structs); err != nil { return err } return nil }
  51. ςετ͸ੜ੒෺ʹ ରͯ͠ߦ͏

  52. //go:generate go run ../cmd/argen/main.go package tests import "github.com/monochromegane/argen" //+AR type

    User struct { Name string Age int } $ go generate && go test ./...
  53. એ఻ ϖύϘͰ͸ΤϯδχΞΛืू͍ͯ͠·͢ɻ ڞʹαʔϏεΛੜΈग़͠ҭͯͯ͘ΕΔ৽͍͠஥ؒ Λ଴͍ͬͯ·͢ɻ http://pepabo.com/recruit/career/engineer/

  54. ͓ΘΓ