Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Allowing for a variable number of return values in method declaration

Tags:

go

I have a function that solves the problem of Go not allowing for the setting of default values in method declarations. I want to make it just a little bit better by allowing for a variable number of return variables. I understand that I can allow for an array of interfaces as a return type and then create an interface array with all the variables to return, like this:

func SetParams(params []interface{}, args ...interface{}) (...[]interface{}) {
    var values []interface{}
    for i := range params {
            var value interface{}
            paramType := reflect.TypeOf(params[i])
            if len(args) < (i + 1) {
                    value = params[i]
            } else {
                    argType := reflect.TypeOf(args[i])
                    if paramType != argType {
                            value = params[i]
                    }
                    value = args[i]
            }
            values = append(values, value)
    }

    return values
}

This is an example of a method you want to define default values for. You build it as a variadic function (allowing a variable number of parameters) and then define the default values of the specific params you are looking for inside the function instead of in the declaration line.

func DoSomething(args ...interface{}) {
    //setup default values
    str := "default string 1"
    num := 1
    str2 := "default string 2"


    //this is fine
    values := SetParams([]interface{str, num, str2}, args)
    str = values[0].(string)
    num = values[1].(int)
    str = values[2].(string)


    //I'd rather support this
    str, num, str2 = SetParams(params, args)
}

I understand that

[]interface{str, num, str2}

in the above example is not syntactically correct. I did it that way to simplify my post. But, it represents another function that builds the array of interfaces.

I would like to support this:

str, num, str2 = SetParams(params, args)

instead of having to do this:

values := SetParams([]interface{str, num, str2}, args)
str = values[0].(string)
num = values[1].(int)
str = values[2].(string)

Any advice? Help?

like image 673
jrkt Avatar asked Mar 11 '23 07:03

jrkt


1 Answers

Please don't write horrible (and ineffective due to reflect) code to solve nonexistent problem. As was indicated in comments, turning a language into one of your previous languages is indeed compelling after a switch, but this is counterproductive.

Instead, it's better to work with the idioms and approaches and best practices the language provides -- even if you don't like them (yet, maybe).

For this particular case you can roll like this:

  • Make the function which wants to accept a list of parameters with default values accept a single value of a custom struct type.

  • For a start, any variable of such type, when allocated, has all its fields initialized with the so-called "zero values" appropriate to their respective types.

    If that's enough, you can stop there: you will be able to pass values of your struct type to your functions by producing them via literals right at the call site -- initializing only the fields you need.

    Otherwise have pre-/post- processing code which would provide your own "zero values" for the fields you need.

Update on 2016-08-29:

Using a struct type to simulate optional parameters using its fields being assigned default values which happen to be Go's native zero values for their respective data types:

package main

import (
    "fmt"
)

type params struct {
    foo string
    bar int
    baz float32
}

func myfun(params params) {
    fmt.Printf("%#v\n", params)
}

func main() {
    myfun(params{})
    myfun(params{bar: 42})
    myfun(params{foo: "xyzzy", baz: 0.3e-2})
}

outputs:

main.params{foo:"", bar:0, baz:0}
main.params{foo:"", bar:42, baz:0}
main.params{foo:"xyzzy", bar:0, baz:0.003}

As you can see, Go initializes the fields of our params type with the zero values appropriate to their respective types unless we specify our own values when we define our literals.

Playground link.

Providing default values which are not Go-native zero values for the fields of our custom type can be done by either pre- or post-processing the user-submitted value of a compound type.

Post-processing:

package main

import (
    "fmt"
)

type params struct {
    foo string
    bar int
    baz float32
}

func (pp *params) setDefaults() {
    if pp.foo == "" {
        pp.foo = "ahem"
    }
    if pp.bar == 0 {
        pp.bar = -3
    }
    if pp.baz == 0 { // Can't really do this to FP numbers; for demonstration purposes only
        pp.baz = 0.5
    }
}

func myfun(params params) {
    params.setDefaults()
    fmt.Printf("%#v\n", params)
}

func main() {
    myfun(params{})
    myfun(params{bar: 42})
    myfun(params{foo: "xyzzy", baz: 0.3e-2})
}

outputs:

main.params{foo:"ahem", bar:-3, baz:0.5}
main.params{foo:"ahem", bar:42, baz:0.5}
main.params{foo:"xyzzy", bar:-3, baz:0.003}

Playground link.

Pre-processing amounts to creating a "constructor" function which would return a value of the required type pre-filled with the default values your choice for its fields—something like this:

func newParams() params {
    return params{
        foo: "ahem",
        bar: -3,
        baz: 0.5,
    }
}

so that the callers of your function could call newParams(), tweak its fields if they need and then pass the resulting value to your function:

myfunc(newParams())

ps := newParams()
ps.foo = "xyzzy"
myfunc(ps)

This approach is maybe a bit more robust than post-processing but it precludes using of literals to construct the values to pass to your function right at the call site which is less "neat".

like image 177
kostix Avatar answered Mar 13 '23 20:03

kostix