Calling functions with unknown number of arguments

71 Views Asked by At

I have the following struct:

// fninfo holds a function and its arity.
type fninfo struct {
    arity int // number of arguments that fn takes
    fn    any // fn must take string arguments and return one string
}

and a map of it:

var fnmap = map[string]fninfo{
    "not": fninfo{
        arity: 1,
        fn: func(a string) string // body omitted...
    },
    "add": fninfo{
        arity: 2,
        fn: func(a, b string) string // body omitted...
    },
    // more items...
}

What I want to do is, with any fninfo, pop fninfo.arity number of strings from a stack (implementation omitted) and call fninfo.fn with all the popped strings as arguments, in a way similar to this:

info := fnmap[fnname]
args := make([]string, info.arity)
for i := 0; i < len(args); i++ {
    if args[i], err = stk.pop(); err != nil {
        // Handle error...
    }
}
info.fn(args...)

However, this is forbidden for two reasons:

  1. info.fn cannot be directly called because it is of type any (interface{})
  2. info.fn cannot be called with a slice as its arguments because it is not a variadic function

Currently, all the functions I have take between 1-3 arguments, so as a workaround, I am manually popping each item and checking each type assertion:

info := fnmap[fnname]
if fn, ok := info.fn.(func(string) string); ok {
    a, err := stk.pop()
    if err != nil {
        // Handle error...
    }
    stk.push(fn(a))
} else if fn, ok := info.fn.(func(string, string) string); ok {
    a, err := stk.pop()
    if err != nil {
        // Handle error...
    }
    b, err := stk.pop()
    if err != nil {
        // Handle error...
    }
    stk.push(fn(a, b))
} else if fn, ok := info.fn.(func(string, string, string) string); ok {
    a, err := stk.pop()
    if err != nil {
        // Handle error...
    }
    b, err := stk.pop()
    if err != nil {
        // Handle error...
    }
    c, err := stk.pop()
    if err != nil {
        // Handle error...
    }
    stk.push(fn(a, b, c))
}

This is very long and repetitive, and it becomes unmaintainable should I add functions with 4, 5, or even more arguments.

How can I bypass the two problems I listed above and have something similar to my second last example? Is this possible to achieve in Go? without reflection, and if not, with?

0

There are 0 best solutions below