gengen

command module
v0.0.0-...-395b629 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 29, 2015 License: MIT Imports: 10 Imported by: 0

README

gengen - A generics code generator for Go

People often lament the lack of generics in Go, and use it as an excuse to dismiss the language. Yes, it is annoying that you often end up rewriting boilerplate. And yes, it is annoying that it's not possible to write a generic data structure that can be type-checked at compile time.

However, we can use Go's powerful source parsing and AST representation packages to build a program that can translate generically-defined code into specifically typed source code and compile that into our projects.

How to use it

Get the gengen tool:

$ go get github.com/alecthomas/gengen

Create a Go source file with a generic implementation. For example, this contrived linked-list implementation in list.go:

package list

//go:generate gengen -o list_string.go -r "List -> StringList" list.go string
import "github.com/alecthomas/gengen/generic"

type List struct {
    data generic.T
    next *List
}

func (l *List) Prepend(d generic.T) *List {
    n := &List{
        data: d,
        next: l,
    }

    return n
}

func (l *List) Contains(d generic.T) bool {
    if l == nil {
        return false
    }

    for i := l; i != nil; i = i.next {
        if i.data == d {
            return true
        }
    }

    return false
}

func (l *List) Data() generic.T {
    if l == nil {
        // Return the zero value for generic.T, whatever type it ends
        // up becoming
        var zero generic.T
        return zero
    }

    return l.data
}

generic.T is simply interface{}. This list implementation is perfectly valid Go code and you could use it as-is, asserting types at runtime.

However, you can generate a specifically typed version of this file by running it through gengen:

$ gengen list.go string > list_string.go

list_string.go will look like this:

package list

type List struct {
    data string
    next *List
}

func (l *List) Prepend(d string) *List {
    n := &List{
        data: d,
        next: l,
    }

    return n
}

func (l *List) Contains(d string) bool {
    if l == nil {
        return false
    }

    for i := l; i != nil; i = i.next {
        if i.data == d {
            return true
        }
    }

    return false
}

func (l *List) Data() string {
    if l == nil {
        // Return the zero value for generic.T, whatever type it ends
        // up becoming (in this example, string)
        var zero string
        return zero
    }

    return l.data
}

The generic package also defines generic.U and generic.V as additional generic types for cases when you want to support more than one type. Simply pass the additional types on the gengen command line:

$ gengen btree.go int string > btree_int_string.go

Caveats

Number of generic types

Currently gengen can support up to three generic types: generic.T, generic.U, and generic.V.

Naming

gengen does not currently do anything with naming of packages or types. This means that you can't compile both a generic version and a type specific version in the same package without modification. Fortunately, gofmt -r is perfect for this:

type MyGenericSlice []generic.T
$ gengen slice_generic.go string | gofmt -r 'MyGenericSlice -> MyStringSlice' > slice_string.go
type MyStringSlice []string
Using zero values

You may need to write code in a slightly different way than you normally would for interface{} in order to support a wide range of types. For instance, in our Data() method, note that we cannot simply return nil in the l == nil case because nil is not a valid value for primitive types like int, string, etc. Instead we instantiate a variable of our generic type but do not assign to it, ensuring that we always return the zero value for that type.

Equality

Checking for equality in a generic implementation can be tricky, and blindly checking if x == y often will not work as you'd hope. For things like slices, it will not even compile. If you need to check for equality, you might want to create an Equaler interface, like so:

type Equaler interface {
    Equal(other Equaler) bool
}

Define types that implement this interface:

type intWithEqual int

func (i intWithEqual) Equal(other Equaler) bool {
    if i2, ok := other.(intWithEqual); ok {
        return i == i2
    }
    return false
}
type Person struct {
    Name string
    SSN string
}

func (p *Person) Equal(other Equaler) bool {
    if p2, ok := other.(*Person); ok {
        return p.SSN == p2.SSN
    }
    return false
}

In your generic implementation, use the interface rather than comparing directly:

type MySlice []generic.T

func (s MySlice) Contains(e generic.T) bool {
    for _, e2 := range s {
        if e2.Equal(e) {
            return true
        }
    }
    return false
}

(Note that because generic.T does not embed the Equaler interface, this code won't compile without being run through gengen first.)

Finally, generate your implementations:

$ gengen myslice.go intWithEqual > myslice_int.go
$ gengen myslice.go *Person > myslice_person.go
Import and type naming inflexibility

The gengen tool looks through the source code for specific strings in order to replace them in the AST. Specifically, it looks for the import github.com/alecthomas/gengen/generic and the types generic.T, generic.U, and generic.V. If you need to change these, you will also have to change the gengen.go source.

Origins

I had been mulling the idea of a generics generator for a while, originally planning to use the text/template package. However, during a panel discussion at GopherCon in which generics inevitably came up, Rob Pike suggested manipulating the AST for Go. I began implementing this approach during the GopherCon Hack Day on 26 April 2014.

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis
examples
btree
Package b implements a B+tree.
Package b implements a B+tree.
package generic defines generic types the gengen tool replaces with provided, more specific types.
package generic defines generic types the gengen tool replaces with provided, more specific types.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL