Skip to content

Instantly share code, notes, and snippets.

@l0k18
Created April 19, 2018 10:43
Show Gist options
  • Save l0k18/61abbfdf2444a48c70a4e68208a95f3e to your computer and use it in GitHub Desktop.
Save l0k18/61abbfdf2444a48c70a4e68208a95f3e to your computer and use it in GitHub Desktop.
Parametric Polymorphism in Go idiom
# Parametric Polymorphism in Go idiom
For some reason, there does not exist a super simple example of how this is done in Golang, but it's completely doable, and gets you all the benefits of abstract parent types which you want to operate on multiple types of data, such as for example a binary tree. All the functions to navigate and add and remove nodes can be generalised and only a small subset of functions must be written to handle the specific data type.
First, the interface stuff:
```go
package parametric
// Data - the type here doesn't really matter, but you can't use interface{} and then create a function for it
type Data uint32
type data interface {
New() Data
IsNull() bool
IsEqual(interface{}) bool
IsGreater(interface{}) bool
isLesser(interface{}) bool
}
// Next, all the functions need to have dummy implementations
// New - create a new abstract type
func (d *Data) New() Data {
return nil
}
// IsNull - check if a data is zero
func (d *Data) IsNull() bool {
return true
}
// IsEqual - check if a data equal to another data
func (d *Data) IsEqual(s Data) bool {
return true
}
// IsGreater - check if data is greater than another data
func (d *Data) IsGreater(s Data) bool {
return true
}
// IsLesser - check if data is greater than another data
func (d *Data) IsGreater(s Data) bool {
return true
}
```
This is the contents of your primary, abstract data type library. Next we need the implementation. For fun we will use String.
```go
package datastring
import "github.com/path/to/other/file"
type StringData
// New - create a new abstract type
func (d *StringData) New() StringData {
var s String
return s
}
// IsNull - check if a data is zero
func (d *StringData) IsNull() bool {
return d == ""
}
// IsEqual - check if a data equal to another data
func (d *StringData) IsEqual(s Data) bool {
return d == s
}
// IsGreater - check if data is greater than another data
func (d *StringData) IsGreater(s Data) bool {
return d > s
}
// IsLesser - check if data is greater than another data
func (d *StringData) IsGreater(s Data) bool {
return d < s
}
```
The important things to note are: In the interface, our replaceable datatype is `interface{}` and the signature, that ensures that your replacement function overrides the generic dummy functions is:
`func (x *ConcreteDatatype) FunctionName(p Parameters) (returntype)`
The first part binds the polymorphic new type to the defined type (it can also be a type struct), the functionname must match, the parameters must match (you don't have to make all parameters in functions replaced, you can leave them the same, define them or not, in the original interface, but presumably at least one of them you will set to the implementation type/struct, and the last part, the return type, same deal - anything defined as `interface{}` in the top file can be replaced with anything else, and anything that is concretely specified must also match the type.
It took me a long time to learn how to do this, but boy I am glad I did. Polymorphism is probably *the* most useful thing in OOP languages, after the obviously useful declarative syntax that lets you summarise the behaviour of a data type. Go lets you do both, with a slightly unfamiliar structure. In theory you probably can do multiple level inheritance as well. But the above concise example will help you understand exactly how you can parlay data models from object oriented programming languages into Go, while retaining the beautiful type safety and extensive compile time checks that save you from having to do loads of testing by eliminating all the common gotchas you may encounter.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment