Home > OS >  Pass list of one of two structures to the function
Pass list of one of two structures to the function

Time:01-20

New in Go, couldn't find any intuitive way of doing that.

I have such piece of code

tx = getTx()
for _, record := range tx.a {
    // do a lot with record.Important
}
for _, record := range tx.b {
    // do a lot with record.Important
}
for _, record := range tx.c {
    // do a lot with record.Important
}

And the following structs:

type Record1 struct {
    // fields of Record1
    Important string
}
type Record2 struct {
    // fields of Record1
    Important string
}
type TX struct {
    a []Record1
    b []Record1
    c []Record2
}

Now the logical is to extract every for logic into the function:

func helper(records) { // Here is the problem
   // do a lot with record.Important
}

Problem:

records is a []Record1 or []Record2 type. But it looks like Union types doesn't exists in Golang. So I thought I could pass []string into the helper, but cannot even find an elegant way to get something equivalent to map(lambda r: r.Important, tx.a). There is no high order map function, no list comprehesion. I am not convinced to use raw for loop to solve that.

CodePudding user response:

One approach to do the loop across multiple types is to use interfaces together with generics. Have each Record type implement a getter method for the important field. Then declare an interface that includes that getter method in its method set. Then you can make your helper generic by declaring the interface as its type parameter.

func (r Record1) GetImportant() string { return r.Important }
func (r Record2) GetImportant() string { return r.Important }

type ImportantGetter interface {
     GetImporant() string
}

func helper[T ImportantGetter](s []T) {
    for _, v := range s {
        _ = v.GetImportant()
    }
}

CodePudding user response:

Unless I'm misunderstanding your question, it seems like you want to extract all the values in column X from a set of records and then pass those values in as a slice to some function - I'm basing my assumption on your wish that go had something like map().

If what you're after is type-agnosticism, you could certainly use an interface approach like that suggested by mkopriva, but you aren't going to get out of using a for loop - iteration over list types is core to idiomatic go. If you need a mapping function, you're going to have to write one that performs the mapping you want.

I'd note that you do not need generics to do what mkopriva suggests, you can just use an interface without muddying the waters with generics go playground:

package main

import "fmt"

type Record1 struct {
    Important string
}

type Record2 struct {
    Important string
}

func (r Record1) GetImportant() string { return r.Important }
func (r Record2) GetImportant() string { return r.Important }

type ImportantGetter interface {
    GetImportant() string
}

func helper(s []ImportantGetter) {
    for _, v := range s {
        fmt.Println(v.GetImportant())
    }
}

func main() {
    records := []ImportantGetter{Record1{Important: "foo"}, Record2{Important: "bar"}}

    helper(records)
}

Another approach to the type-agnosticism, and one that's a bit more (IMHO) idiomatic for "I expect all of these types to have a common property," is to use struct embedding and type assertions to build your own Map() function up go playground:

type CommonFields struct {
    Important string
}

type Record1 struct {
    CommonFields
    FieldSpecificToRecord1 string
}

type Record2 struct {
    CommonFields
    FieldSpecificToRecord2 int
}

func main() {
    r1 := Record1{
        CommonFields{Important: "I'm r1!"},
        "foo",
    }

    r2 := Record2{
        CommonFields{Important: "I'm r2!"},
        5,
    }

    records := []interface{}{r1, r2, "this is not a valid record type"}

    fmt.Println(Map(records))

}

func Map(source []interface{}) []string {
    destination := make([]string, len(source))
    for i, sourceRecord := range source {
        if rr, ok := sourceRecord.(Record1); ok {
            destination[i] = rr.Important
        } else if rr, ok := sourceRecord.(Record2); ok {
            destination[i] = rr.Important
        } else {
            destination[i] = "undefined"
        }
    }
    return destination
}

You'd likely want to make your implementation of Map() accept an argument specifying the field to extract to conform to what you have in other languages, or possibly even just pass in a helper function which does most of the type-specific value extraction.

  • Related