Upgrade to Pro — share decks privately, control downloads, hide ads and more …

GoSF Oct. '17 - Code Generation

Tejas Manohar
October 19, 2017
80

GoSF Oct. '17 - Code Generation

Tejas Manohar

October 19, 2017
Tweet

Transcript

  1. CODE GENERATION $ whoami ▸ Tejas Manohar ▸ tejasmanohar on

    GitHub, Twitter, etc. ▸ Software Engineer at Segment
  2. CODE GENERATION WHAT'S THIS TALK ABOUT? ▸ Why generate code?

    ▸ How does code generation work? ▸ How do I generate code?
  3. CODE GENERATION AT SEGMENT, WE USE RPC ▸ RPC allows

    you to call func()'s across a network ▸ Your RPC protocol abstracts how ▸ Go has a pluggable implementation in net/rpc
  4. CODE GENERATION DEFINE A METHOD ON THE SERVER... func (s

    *Service) DoThing(t Task) (Result, error) { // ... }
  5. CODE GENERATION THEN, JUST CALL IT FROM THE CLIENT... result,

    err := DoSomething(Task{}) if err != nil { // ... } // use result
  6. CODE GENERATION SADLY, THE DEFAULT CLIENT LOOKS LIKE THIS... ▸

    Method name as a string ▸ Arguments as an interface{} ▸ Response as an interface{} that must be a pointer err := client.Call("Service.Method", Req{}, &Res{})
  7. CODE GENERATION WHAT'S WRONG? ▸ interface{}?!? ▸ Types are great!

    ▸ Method names as strings? rpc.Call("MySevice.SaveReport", ..., ...)
  8. CODE GENERATION STATUS QUO: WRITE WRAPPERS BY HAND func (c

    *Client) Create(req *CreateRequest) (*CreateResponse, error) { res := &CreateResponse{} err := c.rpc.Call("Reports.Create", req, res) return res, err } func (c *Client) Update(req *UpdateRequest) (*UpdateResponse, error) { res := &UpdateResponse{} err := c.rpc.Call("Reports.Update", req, res) return res, err } func (c *Client) Delete(req *DeleteRequest) (*DeleteResponse, error) { res := &DeleteResponse{} err := c.rpc.Call("Reports.Delete", req, res) return res, err }
  9. CODE GENERATION SOUNDS EASY! WHAT'S WRONG? ▸ Error-prone ▸ Bikeshedding

    ▸ Tedious ▸ Programmers tend to get bored easily :')
  10. CODE GENERATION THE END RESULT $ glue -name=Service -service=Math -stdout

    package client import "github.com/segmentio/glue/example/stl/math" type Math struct { RPC Client } func (c *Math) Sum(args math.SumArg) (*math.SumReply, error) { reply := new(math.SumReply) err := c.RPC.Call("Math.Sum", args, reply) return reply, err }
  11. CODE GENERATION HOW DOES GO SEE FIBONACCI? func fib(n int)

    int { if n == 0 { return 0 } else if n == 1 { return 1 } else { return fib(n-1) + fib(n-2) } }
  12. CODE GENERATION FIRST, COMES THE LEXER ▸ Lexer converts source

    code to "tokens" ▸ "tokens" are series of meaningful characters ▸ Disregards whitespace and fluff
  13. CODE GENERATION THEN, COMES THE PARSER ▸ Parser translates tokens

    to a syntax tree ▸ "Abstract Syntax Tree", abbrev. AST ▸ AST > Tokens > Code ▸ AST is a logical representation ("abstract") ▸ Disregards syntax
  14. CODE GENERATION GO MAKES PARSING CODE REALLY EASY ▸ go/build

    returns metadata about source code ▸ go/scanner and go/parser parse code to AST ▸ go/ast helps you walk and modify AST ▸ go/types makes type-checking easy ▸ x/tools/go/loader makes loading code easy
  15. CODE GENERATION FIRST, LOAD THE CODE import "golang.org/x/tools/go/loader" var conf

    loader.Config conf.Import("github.com/my/rpc/service") prgm, err := conf.Load()
  16. CODE GENERATION WRITE A VISITOR func (v *Visitor) Visit(node ast.Node)

    ast.Visitor { switch n := node.(type) { case nil: return nil case *ast.TypeSpec: p.visitType(n) } return p }
  17. CODE GENERATION "WALK" THROUGH THE CODE func (v *Visitor) Walk()

    { for _, package := range prgm.InitialPackages() { for _, file := range p.pkg.Files { ast.Walk(v, file) } } }
  18. CODE GENERATION FIND WHAT YOU NEED namedType := p.pkg.Info.ObjectOf(ts.Name).Type().(*types.Named) for

    i := 0; i < namedType.NumMethods(); i++ { method := namedType.Method(i) if p.provider.IsSuitableMethod(method) { recv := namedType.Obj().Name() p.methods[recv] = append(p.methods[recv], method) } }
  19. CODE GENERATION EXTRACT WHAT YOU WANT func (p *Provider) GetArgType(f

    *types.Func) types.Type { signature := f.Type().(*types.Signature) params := signature.Params() arg := params.At(0) return internal.Dereference(arg.Type()) } func (t *Arith) Multiply(args *Args, reply *int) error { *reply = args.A * args.B return nil }
  20. CODE GENERATION GENERATE OUTPUT USING TEXT/TEMPLATE type {{ .Service }}

    struct { RPC client.Client } {{ range .Methods }} func (c *{{ $.Service }}) {{ .Name }}(args {{ .ArgType }}) (*{{ .ReplyType }}, error) { reply := new({{ .ReplyType }}) err := c.RPC.Call("{{ $.Service }}.{{ .Name }}", args, reply) return reply, err } {{ end }}