Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Generics Dismantled

DZone's Guide to

Generics Dismantled

In this article, Kaveh Shahbzian takes a loot at Generics and Types in Go, JavaScript, and Elixir, and explains how each concept relates to these languages.

· Web Dev Zone
Free Resource

Tips, tricks and tools for creating your own data-driven app, brought to you in partnership with Qlik.

While I was bouncing back and forth between Go, JavaScript, and some Elixir, some patterns and thoughts emerged; and please bear with me even if there are better solutions to these samples. The solutions provided are intended merely to get across the concepts under discussion - so they are demonstrative pseudocode.

Puzzle Pieces

The problem: we need to apply some functionality/computation to sets of data in a uniform way. So, to take an example, let’s write a map utility for slices, which will map/apply a function to all the items in a slice of any kind, and gives us a slice of the same length with items of another kind. How about:

func mapSlice(sliceLen int, mapFunc func(int)) {
    for i := 0; i < sliceLen; i++ {
        mapFunc(i)
    }
}

You do not see any slices here. Instead, this function uses the one thing that all items in a slice share uniformly - their index - to tell the passed map function on which item it should work.

For instance, we could take a slice of strings, convert them to float numbers, and have a slice of floats like this:

func sample1() {
    source := []string{`1`, `2`, `3`}
    result := make([]float64, len(source))
    mapSlice(len(source), func(ix int) {
        str := source[ix]
        f64, _ := strconv.ParseFloat(str, 64)
        result[ix] = f64 + 0.1
    })
    log.Println(result)
}

As you see, we’ve passed a function to our utility (mapSlice) which receives the index of the item in the source slice, then grabs the item at that index from the source, then converts it to a float number and puts it inside the resulting slice at the same index.

A uniform property in all members of a slice - their index - allowed us to perform some actions on those members in a uniform way. This utility (mapSlice) works on all kinds of Go slices in a uniform way - kind of in a generic way.

The target does not even need to be a slice. We could write some code with the same structure to calculate the multiplication of all of its items:

func reduceSlice(sliceLen int, reduceFunc func(int)) {
    for i := 0; i < sliceLen; i++ {
        reduceFunc(i)
    }
}

Which is pretty much the same mapSlice utility - I've just done some renaming to convey the intention. And the multiplication of all the items would be:

func sample2() {
    source := []float64{1.01, 2, 3}
    var result float64 = 1
    reduceSlice(len(source), func(ix int) {
        result *= source[ix]
    })
    log.Println(result)
}

What do our utilities have in common so far? They both have two parts, one does the extraction of needed data, and the other part is a common functionality to get applied to that extracted data.

Let’s try it with a type other than slices, like a struct that has some fields including a stringand an uint64 and we want to calculate the hash of those string fields and put that hash in the related uint64 field of the same object.

func hash(slots func() (*string, *uint64), hashFunc func(*string, *uint64)) {
    hashFunc(slots())
}

Here, we have an extractor function (slots) which selects out the necessary fields of the object and the functionality/computation (hashFunc) that we want to apply to these extracted parts/fields. What our utility does is compose these two actions/functions. For example:

func sample3() {
    var t1 struct {
        t1Name string
        h1     uint64
    }
    var t2 struct {
        t2Name string
        h2     uint64
    }
    var t3 struct {
        t3Name string
        h3     uint64
    }

    // dummy data
    {
        t1.t1Name = `A`
        t2.t2Name = `B`
        t3.t3Name = `C`
    }

    name1 := func() (*string, *uint64) { return &t1.t1Name, &t1.h1 }
    name2 := func() (*string, *uint64) { return &t2.t2Name, &t2.h2 }
    name3 := func() (*string, *uint64) { return &t3.t3Name, &t3.h3 }

    hashFunc := func(name *string, hx *uint64) {
        h := fnv.New64a()
        h.Write([]byte(*name))
        *hx = h.Sum64()
    }

    hash(name1, hashFunc)
    hash(name2, hashFunc)
    hash(name3, hashFunc)

    log.Println(t1, t2, t3)
}

Any type that provides a proper selection mechanism (the extractor part) can be used with this utility and the same computation/functionality in a uniform way.

Where’s the Generic?

From this point of view, generics are a form of function composition, a pair of functions, one for extracting needed parts, and one that applies some computation to them, i.e. a form of map/reduce. And of course, an engine that performs the map/reduce action, or more precisely, it composes map functions and reduce functions in a meaningful way.

The first part (extraction/map) can be done in Go by nicely employing interfaces. In fact, Go does this so nicely that you do not even have to do it explicitly. It’s done in a statically duck-typed manner which implies implicity. It even allows you to define interfaces that are already implemented in a package which is not under your control!

On the other hand, the second part (reduce) is impossible to implement in an old school, generic manner - i.e. it does not exist. Because implementing the second part needs the compiler to pin down the actual underlying type, we have to adapt the map to each case to transform the data into something that can be fed into the reduce.

What Are Types?

Are generics some sort of executable type specification? What problems do generics solve?

Having a structure for some data seems insufficient to uniquely identify that data. For example, in Elixir there are situations where we “tag” the data with an atom. Since we have some groups of data with identical structures, that should not be considered the same (ok, not just this). Is that Meta Data? Even the structure is not a one-to-one equivalent to the data - it’s always an “expected structure.”

And even the structure is some sort of tag/metadata, not the value itself. So what’s a type? I wish I could understand it better. Even in statically “typed” languages, in those that it seems that the type is carrying the data, we go through all sorts of hassles to maim the type at the point where we use it. It is at that point that we want another structure/type since what we already have came from, and was designed based on, another concern/restriction, like DB models, not our intention.

So, what’s a type really? Maybe Go comes up with a nice answer for this, by proposing a different generic type definition.

Explore data-driven apps with less coding and query writing, brought to you in partnership with Qlik.

Topics:
generics ,web dev ,go ,javascript

Published at DZone with permission of Kaveh Shahbazian. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}