Go中的函数类型-将特定类型转换为更通用的类型

Go中的函数类型-将特定类型转换为更通用的类型

问题描述:

What cast / assertion need I do in Go in order to pass to a function expecting a generic function like func(interface{}) interface{}, a more specific function like func(int) int instead?

For example, in code like this, fooA can be passed to MakeExclamer, but not fooB:

func MakeExclamer(foo func (interface{}) interface{}, n int) func () {
    return func() {
        fmt.Printf("%v!!!", foo(n))
    }
}

func fooA(x interface{}) interface{} {
    return x.(int)*2
}

func fooB(x int) int {
    return x * 10
}

func main() {
    exclamerA := MakeExclamer(fooA, 12)
    exclamerA()
    exclamerB := MakeExclamer(fooB, 66)
// >> cannot use fooB (type func(int) int) as type func(interface {}) interface {} in argument to MakeExclamer 
    exclamerB()
}

(Go Playground link: https://play.golang.org/p/xGzfco0IAG)

I'm not interested much in alternative code structure patterns, since this is how I want it to work: a specific function should be passed to a general function transformer (accepting function of type Any -> Any) that will return another general function (Any -> Any). This may not be idiomatic in Go, but it is the pattern that I want my code to follow.

To use type assertions, every possible type must be enumerated in MakeExclamer:

func MakeExclamer(fn interface{}, arg interface{}) func() {
    switch fn := fn.(type) {
    case func(int) int:
        return func() {
            fmt.Printf("%v!!!
", fn(arg.(int)))
        }
    case func(interface{}) interface{}:
        return func() {
            fmt.Printf("%v!!!
", fn(arg))
        }
    default:
        panic("not supported")
    }
}

To accept a function of any type, the fn argument is declared as type interface{}. The code uses a type switch to handle the different function types.

playground example

Reflection can be used to write a more general function.

func MakeExclamer(fn interface{}, arg interface{}) func() {
    fnr := reflect.ValueOf(fn)
    argr := reflect.ValueOf(arg)
    return func() {
        resultr := fnr.Call([]reflect.Value{argr})
        fmt.Printf("%v!!!
", resultr[0].Interface())
    }
}

playground example

First things first : When it comes to typing in Go, everything is theoretically possible. That's because even though the compiler does a lot of checks at compile-time, it is possible to change the runtime... at runtime. So-called runtime hacks, where you dynamically manipulate runtime structs that you're NOT supposed to handle.

Now, you have an interesting question, whose answer doesn't include the need to use the 'unsafe' package. However, the way I found of generalizing a function involves heavy reflection.

How to call a function (via reflection) ?

The documentation for the reflect package can be found here.

So, like all elements in Golang, functions have a Type. Without going through all fields, functions do take an array of arguments and produce an array of results. It is possible to investigate the Type of arguments and results through the In(int) and Out(int) method.

func investigate(fn interface{}) {
    fnType := reflect.TypeOf(fn)

    for idx := 0; idx < fnType.NumIn(); idx ++ {
        fmt.Printf("Input arg %d has type %v
", idx, fnType.In(idx))
    }

    for idx := 0; idx < fnType.NumOut(); idx ++ {
        fmt.Printf("Output arg %d has type %v
", idx, fnType.Out(idx))
    }
}

We won't use this code. However, two important things are to be noted at this point :

  • The generic type under which a function can be passed around without caring about its type is interface{}. Something like "func(interface{}) interface{}" is not a generalization of a function, it is already a concrete type. Hence, "func(interface{}) interface{}" is not a generalization of "func(int) int", those are two different function types entirely. This is why you can't use type assertions/cast to convert from one function type to another.
  • A function can be represented as something that takes an input array and produces and output array.

Now, in order to call a function, you have to get not its Type, but its Value. Once you get its value, you can call it using an array of arguments, which must all be Values.

The prototype is:

func (v Value) Call(in []Value) []Value

Using this method, it is possible to call any function.

The code

So, the only thing you need is to convert whichever arguments array you have to an array of Values, then you will be able to call your function.

Here is your code:

package main

import (
    "fmt"
    "reflect"
)

func MakeExclamer(foo interface{}, n int) func() {
    exclamer := generalize(foo, n)
    return func() {
        fmt.Printf("%v!!!
", exclamer())
    }
}

func fooA(x interface{}) interface{} {
    return x.(int) * 2
}

func fooB(x int) int {
    return x * 10
}

func generalize(implem interface{}, args ...interface{}) func() interface{} {
    valIn := make([]reflect.Value, len(args), len(args))

    fnVal := reflect.ValueOf(implem)

    for idx, elt := range args {
        valIn[idx] = reflect.ValueOf(elt)
    }

    ret := func() interface{} {
        res := fnVal.Call(valIn)

        // We assume the function produces exactly one result
        return res[0].Interface()
    }

    return ret
}

func main() {
    exclamerA := MakeExclamer(fooA, 12)
    exclamerA()

    exclamerB := MakeExclamer(fooB, 66)
    exclamerB()
}

Playground

The important bit is the generalize function which makes the translation between your arguments and an array of Values, then returns a new function whith all parameters already filled.

Do not hesitate if you need any precision !