A proposal for generic in go

2102 views
Skip to first unread message

xingtao zhao

unread,
Jun 14, 2016, 9:04:05 PM6/14/16
to golang-nuts
Here is my proposal for generic in go: https://docs.google.com/document/d/1nO7D15c2B3eq2kF62C0yUs_UgpkyPL2zHhMAmlq1l98/edit?usp=sharing

Many parts has not been finished, and just initial thoughts. In the proposal, I want to keep back compatibility. And I try to add the orthogonal feature only and keep the language still simple enough. 

Please add your comments on it. Hope it is useful and could inspire some new features in go 2.0

Thanks!

Ian Lance Taylor

unread,
Jun 14, 2016, 9:22:08 PM6/14/16
to xingtao zhao, golang-nuts
Have you read the generics proposals linked from
https://golang.org/issue/15292 ?

Ian

xingtao zhao

unread,
Jun 15, 2016, 9:43:59 PM6/15/16
to golang-nuts, zhaox...@gmail.com
I read your proposals now. My proposal is very similar to your proposal of type parameter - in fact, they are almost identical. But in my proposal, I am not going that far as in your proposal. I did not include the operators, thus all generic functions can only access the unknown type through interface it satisfied. In my proposal, I am not allowing to access the field of an unknown type. 

Another valuable part my proposal added in the generic struct. I think this is different from the implementation in your proposal. Your proposal convert everything to interface method calls. While in my proposal, we have a special memory layout and padding algorithm, which make access the field of a struct with unknown type very cheap - almost identical to access an element in a slice/array.

And in my proposal, we do not need to change the compiler too much, and do not need to slow down the complier speed a lot.

Anyway, looking forward to any comments for the valuable part in my proposal. Thanks! 

parais...@gmail.com

unread,
Jun 16, 2016, 7:02:16 AM6/16/16
to golang-nuts
A good compromise would be to only implement "parametric" functions. append and make are" parametric" functions as they know what is their return type at compile time , no matter what type the array/slice argument is . That way people who don't want a generic type are happy as no generic type is introduced, and people who want to write functions like push/pop/map on arrays of any type in a type safe way. 

Go is full of compromises of that sort so it shouldn't be an "all or nothing" debate. 

Aside from the implementation, the most difficult matter is choosing the right syntax, such as

    func XOR<T>(array1 []T,array2 []T)array[]T{
        // implementation
    }

So yes, people would not be able to define their own type of container (set,queues,binary trees,...). But all these containers can be build from array or map structures and methods specific to each data structure could now be written in a "generic" way. 

At the end of the day we all want the same thing, compile time type safety, no more, no less.

andrew...@gmail.com

unread,
Jun 20, 2016, 3:49:06 PM6/20/16
to golang-nuts, parais...@gmail.com
As for me then everything is much more simple if Go language (as the starting point) will  introduce a non-reified generics.
Non reified generics does not requires a complex runtime support (static code which was generated by the compiler will be enough).
Type checking of the type arguments performed at the compile time on the calller side (sender code).
The callee side (receiver code) remains unchanged as it is currently is.
This has some benefits:
1. This is a starting point (but not a no dead end) and point for a later enhancements
2. Does not requires significant changes in the compiler (codegen)
3. Does not requires significant changes in the runtime (engine)
4. Does not requires significant changes in the type system (just to add a few additional fields into the interface type presentation)
5. Does not requires significant changes in the reflection (just to adapt for slightly extended type presentation)

Because they (generic types) will be not reified on the callee side (receiver code) then compiler can generate the same code as it generates currently.
Only callers side will perform generic type checks before assignments of values with generic types and vice versa (to variables, constants, function and methods arguments).

That is.

type Foo<K, V> struct {
}

func (s *Foo) Get(key K) (val V) {
  // Here is generated unchanged code
  // The same as for non generic use
}

func some() {
  foo := Foo<string, int>{}

  // Generated by the compiler
  // _typ := _getRTypeOf(foo)
  // _typ.isGeneric = true
  // _typ.genParams = [2]_GenParam
  // _typ.genParams[0] = &_GenParam{key: "K", val: _getRType(interface{}}
  // _typ.genParams[0] = &_GenParam{key: "V", val: _getRType(interface{}}
  // _typ.genArgs = [2]*_RType{}
  // _typ.genArgs[0] = _getRType(string)
  // _typ.genArgs[1] = _getRType(int)
  // Also compiler statically performs type check bounds of the type arguments to type parameters assignments (if type bounds specified)

  var key interface{}
  key = bool

  // Generated by the compiler (safe guard)
  // _temp0 := key
  // _checkGenericArg(_typ, 0, temp0)
  // _temp1 := foo.Get(key)

  val := foo.Get(_temp0)
}

That is, the compiler always (if it cannot compute the type at compile time and cannot to check them statically) inserts "safe guards" type check before any kimd of an assignments.
The `_checkGenericArg(_typ, index, key)` also performs the checks to type parameter bounds (eg. for Foo<K string, V int>) for a given index of the type parameter and argument.

Callee code does not performs any type checks on incomming parameters.
It always think that incomming parameters has specified type.
That is, if type bounds ommitted then specified type is interface{}
If type bounds specified then specified type is itself

Eg.

type Type1<E> struct {
}

type Type2<E int> struct {
}

func(t *Type1) foo(val E) {
}

func(t *Type2) foo(val E) {
}

