Skip to content

Instantly share code, notes, and snippets.

@luisfurquim
Last active June 7, 2019 13:17
Show Gist options
  • Save luisfurquim/548c37870f7ed5a57a1f91a696cfc62f to your computer and use it in GitHub Desktop.
Save luisfurquim/548c37870f7ed5a57a1f91a696cfc62f to your computer and use it in GitHub Desktop.
Go 2 Generics Ideas

Go 2 generics idea

This is not a proposal, that's just an idea and I wrote it in less than 2 hours. I got the https://gist.github.com/faiface/e5f035f46e88e96231c670abf8cab63f document and edited it to adapt to my idea. The examples were adapted from the examples shown there. Many parts of this text are, indeed, the original text took from there. There is no intent of plagiarism here, it's just that I don't have the time needed to work on it and making the explanation structuraly similar to the original one helps people compare the ideas. I also apologize if some parts of the arguments make no sense, it is very likely that the changes introduced by my idea conflicts with the original text and maybe I didn't noticed that.

I was never interested in generics before and made no research to see if someone came up with this idea before. I don't have programming language formality knowledge skills to discuss the implementability of this idea. I am just sharing the thoughts that came to my mind and if it is found to be useful, anyone may work on it, elaborating, detailing, fixing, forking, etc.

Generic functions

Let's start with the Min and Max fnctions.

The generic keyword

A new keyword generic is defined:

type anything generic{}

func Min(x, y anything) anything {
    if x<y {
       return x
    }
    return y
}

func Max(x, y anything) anything {
    if x>y {
       return x
    }
    return y
}

Obs. I did not worked on scope and shadowing, feel free to comment or work on suggestions.

The generic type may be inferable through:

  1. Using concrete type on the list of arguments;
type anything generic{}

func AnyFunc(x anything)  {
    // Do something
}

var a string
var b int

AnyFunc(a)      // anything inferred as string
AnyFunc(b)      // anything inferred as int
AnyFunc("abc")  // anything inferred as string
AnyFunc(4)      // error: ambiguous constant may be int, uint, ...
AnyFunc(int(4)) // anything inferred as int
  1. The expected concrete type;
type anything generic{}

func AnyFunc() anything {
    // Do something
}


var a string
var b int

a = AnyFunc()      // anything inferred as string
b = AnyFunc()      // anything inferred as int
"abc" + AnyFunc()  // anything inferred as string
4 * AnyFunc()      // error: ambiguous constant may be int, uint, ...
int(4) * AnyFunc() // anything inferred as int
x := AnyFunc()     // error: no clue to determine the concrete type

The generic type may be restricted:

type number generic{ int, int8, int16, int32, int64, uint uint8, uint16, uint32, uint64, float32, float64, complex64, complex128}
type comparable generic{
   int, int8, int16, int32, int64,
   uint uint8, uint16, uint32, uint64,
   float32, float64,
   complex64, complex128,
   string,
   bool,
}

func Sort(a []comparable, less func(comparable, comparable) bool)
func Merge(chans ...<-chan number) <-chan number
func Map(a []comparable, f func(comparable) number) []number

What can we do with generic values?

Under this idea:

Rule. You can do anything with generic values that you are able to do with interface{} values. This means that you can:

  1. Assignment of a generic type T to U and vice versa is allowed if T was already inferred in the current context and U will be inferred THROUGH this assignment AND the inferred T satisfies U
type anything generic{}
type something generic{int, string}

func AnyFunc(x anything) something {
    return OtherFunc(x)
}

func OtherFunc(y something) something {
    return y
}

var a int
var b string
var c float32

a = AnyFunc(7)    // anything is inferred to be int, which, in turn, makes something to be also inferred as int
b = AnyFunc("fg") // anything is inferred to be string, which, in turn, makes something to be also inferred as string
c = 7.0
a = AnyFunc(c)    // error: `anything` is inferred to be float32, but `something` is restricted and cannot be float32
  1. Compare values of the same generic type (or between generic and concrete type IF the generic type was inferred to that concrete type) using == and !=.
  2. Use them as keys in maps.
  3. Convert them to interface{}.
  4. Use type assertions and type switches (for example, to check if T implements an interface).

Of course, there's no need for doing a type assertion when assigning from T to T.

Some examples:

// Keys returns a slice of keys from the map m.
type K generic{}
type V generic{}
type T generic{}

func Keys(m map[K]V) []K {
    var keys []K
    for k, _ := range m {
        keys = append(keys, k)
    }
    return keys
}

// Find finds the value x in the slice a and returns its index.
// Returns 0, false if the value isn't in the slice.
func Find(x T, a []T) (i int, ok bool) {
    for i = range a {
        if a[i] == x {
            return i, true
        }
    }
    return 0, false
}

// Unique returns a new slice same as a, with duplicate elements removed.
func Unique(a []T) []T {
    var unique []T
    seen := make(map[T]bool)
    for _, x := range a {
        if seen[x] {
            continue
        }
        unique = append(unique, x)
        seen[x] = true
    }
    return unique
}

Reflection and interface{}

Generic functions can be converted to inteface{} directly, generic parameters and return values have the type defined by the type declaration and their kind is reflect.Generic (new kind!).

Generic types and methods

List example

Let's make the simplest generic type - a singly-linked list. This is how:

type anything generic{}

type List struct {
    Value anything
    Next  *List

func (l *List) Prepend(x anything) *List {
    return &List{x, l}
}

func FromSlice(a []anything) *List {
    var list *List

    for i := len(a) - 1; i >= 0; i-- {
        list = list.Prepend(a[i])
    }
    return list
}

// list have a generic type
var list *List

// The literal assignment forces the inferrence, so:
// nums have a concrete type
// nums.Next have also a concrete type
var nums *List = &List{
    Value: "abc",
    Next: *List,
}

Generic interfaces

type anything generic{}

type Set interface {
    Add(anything)
    Delete(anything)
    Contains(anything) bool
}

As an example, here's a generic Set interface. It can be implemented by a hash-set, a tree-set, or a list-set.

A generic interface is, in fact, an infinite family of interfaces. For example, the type below only implements the intversion of the interface:

type SliceIntSet []int

func (sis *SliceIntSet) Add(x int)           { /* ... */ }
func (sis *SliceIntSet) Delete(x int)        { /* ... */ }
func (sis *SliceIntSet) Contains(x int) bool { /* ... */ }

And this one implements all Set interfaces:

type HashSet(anything) map[anything]bool

func (hs HashSet) Add(x anything)           { /* ... */ }
func (hs HashSet) Remove(x anything)        { /* ... */ }
func (hs HashSet) Contains(x anything) bool { /* ... */ }

Following the thoughts of the original author of the example, I'll also leave it as an open question about whether to allow generic interfaces. Quoting him: "While they may be useful, there's a danger that they'll allow creating crazy and hard to understand abstractions. But maybe not. This needs to be investigated better.".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment