0

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?

ban_javascript
  • 339
  • 1
  • 4
  • 16
  • 1
    Use `func f(a ...string)`. Consult the spec. – Volker Feb 20 '23 at 06:36
  • 1
    And if you can't change the function signatures as suggested by Volker, your only other option, if you gotta stick with `fn any`, is to use reflection. – mkopriva Feb 20 '23 at 06:37
  • If you trust `arity` to match `fn`, then you don't need a type assertion, you can just use a type switch on `arity`. Although that raises the question: Why have both, since you can determine `arity` by looking at `fn`? – Jonathan Hall Feb 20 '23 at 08:42
  • @JonathanHall how do can you determine `arity` by looking at `fn`? Do you mean by reflection? – ban_javascript Feb 20 '23 at 08:48
  • You have two options: reflection, or exactly what you're doing: A type assertion or type switch. – Jonathan Hall Feb 20 '23 at 08:51
  • 1
    @ban_javascript if you want to avoid reflection, you can also just simplify your original design. For example you could do the following: https://go.dev/play/p/RlEDki2MG7R – mkopriva Feb 20 '23 at 09:39

0 Answers0