Generic Programming in Golang

gopher10th-small.jpg
Credit by Golang Official Blog

In many programming languages such as Java, Kotlin, Objective-C, and Swift, they support the Generic programming. But in Golang, it doesn’t have that kind of feature. What if we want to use this feature, how can we do? Let’s check the Interface in Golang. And how it can achieve the generic programming in Golang.

Let’s see the definition of Generic Programming from Wikipedia:

Generic programming is defined in Musser & Stepanov (1989) as follows, Generic programming centers around the idea of abstracting from concrete, efficient algorithms to obtain generic algorithms that can be combined with different data representations to produce a wide variety of useful software.

— Musser, David R.; Stepanov, Alexander A., Generic Programming[5]

The original idea describes that we design an algorithm to solve a problem, but we don’t care about the types of objects involved. We use a sorting algorithm to sort fruits from small sizes to large sizes. This algorithm should be able to apply on apples, oranges, guavas, and other different kinds of fruits. We don’t care about what kind of fruit is, we only focus on the size issue.

How other languages do generic programming?

Java

1
2
3
4
5
6
7
8
9
10
11
public class GenericFoo<T> {
private T foo;

public void setFoo(T foo) {
this.foo = foo;
}

public T getFoo() {
return foo;
}
}

Kotlin

1
2
3
4
5
6
7
8
class GenericFoo<T>() {
private var foo:T

fun setFoo(foo: T) {
this.foo = foo
}
fun getFoo(): T = this.foo
}

Objective-C

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@interface GenericFoo<ObjectType> : NSObject
- (void) setFoo: (ObjectType) foo;
- (ObjectType) getFoo;
@end

// implementation
@interface GenericFoo()
// some properties here
@end

@implementation GenericFoo
- (instancetype)init {
// init some properties here
}

- (void) setFoo: (id) foo {
...
}
- (id) getFoo {
...
}
@end

Swift

1
2
3
4
5
6
7
8
9
10
struct GenericFoo<T> {
var foo: T

mutating func setFoo(f: T) {
foo = f
}
mutating func getFoo() -> T {
return foo
}
}

What about Golang? Can Golang do the same thing as other programming languages do? Unfortunately, Golang doesn’t support generic programming because it does not have that syntax to do. That’s its drawback, but we still have another way to do a similar thing. The answer is Interface.

What’s Interface in Golang? Let’s see an example:

1
2
3
4
5
6
7
8
9
10
type geometry interface {
area() float64
perim() float64
}

func measure(g geometry) {
fmt.Println(g)
fmt.Println(g.area())
fmt.Println(g.perim())
}

So, we define an interface - geometry with two methods:

  1. area() // get the geometry area.
  2. perim() // get the perimeter of this geometry.

And a measure function that accepts a geometry interface type. How do we use that? You can create two different geometry objects.

1
2
3
4
5
6
7
type rect struct {
width, height float64
}

type circle struct {
radius float64
}

A tip here, when you implement all methods in an interface, then you are a type of that interface, which is so-called Duck typing the core concept of Golang Interface.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// duck typing
func (r rect) area() float64 {
return r.width * r.height
}

func (r rect) perim() float64 {
return 2*r.width + 2*r.height
}

func (c circle) area() float64 {
return math.Pi * c.radius * c.radius
}

func (c circle) perim() float64 {
return 2 * math.Pi * c.radius
}

That means these two objects are well-defined geometries. We can use both of them in measure function.

1
2
3
4
5
6
7
func main() {
r := rect{width: 3, height: 4}
c := circle{radius: 5}

measure(r)
measure(c)
}

Through the interface design, we can easily do the generic programming on Go. What’s another benefit to using the interface? The most significant benefit is that you can hide your implementation detail. Let’s check Sort.go source code:

1
2
3
4
5
6
7
8
9
10
11
12
// A type, typically a collection, that satisfies sort.Interface can be
// sorted by the routines in this package. The methods require that the
// elements of the collection be enumerated by an integer index.
type Interface interface {
// Len is the number of elements in the collection.
Len() int
// Less reports whether the element with
// index i should sort before the element with index j.
Less(i, j int) bool
// Swap swaps the elements with indexes i and j.
Swap(i, j int)
}

It defines three methods, Len(), Less() bool, Swap(). When you try to use Sort interface. Just do something like this:

1
2
3
4
5
6
7
8
// Insertion sort
func insertionSort(data Interface, a, b int) {
for i := a + 1; i < b; i++ {
for j := i; j > a && data.Less(j, j-1); j-- {
data.Swap(j, j-1)
}
}
}

You pass a data as an Interface; then we don’t need to care about what the real type of data is, we need to know: we can use the data to do Swap. Another interesting thing I want to mention is that the interface allows you to hook/intercept with a customized handler created by you to assemble with other data, which is so-called dynamic dispatch. Let’s see an example:

If you try to use http.Get to get some resources from server-side:

1
2
3
4
func main() {
resp, err := http.Get("http://www.sampleside.com/robots.txt")
...
}

What if we need to add an authentication token to the header for a request? You might try to use http client to do something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type header struct {
roundTrip http.RoundTripper
possibleHeader map[string]string
}

func (h header) RoundTrip(req *http.Request) (*http.Response, error) {
for k, v := range h.possibleHeader {
req.Header.Set(k, v)
}
return h.roundTrip.RoundTrip(req)
}


func main() {
var client = &http.Client{
Transport: header{
roundTrip: http.DefaultTransport,
possibleHeader: map[string]string{"Authorization": "Bearer this-is-a-token"},
},
}
response, _ := client.Get("http://www.sampleside.com/robots.txt")
}

The header implements the RoundTripper interface that only contains one method - RoundTrip. This interface gives us a chance to put authorization token into every request without manually add it into request header. That’s powerful.

The interface is a foundational feature in Golang, but some benefits build on the runtime that means performance cost. So we should use them carefully.

Happy coding. Enjoy!