可以采用多种类型的功能

I'm trying to create a helper function which will take a map with string keys and return a slice of the map keys.

The problem is that I want the function to not care about the type of the map values.

For example:

stringStringMap := map[string]string{
    "one": "first",
    "two": "second"
}

mapKeys(stringStringMap) // ["one", "two"]

stringIntMap := map[string]int{
    "one": 1,
    "two": 2,
}

mapKeys(strinIntMap) // ["one", "two"]

It seems that the only way around this problem is to create two similar helpers. Something like this:

func mapKeys(m map[string]string) []string {
    ...
}

func mapKeys2(m map[string]int) []string {
    ...
}

But this seems ugly. Is this helper function I'm trying to create possible?If not, is there a good convention I should follow when writing this?

There is a third option not mentioned yet, and that is to use a type switch. This may be a good option if you know that you'll be passing a manageable number of types to the function. It would work like this:

func Keys(m interface{}) ([]string, error) {
    switch t := m.(type) {
    case map[string]string:
        keys := make([]string, 0, len(t))
        for key := range t {
            keys = append(keys, key)
        }
        return keys, nil
    case map[string]int:
        keys := make([]string, 0, len(t))
        for key := range t {
            keys = append(keys, key)
        }
        return keys, nil
    default:
        return nil, fmt.Errorf("unknown map type: %T", m)
    }
}

This still gives you a bunch of seemingly duplicate code, but at least it's all behind a single function name, and it's more efficient than reflection.

A way to solve this problem is to use "interface{}" when creating the maps and then use a type switch statement in the method i.e.

func mapKeys(m map[string]interface{}) []string {
    for k,v := range m {
        switch a := v.(type) {
        case int:
          ... do int stuff
        case string:
          ... do string stuff
        }
    }
}

Go does not have user specified generic parameter types, so what you are describing must be done via:

  1. Separate functions that accept the different types you wish to support, or
  2. A function that accepts the empty interface (interface{}) and uses reflection.

Here is an example implementation of the second approach:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    stringStringMap := map[string]string{
        "one": "first",
        "two": "second",
    }

    fmt.Println(mapKeys(stringStringMap)) // ["one", "two"]

    stringIntMap := map[string]int{
        "one": 1,
        "two": 2,
    }

    fmt.Println(mapKeys(stringIntMap)) // ["one", "two"]
}

func mapKeys(m interface{}) []string {
    v := reflect.ValueOf(m)
    if v.Kind() != reflect.Map {
        panic("m is not a map")
    }
    if v.Type().Key().Kind() != reflect.String {
        panic("m does not have a string key")
    }
    keys := make([]string, 0, v.Len())
    for _, key := range v.MapKeys() {
        keys = append(keys, key.String())
    }
    return keys
}