v2 go get -u github.com/taylorchu/generic/cmd/gorewrite
v1 (deprecated) go get -u github.com/taylorchu/generic/cmd/generic
This is an experiment to enable generics with code generation in the most elegant way.
You can create a package like this. Note that Type and TypeQueue are type placeholders.
package queue
type Type string
// TypeQueue represents a queue of Type types.
type TypeQueue struct {
items []Type
}
// New makes a new empty Type queue.
func New() *TypeQueue {
return &TypeQueue{items: make([]Type, 0)}
}
// Enq adds an item to the queue.
func (q *TypeQueue) Enq(obj Type) *TypeQueue {
q.items = append(q.items, obj)
return q
}
// Deq removes and returns the next item in the queue.
func (q *TypeQueue) Deq() Type {
obj := q.items[0]
q.items = q.items[1:]
return obj
}
// Len gets the current number of Type items in the queue.
func (q *TypeQueue) Len() int {
return len(q.items)
}Add rewrite config in GoRewrite.yaml, and run gorewrite:
spec:
- name: result
import: github.com/YourName/queue
typeMap:
Type:
expr: int64
TypeQueue:
expr: FIFOThe output is saved to $PWD/result/.
package result
type FIFO struct {
items []int64
}
func New() *FIFO {
return &FIFO{items: make([]int64, 0)}
}
func (q *FIFO) Enq(obj int64) *FIFO {
q.items = append(q.items, obj)
return q
}
func (q *FIFO) Deq() int64 {
obj := q.items[0]
q.items = q.items[1:]
return obj
}
func (q *FIFO) Len() int {
return len(q.items)
}The yaml config contains multiple rewrite specs.
spec[*].name(string): unique identifier the spec. It is a path to the output, and used as package name if the spec is not local.spec[*].local(bool): true if the spec is local. If the spec is local, the output will be saved in$PWDinstead of a new package relative to$PWD. All the top level identifiers and the filename will also be prefixed withspec[*].nameto avoid conflicts.spec[*].typeMap(map): type mappings used to replace placeholders. The key is type placeholder. The valueexprcan be any go expression. Ifexprreferences any other packages, all those packages need to be listed inimport.
spec:
- name: result
local: true
import: github.com/YourName/queue
typeMap:
Type:
expr: test.Box
import:
- github/YourName/testThis post summarizes the current state in go to "simulate" generics.
Type-checking and ast-based replacement ensure that the tool doesn't generate invalid code even you or the tool make mistakes, and rewrites identifiers in cases that it shouldn't.
type TypeXXX int32
- It provides a namespace for replaceable types.
- Knowing that this type might be replaced, package creator can still write go-testable code with a concrete type.
- It can express meaning. For example,
TypeQueueshows that it is a queue.
- This tool tries NOT to apply any restriction for package creator except that any TypeXXX might be rewritten. Package creator has full flexibility to write normal go code.
- It is common to distribute go code at package-level.
Comments in go ast are free-floating, so they are hard to work with. Hopefully it is fixed in the near future.
The spec name should start with internal/. For example, internal/queue.
When the go command sees an import of a package with internal in its path, it verifies that the package doing the import is within the tree rooted at the parent of the internal directory. For example, a package .../a/b/c/internal/d/e/f can be imported only by code in the directory tree rooted at .../a/b/c. It cannot be imported by code in .../a/b/g or in any other repository.
See Internal packages.
Spec name is essentially go package path, and the base name of this package path is a package name.
Good package names are short and clear. They are lower case, with no under_scores or mixedCaps.
See Package names.
