2

I have multiple structs like bellow

type Person struct {
    first string
    last  string
    age   int
}

type Product struct {
    ProductID     int                
    Name          string             
    Description   string             
    Price         float64           
}

I want a function that will take a slice of any type as the first argument and a function as a second argument that it will use to call with each element of the slice to construct a string slice and will return that string slice. Something like map() in Typescript/scala or select() in C#.

1
  • You are talking of generics - which do not exist in Go. However, you can easily implement func([]Person,func([]Person)([]string,error)) Commented Nov 22, 2019 at 6:37

5 Answers 5

1

Since Go doesn't have generics, "any type" can only mean interface{}. You can have something like this:

func ToStringSlice(arr []interface{}, convert func(interface{}) string) []string {
    ret := []string{}
    for _, elem := range arr {
        ret = append(ret, convert(elem))
    }
    return ret
}

Then you can basically inject any conversion function you want. e.g.

fmt.Println(ToStringSlice([]interface{}{1, 2, "foo", "bar"},
    func(x interface{}) string {
        return fmt.Sprint(x)
    }))

And since string conversions can go bad, I'd also add error checking:

// Define the function with an error return.
type Stringifier func(interface{}) (string, error)

func ToStringSlice(arr []interface{}, convert Stringifier) ([]string, error) {
    ret := []string{}
    for _, elem := range arr {
        if s, e := convert(elem); e != nil {
           return nil, e
        } else {
           ret = append(ret, s)
        }

    }
    return ret, nil
}
Sign up to request clarification or add additional context in comments.

3 Comments

Just for convenience, type safety is given up here. It puts the heavy lifting into the convert function, which then in turn has to figure out wether the interface{} parameter actually IS what convert expects during runtime.
@MarkusWMahlberg OP wrote: "I want a function that will take a slice of any type". That's what they asked. If you want "either type" then you probably need generics or to just implement two functions which is easier.
See @mh-cbon's solution. There is no need for runtime checks.
0

one solution involves fmt.Stringer

package main

import (
    "fmt"
)

func main() {

    items := []fmt.Stringer{Product{ProductID: 1}, Person{first: "first"}}

    var res []string
    for _, i := range items {
        res = append(res, i.String())
    }
    fmt.Println(res)

}

type Person struct {
    first string
    last  string
    age   int
}

func (p Person) String() string { return p.first }

type Product struct {
    ProductID   int
    Name        string
    Description string
    Price       float64
}

func (p Product) String() string { return fmt.Sprint(p.ProductID) }

1 Comment

: ) see the other answer ^^
0

That's a typical use case for an interface. Luckily enough the interface you need already exists as fmt.Stringer (https://golang.org/pkg/fmt/#Stringer):

Have a look at https://play.golang.org/p/d1sNPLKhNCU...

First your structs are required to implement the Stringer interface. This is done by simply adding a menthode String(), for example:

type Product struct {
    ProductID   int
    Name        string
    Description string
    Price       float64
}

func (p Product) String() string {
    return fmt.Sprintf("%d %s", p.ProductID, p.Name)
}

Now your Product is also a Stringer and can be Therefore be passed in any function that accepts Stringers:

func AsStringSlice(in []fmt.Stringer) []string {
    out := []string{}
    for _, i := range in {
        out = append(out, i.String())
    }
    return out
}

Since "interface{} says nothing" (https://go-proverbs.github.io/) I would recommend to use interface{} as rarely as possible. This is achieved in this approach. Also you can avoid reflection that way.

On the other hand you have to be able to manage the structs, eg. implement the String function. If thats not possible because the Types come from a dependency consider wrapping them:

type MyTypeWithStringer package.ForeinTypeWithoutStringer

func(t MyTypeWithStringer) String() string {
    // ...
}

Comments

0

This is a solution that fits me best

type Person struct {
    first string
    last  string
    age   int
}

type Product struct {
    ProductID     int                
    Name          string             
    Description   string             
    Price         float64           
}

func mapToStringSlice(slice interface{}, mapFunc func(interface{}) string) []string {
    s := reflect.ValueOf(slice)
    if s.Kind() != reflect.Slice {
        panic("mapToStringSlice() given a non-slice type")
    }
    ret := make([]string, s.Len())

    for i:=0; i<s.Len(); i++ {
        ret[i] = mapFunc(s.Index(i).Interface())
    }

    return ret
}


func main() {
persons := []Person{{
        first: "A",
        last:  "g",
        age:   20,
    },{
        first: "B",
        last:  "r",
        age:   40,
    },{
        first: "C",
        last:  "",
        age:   0,
    }}

products := []Product{Product{ProductID: 1}, Product{ProductID: 2}}

    personFirstNames := mapToStringSlice(persons, func(input interface{}) string {
        return input.(Person).first
    })
    productIDs := mapToStringSlice(products, func(input interface{}) string {
        return input.(Product).ProductID
    })

}

Comments

0

You can just simply use Sprintf method like below:

func main() {
     items := []any{1, 2, 3, 21.2, "a"}
     var strElems []string
            
     for _, item := range items {
          strElems = append(strElems, fmt.Sprintf("%v", item))
     }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.