Generated as the following (that's easy)

func(t *Type1) foo(val interface{}) {
}

func(t *Type2) foo(val int) {
}

andrew...@gmail.com

unread,
Jun 20, 2016, 4:34:15 PM6/20/16
to golang-nuts, parais...@gmail.com, andrew...@gmail.com
Overhead at runtime will be not very big;
1. Generate each time an instance of specific generic type with arguments
2. Perform generic specific type checks before any kind of an assignements

1. The first overhead can be reduced (eleminated) by the static code generation of specified generic types with storing them in special place

Eg.

foo := Foo<string, int>{}

Since this code is static (which means that at runtime the arguments (string and int) remains unchanged then this type (Foo<string, int>) can be canonicalized (I can also say "hashed" but canonicalized is more precisely). pre-generated by the compiler and stored (to avoid re-creating it each time at runtime) for further use.

2. The second overhead can be reduced (eleminated) by the static code generation when compiler can compute types (used in assignments).

Since specific generic type is know at compile time then generic type checks (the same as regular, non generic) can be performed at compile time

Micky

unread,
Jun 20, 2016, 7:54:42 PM6/20/16
to xingtao zhao, golang-nuts
It's never going to happen! To quote the powers-to-be, "The language is done".
Good luck!
> --
> You received this message because you are subscribed to the Google Groups
> "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to golang-nuts...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

Rodrigo Kochenburger

unread,
Jun 20, 2016, 8:15:29 PM6/20/16
to Micky, xingtao zhao, golang-nuts
Micky, I'm not sure where you're quoting from but they never said it's never gonna happen.

From the FAQ: "Generics may well be added at some point." and "This remains an open issue".


xingtao zhao

unread,
Jun 20, 2016, 8:29:26 PM6/20/16
to golang-nuts, parais...@gmail.com, andrew...@gmail.com
That was my original thought. While later I realized that we still need some parameter check in the callee code to satisfy the interface matching (the generic version of each function in my proposal). And we need special assembler function for dynamic interface cast.

andrew...@gmail.com

unread,
Jun 21, 2016, 3:24:12 AM6/21/16
to golang-nuts, parais...@gmail.com, andrew...@gmail.com
>> While later I realized that we still need some parameter check in the callee code to satisfy the interface matching (the generic version of each function in my proposal).

A am sorry but your judgements is not correct.
Here is a proofs:
1. It is redundant to perform check of the correctness of the assignments after when assignement already made.

Eg.

val i int
i = 0
// It is redundant to perform check the correctness of the assignments of `i` after when assignement of `i` already made.

2. Fucntion is a callable piece of code which CAN have initial variables (function parameters) which SHOULD be assigned (via function arguments) before function invocation.

Of course, these parameters can be explicit and implicit.
Eg. receiver of the method is an implicit parameter but it still a parameter (function variable which would be assigned before function invocation implicitly by the compiler, or by the reflection construction).

That is, this code is equilvalent.

var i int
i = 0
// other code, `i` already assigned

func foo(i int) {
  // other code, `i` already assigned
}


3. Also you should to know that in a generic type system (type sytem with the generic types) each type is potentialy a generic type.
Here is a proof.

Each type in a generic type system looks like a function.
Type can have a [0..~] number of parameters which are declared by the type declaration and whose parameters can be assigned by the type arguments. If arguments omitted then parameters assigned to default values (types).


The same as the funtions.

// Declare type with 2 parameters, K and V
type Type<K, V> {}

// Declare type with 2 bounded parameters, K and V
type Type1<K string, V int> {}

// Instantiate new type with a specified arguments (the same as the function invocation)
// For simplicity we can imagine the following auto generated code
// if *__staticType1021 == nill {
//   *__staticType1021 = __createGenericType(__getType(Type1), __getType(string), __getType(bool))
//  That is, call to __createGenericType(*t RType, args... *RType)
// }

// foo := __newobject(*__staticType1021)

foo := Type1<string, bool>()

Another story when we use generic type on the callee side.

func (t *Type1) foo(key K) V {
  // Here compiler always assume that the type of `t` is a generic type `Type1` with a correctly assigned type arguments.
  // Assigned arguments does not need to re-check 
  // ___type0 := __getTypeOf(t)
  // Here is enough (since type are generic) rely only on that the each type argument bound to the type parameter
  ...
}


Henry

unread,
Jun 21, 2016, 4:13:53 AM6/21/16
to golang-nuts
I think we should carefully consider whether generics is worthy enough to be added into the language. It is easy to add a feature, but nearly impossible to remove one without breaking the backward compatibility promise.

One thing for sure is that we are in the middle of a paradigm shift. I am seeing more and more codes blending OOP with elements of functional programming. Some OOP purists go as far as avoiding getter/setter and use DTO to allow data editing, which to me it looks like a separation of data and function in procedural programming. Whatever the latest trend is, we shouldn't let Go become another C++ by blindly following trends. Everything must be thought of carefully.

I was one of the generics supporters for Go in this forum, but now I am not so sure.

andrew...@gmail.com

unread,
Jun 21, 2016, 4:45:08 AM6/21/16
to golang-nuts
>> I was one of the generics supporters for Go in this forum, but now I am not so sure.

But why?
Generic types is the same types as and the regular types.
Difference only in that the regular types does not have a parameters but generic types are always have at least one parameter.
This is why they are called generic (universal) types.
The same as you don't worry about that the function can have a parameters why you should worry about that the type can have a parameters?
When for types allowed to have parameters then this get a some benefits:
1. Generice types can be "configured" by some requirements (by their parameters)
2. Generice types can have a restrictions (eg. each parameter can have its own upper type bounds)
3. Generic types are universal types

Eg. Trivial example

type KeyValuePair<K, V> struct {
  key K
  val V
}

You can use this type everywhere since it is universal type.

Below is not the same type (because in generic type system K != V, but here interface{} == interface{}):

type KeyValuePair struct {
  key interface{}
  val interface{}
}

Because power of the generic types in that the they are variative:
- Covariant
- Contrvariant
- Invariant

And they, of course, can have a parameters.

Henry

unread,
Jun 21, 2016, 6:01:26 AM6/21/16
to golang-nuts, andrew...@gmail.com
I am not saying that generics is bad, but I am questioning whether generics is necessary.

Micky

unread,
Jun 21, 2016, 7:53:48 AM6/21/16
to Rodrigo Kochenburger, xingtao zhao, golang-nuts
I guess that's for diplomatic reasons. If you watch talks of core
contributors then I believe you'll know what I mean :)

andrew...@gmail.com

unread,
Jun 21, 2016, 8:45:21 AM6/21/16
to golang-nuts, andrew...@gmail.com
>> I am not saying that generics is bad, but I am questioning whether generics is necessary.

Please, do not panic.
If you worry about the following things:
- Generated code will grow when used generics
- Generated code will be more complex when used generics
- Execution performance will slow down when used generics
- Memory consumption will grow when used generics

I can say you.
- Generated code will NOT grow when used generics
- Generated code will NOT more complex when used generics
- Execution performance will NOT slow down when used generics
- Memory consumption will NOT grow when used generics

They are so arranged that they are universal for all uses cases and does not require a significant changes in the following things:
- Compilers
- Runtimes
- Reflections

Most of the type checks will be performed at the compile time only because the Go language is statically typed language.
Most of the type instances (instances of the generic types which will be created at runtime) will be stored for the further reuse (they will be not created again and again but they will retrieved from the hidden static variables, so called lazy getters).

Only when used reflection then they (newly created type instances) can get some overhead but just a little bit more then with non-generic types.

Generic types is not evil, nor complex.
They are still the same types but only with the parameters (this is single kind of complexity which for me is not complexity at all) and they are variative (but this is not a complexity this is just a rules).

Ian Davis

unread,
Jun 21, 2016, 9:48:32 AM6/21/16
to golan...@googlegroups.com
 
On Tue, Jun 21, 2016, at 01:45 PM, andrew...@gmail.com wrote:
>> I am not saying that generics is bad, but I am questioning whether generics is necessary.
 
Please, do not panic.
If you worry about the following things:
- Generated code will grow when used generics
- Generated code will be more complex when used generics
- Execution performance will slow down when used generics
- Memory consumption will grow when used generics
 
I don't believe most people in this mailing list are worried about those things. They are worried about things like code complexity, readability and increase in cognitive load to decipher chains of type definitions.
 
Ian

andrew...@gmail.com

unread,
Jun 21, 2016, 10:18:04 AM6/21/16
to golang-nuts
>> increase in cognitive load to decipher chains of type definitions.

Sorry, but who are members of this mail lists?
This is a first time when I hear about such loads such as the `cognitive load`.
Also I am possible here a single person who does not know anything about the `cognitive load to decipher chains of type definitions`.
I am mostly business man and a programmer just for my own requirements.
I love to implements parsers, code generators, some effective algorithms for solving some problem at the most effective time.
Also my work includes a diagnose the failures and locate them.
And I want to apologize for my illiteracy (thick-headed), but I do not really was expecting that all members of this mail list worry mostly about the ` increase in cognitive load to decipher chains of type definitions` and don't worry about other things.
I am sorry.

Ian Davis

unread,
Jun 21, 2016, 10:24:23 AM6/21/16
to golan...@googlegroups.com
What I mean is that most people who have followed the past generics discussions already know it's possible to implement them with efficient memory use and performance. So they don't need to worry much about that.
 
What stops them being added to the language is: can they be implemented without making Go a much more complex language than it is now.  In my opinion complex or large languages result in code that is much harder and expensive to maintain over the long term.
 
Ian
 

Henry

unread,
Jun 21, 2016, 10:29:37 AM6/21/16
to golang-nuts
You still haven't provided any argument why generics is indispensable.

The reason why I am no longer sure about my position in this issue is because -while I agree that generics is useful- I don't think that generics is essential. In fact, all of C++ features are useful and implemented in a very efficient manner, but take a look what happened when you slab that many features together. If you can do away with less, I think you should go for less. The trouble is deprecating language features is a lot harder than deprecating APIs in the standard library, while programming fads come and go.

andrew...@gmail.com

unread,
Jun 21, 2016, 11:06:30 AM6/21/16
to golang-nuts
>> What I mean is that most people who have followed the past generics discussions already know it's possible to implement them with efficient memory use and performance. So they don't need to worry much about that. 

Thank you for explanation.

>> can they be implemented without making Go a much more complex language than it is now

What is a measure of the syntax complexity in the Go language.
What this means and how they should be measured such a relative criteria?
- Much more complex
- More complex
- Slightly more complex
- Little bit more complex

Also how you personally measure these cognitive loads?

1.Sample (pattern)

type KeyValuePair interafce {
  key interface{}
  val  interface{}
}

2.Sample (pattern)

type KeyValuePair<K, V> interafce {
  key K
  val  V
}

What about that a value of the #2 sample is more complex than #1 sample on the 146%?
With permissible error of no more than 5%.

I think that possible we should to start a discussion about that problem (complexity measurement of the Go language perception).

I'm not kidding.

Egon

unread,
Jun 21, 2016, 11:50:33 AM6/21/16
to golang-nuts, andrew...@gmail.com
On Tuesday, 21 June 2016 17:18:04 UTC+3, andrew...@gmail.com wrote:
>> increase in cognitive load to decipher chains of type definitions.

Sorry, but who are members of this mail lists?
This is a first time when I hear about such loads such as the `cognitive load`.


Also I am possible here a single person who does not know anything about the `cognitive load to decipher chains of type definitions`.

See http://www.boost.org/, although it is at the very far end of the spectrum (for example Dijkstra algorithm). Or, examples in "Modern C++ Design Generic Programming".

Or if I take the trivial example and write some facilitated code (I know that concurrent map API wouldn't look like that):

package conc

type Map<K,V> struct {
  Entries []Entry<K,V>
}
type Entry<K, V> struct {
  Key K
  Value V
}

package main

func PrintItems<T>(m conc.Map<Person, T>, format func(T)string) {
  var entry conc.Entry<Person, T>
  for m.Next(&entry) {
    fmt.Println(entry.Key.Name, format(entry.Value))
  }
}

func main() {
  m := conc.NewMap<Person, string>()
  PrintItems<string>(m, func(x string) string { return x })
}

And I'm not doing anything complicated, yet. This approach doesn't properly isolate the generic parts and you get code that is completely mixed with it.

I am mostly business man and a programmer just for my own requirements.
I love to implements parsers, code generators, some effective algorithms for solving some problem at the most effective time.
Also my work includes a diagnose the failures and locate them.
And I want to apologize for my illiteracy (thick-headed), but I do not really was expecting that all members of this mail list worry mostly about the ` increase in cognitive load to decipher chains of type definitions` and don't worry about other things.
I am sorry.

xingtao zhao

unread,
Jun 21, 2016, 2:11:40 PM6/21/16
to golang-nuts, parais...@gmail.com, andrew...@gmail.com
For example:

type Int int
func (i Int) Add(j Int) Int { return i + j }

type Number<T Number<T>> interface {
    Add(j T) T
}

If we want Int satisfy the interface Number<Int>, we have to create two versions of function Int.Add, one for concrete type Int, one with generic type T

And if we want to support:

var i Int
var v interface{} = i
var n Number<Int> = v.(Number<Int>)

We will have to create a stub for Int.Add in itable of Number, and dynamically adapt the parameter types at runtime.

Axel Wagner

unread,
Jun 21, 2016, 2:56:01 PM6/21/16
to andrew...@gmail.com, golang-nuts
The issue is, that a "KeyValuePair<K, V>" (no matter if you implemented it via generics or like you mention via interfaces) is a fundamentally useless type and generics encourage people to add useless types. A "KeyValuePair<K, V>" is a "struct { Key K, Value V }", plain and simple. It's not an interface and it's not a generic type, it's simply a struct.

I agree, that generics are useful, but they seem to be mainly useful for people who write "frameworks", not so much for people who write "applications" and so far, go is pretty much a language for the latter. Whenever people try to justify generics, they do it from a "I want to write a framework" POV - and whenever I miss generics, I do because I'm currently writing a framework of some kind.

Generics empower abstractions, but most programmers are very bad at building abstractions, so if you make it easy to build abstractions, you will end up with a lot of bad abstractions (have a look at java). So, to a certain degree, the value of go is, to *not* make building abstractions overly simple. That leads to abstractions being used only where they are essential and being left out where they are superfluous. This is where reduced cognitive overhead comes into play - limiting the levels of abstractions that people need to deal with to the bare essentials. Java is bloated and hard to use, not because the language is bad, but because it has a history of programmers building bad abstractions into it which gets stacked on top of each other. So, yes, if you compare a bad abstraction using interfaces with a bad abstraction using generics, generics will, in general, compare very well. But you just shouldn't build the bad abstraction in the first place.

The second concern with complexity is the spec. The exact behavior and semantics of generics need to be spec'ed and useful generics need a lot specification. For example, the current rules for type inference can be understood completely just by looking at a single expression and it's type and it's correspondingly simple to implement and spec. Generics usually need more powerful type inference methods to not be cumbersome, which will take up a lot of space in the spec. As humans, just like computers, have very little memory, the time it takes to understand the spec will grow superlinear with the length of it, due to frequent cache misses, so a long spec will significantly increase the time needed to learn the language. In the same vein, to understand a language, you need to know about interactions between it's different concepts, not just the concepts itself, so the needed space and time complexity to learn a language also grows quadratic in the number of concepts in the language (in general). All of that contributes to why people are wary of adding new concepts to go - the costs in terms of understanding and learning the language are huge and they grow very much superlinear in the number of concepts added, so each added concept must be carefully examined (I know go for years and I still learn new things about interactions between different concepts all the time).


I sometimes miss generics, yes, but I also believe adding them will make the language significantly harder to learn and will significantly worsen the quality of go code in the wild, so it would likely eliminate the reasons I like go currently (which is that go code is usually of exceptionally high quality, uniform and easy to understand).

--

xingtao zhao

unread,
Jun 21, 2016, 3:38:16 PM6/21/16
to golang-nuts, andrew...@gmail.com
I totally agree that I always want generics mainly when I am writing a framework. But I do not agree with you on that "go is pretty much a language for writing applications". I don't think go is powerful enough that we do no need any framework. I don't think we can always develop applications from the scratch. And I do think it is a big help for go if we have a lot of frameworks/libraries right on your hand. I do think it is a waste of engineer time to develop all the tools from scratch. Currently there are many frameworks/libraries have to use a lot of reflections just because generic is missing. This both makes the framework/library hard to use, slower, and less safe (a lot of runtime type checking which should/could be done at compiling time).

Think about how many people were asking: For the following types, how can I convert []Fool to []Barer, and via verse?

type Barer interface { Bar() }
type Foo struct{ ... }
func (f *Foo) Bar() { ... }
func Process([]Barer) { ... }

We have to tell them write a helper function to convert it. While in fact, if we have generic, and declare Process as:

func Process<T Barer>([]T) { ... } 

we do not need this kind of conversions at all. Interface is perfect for abstraction of the direct layer, while it is not good enough for abstractions inside of slice, map, chan, and structure, especially for slices which are used a lot in go code. I think this is a very big requirement by the users who is writing "applications" as well.

Axel Wagner

unread,
Jun 21, 2016, 4:35:30 PM6/21/16
to xingtao zhao, golang-nuts, andrew...@gmail.com
On Tue, Jun 21, 2016 at 9:38 PM, xingtao zhao <zhaox...@gmail.com> wrote:
I totally agree that I always want generics mainly when I am writing a framework. But I do not agree with you on that "go is pretty much a language for writing applications". I don't think go is powerful enough that we do no need any framework. I don't think we can always develop applications from the scratch.

I agree. But I don't think that "reuse" implies "framework", lest "requires generic". For example, you can write very easily reusable components based on net/http. Much, much more so, than what we probably would have with generics. For reuse you need to build abstractions, but the limited way in which we can abstract in go requires you to think hard about *good* ones.
 
And I do think it is a big help for go if we have a lot of frameworks/libraries right on your hand. I do think it is a waste of engineer time to develop all the tools from scratch. Currently there are many frameworks/libraries have to use a lot of reflections just because generic is missing.

No, they don't have to, they want to, because thinking about good abstractions is hard and reflection is the only way go gives you to write bad and ultimately useless abstractions.
 
This both makes the framework/library hard to use, slower, and less safe (a lot of runtime type checking which should/could be done at compiling time).

I disagree that safety for it's own sake is a useful goal. I consider type safety like I consider memory safety: You definitely want the vast majority of your code to be memory safe, but sometimes you have to use unsafe in limited cases and that's fine and it doesn't compromise the safety of the program as a whole. If the unsafe amount of code is small enough, it's easily verified by hand. The same goes for type-safety. It is fine to lose some type-safety in limited amounts of code, as long as you are careful, review it and make the limitations simple to understand.

A good example are the encoding packages - that's a decent place to use reflection, even though it limits your static type safety. But there is limited API surface that you need to understand, usually just two functions or so. It's easy to still write safe programs that way.
 
Think about how many people were asking: For the following types, how can I convert []Fool to []Barer, and via verse?

type Barer interface { Bar() }
type Foo struct{ ... }
func (f *Foo) Bar() { ... }
func Process([]Barer) { ... }

We have to tell them write a helper function to convert it.

No, we don't. We have to write a loop. We might opt to write a helper if we need to do that very often. But, honestly, we almost never do.
 
While in fact, if we have generic, and declare Process as:

func Process<T Barer>([]T) { ... } 

we do not need this kind of conversions at all. Interface is perfect for abstraction of the direct layer, while it is not good enough for abstractions inside of slice, map, chan, and structure, especially for slices which are used a lot in go code. I think this is a very big requirement by the users who is writing "applications" as well.

I disagree. You are, again, writing frameworks. A perfectly understandable and readable loop that turns a []Foo into a []Barer is four lines of code. A helper function is six. You probably need to do that on average at most .1 times per application that you write. I don't consider .6 lines of code per application saved a significant saving that could serve as an argument here.

And you are also misrepresenting, what would happen. Because realistically (look at existing languages with generics) the signature of that function would more be something like

func Map<Iterable<T>, O>(in T, f func(T) O) Iterable<O>

Just to save .6 lines of code per application.

Alex Bligh

unread,
Jun 21, 2016, 6:07:09 PM6/21/16
to Axel Wagner, Alex Bligh, andrew...@gmail.com, golang-nuts

On 21 Jun 2016, at 19:55, 'Axel Wagner' via golang-nuts <golan...@googlegroups.com> wrote:

> The issue is, that a "KeyValuePair<K, V>" (no matter if you implemented it via generics or like you mention via interfaces) is a fundamentally useless type and generics encourage people to add useless types. A "KeyValuePair<K, V>" is a "struct { Key K, Value V }", plain and simple. It's not an interface and it's not a generic type, it's simply a struct.
>
> I agree, that generics are useful, but they seem to be mainly useful for people who write "frameworks", not so much for people who write "applications" and so far, go is pretty much a language for the latter. Whenever people try to justify generics, they do it from a "I want to write a framework" POV - and whenever I miss generics, I do because I'm currently writing a framework of some kind.

Not sure that's fair.

For background, I'd quite like generics and liked Ian's last proposal quite a bit, but I also appreciate the cleanliness of go.

An recent example, in an real life application, where generics would have been useful is as follows.

I had a cache type thing built around a red-black tree, or more precisely two red black trees. The items were structs, and I needed one RB tree to be ordered by time of insertion (then key, for uniqueness), and one by key. One red-black tree dealt with expiries, and one with finding the item. In the absence of generics, I needed to embed the struct in a 'ByTime' and 'ByItem' struct, each of which implement a different 'LessThan' method. And everything that accessed the trees had a bunch of ugly casting from an interface{} to the relevant type. This does not make for readable code in any form. A RB tree that was implemented as a generic (and perhaps took a 'LessThan' function as a lambda) would have been far far cleaner in *my* code.

So saying it's all about being useful for people who write "frameworks" is unfair. It would actually have made the application code (i.e. the library user) far easier to read.

Of course it's possible to generate foolish uses (I would however note in passing that even with KeyValuePair things might not be as simple as you think, as it would (presumably) require comparability of K type items); however, it's also possible to perform stupidity with existing go syntax. The question is whether generics encourage it.

--
Alex Bligh




ad...@gardener.com

unread,
Jun 21, 2016, 7:20:49 PM6/21/16
to golang-nuts, andrew...@gmail.com


On Tuesday, June 21, 2016 at 2:56:01 PM UTC-4, Axel Wagner wrote:
The issue is, that a "KeyValuePair<K, V>" (no matter if you implemented it via generics or like you mention via interfaces) is a fundamentally useless type and generics encourage people to add useless types. A "KeyValuePair<K, V>" is a "struct { Key K, Value V }", plain and simple. It's not an interface and it's not a generic type, it's simply a struct.

I agree, that generics are useful, but they seem to be mainly useful for people who write "frameworks", not so much for people who write "applications" and so far, go is pretty much a language for the latter. Whenever people try to justify generics, they do it from a "I want to write a framework" POV - and whenever I miss generics, I do because I'm currently writing a framework of some kind.

generics would greatly reduce the work required to reproduce or possibly eliminate all work when building container types.

 
Generics empower abstractions, but most programmers are very bad at building abstractions, so if you make it easy to build abstractions, you will end up with a lot of bad abstractions (have a look at java). So, to a certain degree, the value of go is, to *not* make building abstractions overly simple. That leads to abstractions being used only where they are essential and being left out where they are superfluous. This is where reduced cognitive overhead comes into play - limiting the levels of abstractions that people need to deal with to the bare essentials. Java is bloated and hard to use, not because the language is bad, but because it has a history of programmers building bad abstractions into it which gets stacked on top of each other. So, yes, if you compare a bad abstraction using interfaces with a bad abstraction using generics, generics will, in general, compare very well. But you just shouldn't build the bad abstraction in the first place.
 
this is true with almost every feature of the language, so its absurd to even use this as an argument. generics would greatly reduced computational complexity.
 

The second concern with complexity is the spec. The exact behavior and semantics of generics need to be spec'ed and useful generics need a lot specification. For example, the current rules for type inference can be understood completely just by looking at a single expression and it's type and it's correspondingly simple to implement and spec. Generics usually need more powerful type inference methods to not be cumbersome, which will take up a lot of space in the spec. As humans, just like computers, have very little memory, the time it takes to understand the spec will grow superlinear with the length of it, due to frequent cache misses, so a long spec will significantly increase the time needed to learn the language. In the same vein, to understand a language, you need to know about interactions between it's different concepts, not just the concepts itself, so the needed space and time complexity to learn a language also grows quadratic in the number of concepts in the language (in general). All of that contributes to why people are wary of adding new concepts to go - the costs in terms of understanding and learning the language are huge and they grow very much superlinear in the number of concepts added, so each added concept must be carefully examined (I know go for years and I still learn new things about interactions between different concepts all the time).

this comment is more mumbo-jumbo
 


I sometimes miss generics, yes, but I also believe adding them will make the language significantly harder to learn and will significantly worsen the quality of go code in the wild, so it would likely eliminate the reasons I like go currently (which is that go code is usually of exceptionally high quality, uniform and easy to understand).

another absurd comment. generics would greater benefits and reduce the complexity of code.

Marcus Yip

unread,
Jun 21, 2016, 7:38:05 PM6/21/16
to Egon, Axel Wagner, Rodrigo Kochenburger, Ian Lance Taylor, parais...@gmail.com, Ian Davis, Henry, Micky, Alex Bligh, xingtao zhao, 'Axel Wagner' via golang-nuts, andrew...@gmail.com, ad...@gardener.com
On Tuesday, June 21, 2016 at 2:56:01 PM UTC-4, Axel Wagner wrote: The issue is, that a "KeyValuePair" (no matter if you implemented it via generics or like you mention via interfaces) i

ad...@gardener.com

unread,
Jun 21, 2016, 7:44:56 PM6/21/16
to golang-nuts
generics would greatly improve code reuse for many algorithms- this one fact alone trumps all opposition. In fact, go's builtin container types have generics-like functionality already. Imagine arrays, slices, maps without this functionality- exactly, enough said. the arguments against generics are mind-numbing.

with that said, the best approach for those seeking generics in go is to produce preprocessers chained with the existing go tools, and wrapping that chain in a new toolset. this approach would reduce the effort needed to produce and maintain this new toolset. Most of the useful generic-like functionality is easily produced at compile time. I doubt there would be any benefit pushing generics into the language proper.

I'd also like to think generics could be introduced very similar to the how unsafe is designed- as a compiler feature.

Matt Ho

unread,
Jun 21, 2016, 10:12:09 PM6/21/16
to golang-nuts, ad...@gardener.com
I think I have to agree with most of the posters here about generics.  In theory, I miss them.  However, in practice, I find that there are usually only a few times during the course of a project where I wish I had generics.  And then after writing a few lines of code to do what the generic would have done, I find I stop missing them.  

M



andrew...@gmail.com

unread,
Jun 22, 2016, 12:21:49 AM6/22/16