Skip to content

Latest commit

 

History

History
219 lines (160 loc) · 9.12 KB

Contributing.md

File metadata and controls

219 lines (160 loc) · 9.12 KB

Contributing

Introduction

Thank you so much for considering to contribute. I don't have time to contribute myself, so I appreciate the help to maintain and build this project out further. I will be here to discuss your issue, help with designing a solution and review your code. I will do my best to guide you, whatever your skill level.

In all cases please open an issue to discuss your idea or issue, before doing any work. I don't have time to do the work myself, so don't worry about losing the chance to implement your idea.

If it is your first time contributing and don't have an idea of your own, then you can start with the list of good first issues

Contributing a new Function or Plugin

This project is built to support the generation of new types of functions and only that. A good function idea, is typically a function with parametric types, where the type of the function depends on the input types. Even though some great ideas come from Haskell and Category Theory, this is not a requirement. We are also looking for functions that express repeated patterns in your Go code, that you would prefer to generate over copying and will help with maintainability and/or speed.

Boilerplate

  • Start by copying and renaming a plugin in the plugin folder, that you find to most closely represent your idea.
  • Rename the package and update all comments to reflect your function name.
  • Put your name in the Copyright of the plugin.
  • Add your plugin to the list of plugins in main.go
  • Add your function to the Readme.md, like all the others
  • Add an example, see Contributing Examples

Now that all the plumbing is done you are your going to start by implementing your NewPlugin function, which is done with a helper function from the derive package:

func NewPlugin() derive.Plugin {
	return derive.NewPlugin("myfunctionname", "deriveMyFunctionName", New)
}

Next your New function that is referenced in derive.NewPlugin needs to have the following signature and return a derive.Generator

func New(typesMap derive.TypesMap, p derive.Printer, deps map[string]derive.Dependency) derive.Generator {

}

The Generator interface has two methods that you need to implement:

  • Add
  • Generate
type Generator interface {
	TypesMap
	Add(name string, typs []types.Type) (string, error)
	Generate(typs []types.Type) error
}

It also requires you to implement derive.TypesMap, which is luckily passed to you in New, so you can simply embed it in your implemention of the Generator.

You also want to say the Printer as this will be useful for printing your function to a file later.

If you require any imports, like the math package in this example, you should also initialize and save them for use in the New function.

Finally if your plugin requires code to generated by another plugin, in this example deriveMin, then you can also save them here.

func New(typesMap derive.TypesMap, p derive.Printer, deps map[string]derive.Dependency) derive.Generator {
  return &gen{
    TypesMap: typesMap,
    printer:  p,
    mathPkg:  p.NewImport("math", "math"),
		min:      deps["min"],
  }
}

type gen struct {
  derive.TypesMap
  printer derive.Printer
  mathPkg derive.Import
	min     derive.Dependency
}

Typecheck

The Add gives you a function name and a list of input argument types that look like they should result in the generation of a function. Your plugin's responsibility is to return either an error for illegal input types or a function name that is unique for the given input types.

So you can start by doing some type checks:

func (g *gen) Add(name string, typs []types.Type) (string, error) {
  // if we expect two arguments
	if len(typs) != 2 {
		return "", fmt.Errorf("%s does not have two arguments", name)
	}
  // if we expect that the first parameter is a function
  sig, ok := typs[0].(*types.Signature)
	if !ok {
		return "", fmt.Errorf("%s, the first argument, %s, is not of type function", name, g.TypeString(typs[0]))
	}

And then when you are happy that this will result in the successful generation of your function you need to return the function name. Luckily a helper function is provided for this in the TypesMap:

SetFuncName(name string, typs ...types.Type) (newName string, err error)

SetFuncName takes the function name provided by the user and the input types that make this function unique. This can be the original types passed in by the user, or simply the single type that makes this function unique.

For example: deriveAny(func (T) bool, []T) bool only requires T and does not need to know that the first argument is a function or that the section argument is a slice.

func (g *gen) Add(name string, typs []types.Type) (string, error) {
  ...
  return g.SetFuncName(name, elemTyp)
}

In other cases it is fine to use the original types, if all the information is required:

func (g *gen) Add(name string, typs []types.Type) (string, error) {
  ...
  return g.SetFuncName(name, typs...)
}

Generate

Now we finally get to the generation of the function:

Generate(typs []types.Type) error

The generate function gets the types that you passed to SetFuncName, which means they are already type checked and simplified, to make generation hopefully error free. But it is still possible to return an error.

func (g *gen) Generate(typs []types.Type) error {
  // Save g.printer as p for less typing
	p := g.printer
  // Tell TypesMap that you are generating this function.
	g.Generating(typs...)
  // Get the function name for the function your are about to generate.
  funcName := g.GetFuncName(typs...)
  // TypeString prints a type as a string.
	firstArgTypStr := g.TypeString(typs[0])
  // Always start with a space, we want your generated code to be perfectly gofmted, so that we save gofmt the effort and optimize the speed of our code generation.
	p.P("")
  // Start with a comment, because we want our generated code to look like non generated code that a user wrote.
	p.P("// %s ...", funcName)
	p.P("func %s(arg1 %s, ...) ... {", funcName, firstArgTypStr, ...)
	p.In()
	...
	p.Out()
	p.P("}")
	return nil
}

Test

Next you want to add a test. Usually this is done in the test/normal folder. Create a new test file for your plugin and write a test for an example use case where you use your generated function. Finally run make test. When all of this is working, please create a pull request. This is already a good time to get feedback, before creating an example.

Don't Repeat Yourself

The DRY principle is great in programming, but in Go we violate this rule more often than in other languages, because of the lack of generics. Even without this limitation it is still a balance and not every function is a great library function. Having said that the derive library contains quite a few functions, to help with generating code. Some example include:

  • Zero returns the zero value as a string, for a given type.
  • Fields is useful for generating code that operates on structs.
  • TypesMap is the go to for anything that requires more context.
  • IsError returns whether a type implements the Error interface.

Contributing Examples

We can always use better examples. Contributing examples for a plugin can be done by creating a folder if one does not already exists in the example/plugin folder.

First create your example go file foldername.go with your example code.

Please do not use words like foo, bar and MyStruct. Examples are more useful if they are as close as possible to actual use cases. Yes some current examples violate these rules, contributions that replace these examples will be highly appreciated.

Next create a Readme.md in the folder with the following content:

The <functionName> function ...

```go

```

goderive will generate the following code:

```go

```

The go blocks will be populated with your example code and the generated code using the following command:

cd .. # goderive/example
make

Finally go and add a link (if one does not already exist) in the top level Readme.md of the project.

You are ready to submit a pull request.