6

I know that Go doesn't support templates or overloaded functions, but I'm wondering if there's any way to do some kind of generic programming for variadic functions anyway?

I have many functions such as these:

func (this Document) GetString(name string, defaults ...string) string {
    v, ok := this.GetValueFromDb(name)
    if !ok {
        if len(defaults) >= 1 {
            return defaults[0]
        } else {
            return ""
        }
    }
    return v.asString
}

func (this Document) GetInt(name string, defaults ...int) int {
    v, ok := this.GetValueFromDb(name)
    if !ok {
        if len(defaults) >= 1 {
            return defaults[0]
        } else {
            return 0
        }
    }
    return v.asInt
}

// etc. for many different types

Is there any way to do this without having so much redundant code?

blackgreen
  • 34,072
  • 23
  • 111
  • 129
laurent
  • 88,262
  • 77
  • 290
  • 428

3 Answers3

12

The most of what you can achieve is usage of interface{} type, something like this:

func (this Document) Get(name string, defaults ...interface{}) interface{} {
    v, ok := this.GetValueFromDb(name)
    if !ok {
        if len(defaults) >= 1 {
            return defaults[0]
        } else {
            return 0
        }
    }
    return v
}

GetValueFromDb function should also be tweaked to return interface{} value and not some wrapper like now.

Then in the client code you can do the following:

value := document.Get("index", 1).(int)  // Panics when the value is not int

or

value, ok := document.Get("index", 1).(int)  // ok is false if the value is not int

This will yield some runtime overhead though. I'd better stick with separate functions and try to restructure the code somehow.

blackgreen
  • 34,072
  • 23
  • 111
  • 129
Vladimir Matveev
  • 120,085
  • 34
  • 287
  • 296
2

Here's a working example of how you could change your code.

package main

import (
"fmt"
)

type Document struct{
    getSucceeds bool
}

func (d *Document) GetValueFromDb(name string) (interface{}, bool) {
    return 1, d.getSucceeds
}

func (this Document) Get(name string, def ...int) interface{} {
    v, ok := this.GetValueFromDb(name)
    if !ok {
        if len(def) >= 1 {
            return def[0]
        } else {
            return 0
        }
    }
    return v
}

func main() {
    d1 := Document{true}
    d2 := Document{false}
    
    var int1, int2 int
    
    int1 = d1.Get("foo", 2).(int)
    int2 = d2.Get("foo", 2).(int)
    
    fmt.Println(int1, int2)
}

Since you know what type you expect for the given name, you can write your Get method in a generic way, returning interface{}, and then assert the type at the call site. See the spec about type assertions.

There are different ways to emulate some aspects of generics in Go. There were lots of discussions on the mailing list. Often, there's a way to restructure code so it's less dependent on generics.

blackgreen
  • 34,072
  • 23
  • 111
  • 129
Thomas Kappler
  • 3,795
  • 1
  • 22
  • 21
0

In the client code you can do like this :

res := GetValue("name", 1, 2, 3)
// or
// res := GetValue("name", "one", "two", "three")

if value, ok := res.(int); ok {
    // process int return value
} else if value, ok := res.(string); ok {
    // process string return value
}

// or
// res.(type) expression only work in switch statement
// and 'res' variable's type have to be interface type
switch value := res.(type) {
case int:
    // process int return value
case string:
    // process string return value
}
mug896
  • 1,777
  • 1
  • 19
  • 17