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.
>> 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
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.
>> 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.
--
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.
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).
key K val V } func accepInt(i int) { } func some() string { foo := &Foo<string, int>() // some code // Compile error: `key` is not `int` acceptInt(foo.key) // Compile error: `key` (int) cannot be compared with `string` if foo.key == foo.val { } // Compile error: `key` is not `string` return foo.val } func baz(foo Foo<K, V>) bool { // Compile error: `key` (K) cannot be compared with `V` return foo.key == foo.val }
type Foo<K, V> interface { Get(key K) V Set(key K, val V) } func foo() (int, Foo<string, int>) { foo := &Foo<string, int>{} // ... foos := []Foo<K,V>{} // ... k := "London" // ... return 55, foo2(foos, 55, k) } // This code is type safe and reusable foo2<K, V> (foos []Foo<K,V>, i int, key K) (V, Foo<K,V>) { foo := foos[i] return foo.Get(key), foo }
--
type Foo<K, V> interface { Get(key K) V Set(key K, val V) } func foo() (int, Foo<string, int>) {
// Typo: foo := &Foo<string, int>{} foo := &fooStruct<string, int>{}
Candidate design features for software reuse include:
We continue the discussion about the dangers of `to be both type safe *and* reusable`?
Me and most of us MUST focusing our energy on writing high quailty and reusable software.
Want write software only for himself? Not a problem, your energy are your energy.
Here we do not discuss: How to coding in Go language without generic programming.I think that it should be different topic with appropriate title.
In the years following the publication of K&R C, several features were added to the language, supported by compilers from AT&T and some other vendors. These included:
void
functions (i.e., functions with no return value)struct
or union
types (rather than pointers)struct
data typeslong long int
and a complex
type to represent complex numbers), variable-length arrays and flexible array members, improved support for IEEE 754 floating point, support for variadic macros (macros of variable arity), and support for one-line comments beginning with //
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).
>> The version without generics is necessary to compare how well a particular generics approach improves the current language and code.I am sorry but this is obviously that any useful feature (in the context of solving problems) improves the current language and code.
Another question: how new (version of the) language will look cosmetically with this newly added feature.I found that the second question here more important then the first.It is obviously correct that the code with a generics (in appropriate place) would be more useful then other code.
But here I undestand one interesting thing which states: Some obviously useful feature can significantly spoil the overall picture of language.For me this is very strange only because even a C language also has been improved after when it initially was introduced.
In the years following the publication of K&R C, several features were added to the language, supported by compilers from AT&T and some other vendors. These included:
void
functions (i.e., functions with no return value)- functions returning
struct
orunion
types (rather than pointers)- assignment for
struct
data types- enumerated types
That can't be the litmus for language feature inclusion; if it was, Go would resemble ASM.
In my personal experience, something North of 50% of my non-trivial applications could have been more simple with some form of generics allowing me to reduce code duplication. In particular, any application dealing with demarshaling of data from a large set of similar functions (e.g. web calls) are good examples. Have 30 functions to make the same web calls and perform the same demarshaling calls -- especially where the web call may be more complex, as in a SOAP call -- does not make the code cleaner, easier to cognitively parse, or more safe. Indeed, in almost every such case, generics reduce code duplication and make the code safer from bugs, and easier to maintain.
Indispensable? That's subjective, and very few language features satisfy that requirement. For me, the strongest argument against it is that if the core Go team can't think of a way to implement it cleanly and efficiently, then I trust it's a hard problem. I'm sure they've looked at it; it must be the single most commonly requested, hashed-over, and proposal-backed feature.
--- SER
And yet the 'K' and 'V' within the struct are generic types. A struct is never simple. That's its whole point.
A lot of these 'applications' that you think are the language's main target are built on top of libraries, including stdlib, written by that same language. And these same libraries can benefit from having generics, once again, including stdlib.
I've found this type of thinking quite poisonous. The "lets bereave ourselves of something universally lauded as useful, because it _might_ be used in the wrong manner by some people" mentality is just wrong and prevents any kind of useful evolution under any context.
And yet this hasn't stopped people from successfully learning and utilizing languages that have generics are part of their specifications.
The fact still remains that unlike a lot of other proposed languages additions that are immediately dropped, generics are still open for discussion by the language developers themselves.
I'm sorry, but that's just fearmongering. Unless you have some kind of magic crystal ball, there's now way you'd know how the quality of go code will change. You don't even know what the implementation will look like ...
--
if a < b {return b}return a
But Michael, that implementation isn't right for floats! Check out the actual source for math.Max.
Yes, for floats with NANs and the like, you must specialize. My replacement for the standard library sort (3x to 10x faster in the common case) had to have more macro generators than expected just to handle the "uncomparables" in float32 and float64.
You are right about that issue.
P.S. By the way, Hi! Good to hear from you.
This is why I like this example. Generics induce people to write the wrong thing instead of thinking about the right thing. When you start thinking of the right thing, you often discover better solutions.
I think the sort package's way of doing sorting and binary search are so clearly better than almost any generic implementation I've seen, that I think generics are a bad idea for this reason alone.
Thomas
This is why I like this example. Generics induce people to write the wrong thing instead of thinking about the right thing. When you start thinking of the right thing, you often discover better solutions.
I think the sort package's way of doing sorting and binary search are so clearly better than almost any generic implementation I've seen, that I think generics are a bad idea for this reason alone.
Having completed an earlier project, my colleagues and I sat down to review the team performance and what we could do better next. My colleague proposed an intriguing idea and we decided to randomly pick two components of our past projects that could be implemented independently from the rest of the projects and were easy to test. We rewrote the components from using OO (object oriented) to using PP (procedural). I was surprised with the result. The number of lines was reduced by about 30%. We were able to see things that were hidden behind the complex interaction of objects before, and simplified the codes considerably. Performance was improved by about 5%. This was no surprise because I always knew that OO has a bit of an overhead and the code simplification also helps. Maintainability is a bit subjective, but my colleagues and I think that the PP codes are easier to read and simpler to understand. The main obstacle in OO is with understanding the model employed. In our case, we has components that were outsourced to some offshore developers before, and we often had to rely on external documentation to understand the model they use. What was intuitive to them was not very intuitive to us. With PP, my other colleague who was not involved with rewriting the codes thinks that our PP codes can be understood by reading the source codes alone. However, the main selling point to me is the reduction in loc. For every 1000 lines of OO codes, we can save about 300 lines of codes with PP.This experience makes me question many things and wonder whether the advances in programming for the past 40 years are really improvements over earlier methodologies. Everything I was taught of in college now seems questionable. Some people may argue that -in my case- it is probably a case of bad OO design and that if they get it right, OO should yield substantial benefits, especially when it comes to maintainability. The key phrase here is 'if they get it right'. OO allows abstraction, including over-abstraction, that is limited only by imagination. How do you know when you reach the right design?
If this sort of code is written very often, the "prove it to me" argument against generics will be less effective.
So, how about some more examples?
Earlier someone asked about real world examples. The obvious standard library was mentioned. It only took me a few seconds to remember Jan Mercl's interval package. A quick look suggests that the current 1200+ lines of code would be reduced to about 250.
Just to be clear what I consider a great example for analyzing generics:
2. 3+ real-world packages/applications use that packageBy real-world here I mean it wasn't written for learning or fun, but rather solving a concrete problem.
4. the package is implemented "in the best way"e.g. linked list would be a bad example, because it's usually a bad solution in the first place
On Wednesday, June 29, 2016 at 2:31:59 AM UTC-4, Egon wrote:Just to be clear what I consider a great example for analyzing generics:2. 3+ real-world packages/applications use that packageBy real-world here I mean it wasn't written for learning or fun, but rather solving a concrete problem.How about one (a single) production application with 106 types, all sharing three common functions identical except for the type categories they operate over?
1411 LOC that could not only be reduced to around 300 (interesting that my numbers are similar to the one @Mandolyte came up with from 'interval') -- although, I'll state again that saving LOC isn't the killer feature of this request; it's the fact that a bug fix or other change has to be made in 25 different places.4. the package is implemented "in the best way"e.g. linked list would be a bad example, because it's usually a bad solution in the first placeMy example, detailed in another thread, is deserializing XML content from a legacy SOAP application, and normalizing and simplifying the data structures.
The 106 types, 25 functions, and 1411 LOC is just for the SOAP & deserializing. There's a whole other chunk of code dealing with the conversion from type to type that I used introspection for (that was a mistake!) rather than deal with several more thousand lines of duplicate code.My case is extremely error prone and difficult to maintain safely. I'm afraid to touch the code. There's enough customization on a couple types that templating wouldn't be much help. I'd love suggestions of a better way to implement than copy/paste. I can't even use introspectionThe criteria you lay out assumes a public library; bespoke, single-purpose applications should also be able to be used as valid examples.
The suggestions of generics discussed here and in the referenced documentation, will it be possible to compile the "Go-with-generics" language into some idiomatic Go and compile it with the standard compiler? (I guess what idiomatic means is the real question here..). Or would it break idiomatic Go?
On Wednesday, June 29, 2016 at 3:42:51 PM UTC+2, Øyvind Teig wrote:The suggestions of generics discussed here and in the referenced documentation, will it be possible to compile the "Go-with-generics" language into some idiomatic Go and compile it with the standard compiler? (I guess what idiomatic means is the real question here..). Or would it break idiomatic Go?it can be done with using unsafe
I assume the scope of the discussion about introducing generics is not how Go-generics might hypothetically be transformed to idiomatic Go. I find no reference to "unsafe" in neither of these (already mentioned here):
- "Proposal: Go should have generics" by Ian Lance Taylor https://github.com/golang/proposal/blob/master/design/15292-generics.md
- "proposal: generic programming facilities" by Andrew Gerrand https://github.com/golang/go/issues/15292
Neither do I find any mention of "reflect", which I assume might be relevant.From this I infer that adding generics to Go is a rather large affair. It also looks like that in the pages above.More interesting, I don't see formal modeling verification mentioned in any of the generics-documents above, neither in this recent thread:
- "formal verification in Go? someday perhaps?" at https://groups.google.com/forum/#!topic/golang-nuts/MVITBF3TcOE
I know that Rob Pike was involved with formal verification since long ("Bell Labs and CSP Threads", by Russ Cox, see https://swtch.com/~rsc/thread/) and that this background was colouring much of how Go ended up. Has generics been discussed along this line: that Go-generics might be more/less suitable for formal verification?
Here is my proposal for generic in go: https://docs.google.com/document/d/1nO7D15c2B3eq2kF62C0yUs_UgpkyPL2zHhMAmlq1l98/edit?usp=sharingMany 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
type S<T> struct {
I1 int
C1 char
T1 T
I2 int
Ts1 [2]T
Is1 [10]int
Ts2 [53]T
C2 char
T2 T
Ts3 [2]T
Tp1 *T
I3 int
}I1 int
C1 char
T1 int8
I2 int
Ts1 [2]int8
Is1 [10]int
Ts2 [53]int8
C2 char
T2 int8
Ts3 [2]int8
Tp1 *T
I3 int
}type S<T> struct {
I1 int
C1 char
I2 int
Is1 [10]int
C2 char
Tp1 *T
I3 int
//////////
T1 T
Ts1 [2]T
Ts2 [53]T
T2 T
Ts3 [2]T
}Thanks!
>> Now imagine that T=int8Generic struct always has the same size only because it uses pointers.type S<T> struct {
elem *Tarr []T}
array unsafe.Pointer len int cap int }
type sliceType struct { rtype elem *rtype }
// eltSize wide (in bytes). func arrayAt(p unsafe.Pointer, i int, eltSize uintptr) unsafe.Pointer { return unsafe.Pointer(uintptr(p) + uintptr(i)*eltSize) }
func set(slice []string, int i, val string) { slice[i] = val } func Set(list List<string>, int i, val string) { list.Set(i val)
func (r *List) Set(i int, val E) { r.slice[i] = val }
Compiler, in this generic code, does not know what type of value will have a variable `val E` at runtime.
Here arrives is a reasonable question: "Should compiler perform type checking before performing such assignment?".
My answer is: ". No. In such case compiler should not perform type checking at runtime All required type checks already was performed previously."
Here is a proof:
type List<E> struct { slice []E }This means that the compiler is the guarantor of that the for some instance of the type `List<E>` the field `slice []E` with some type assigned to `E` of `List` will have the same type assigned to `E`.
Here is a proof:
list := &List<string>{} // or list := NewList<string>() // where `NewList` is func NewList<E>() *List<E> { i := &List<E>{} i.slice = make([]E, 5) return i }In both cases the compiler ensures that the every member of the generic type, which has the type `E`, will be assigned only to the specified type argument (in our case, `string`).
Now look again on our questionable code which possible can be slow.
func (r *List) Set(i int, val E) { r.slice[i] = val }What we (and compiler) know about it?
- The receiver (r *List) has some unknown type- The parameter (val E) has some unknown type but we know that this type is the same as `E` in `List<E>`- The field (List<E>.slice []E) of the receiver has some unknown type but we know that this type is the same as `E` in `List<E>`
Should compiler perform some (possible slow) type checking at runtime to compare the type `E` with type `E`?
What does this means?
This means that this generic code also can fast as it possible.
P.S.
The slow code mostly goes in the instantiation process but all the benefits from the use of new types cost to put up with it (and this is not very slow, and do not forget that arrays and maps in Go also has the "generics code" but it hidden from programmers).
Finally.
Question: "Does this code (with two parametrized types) requires the runtime type checks?"
var slice []int{} var list List<int>{} var v1 int v1 = slice[1] v1 = list.Get(1) // List<E> with the argument `E` set to `int` always returns the `int`No. But will be executed sligtly slower than this code:
v1 = funcWhichAlwaysReturnsOnlyInt(1)
But why?
This is because generic function always returns the pointers that need to dereference:
v1 = list.Get(1)
// Compiled to_temp0 := list.Get(1) // unsafe.Pointer, compiler know that it points to `int` v1 = *(*int)(_temp0)
>> The generic dilemma is this: do you want slow programmers, slow compilers and bloated binaries, or slow execution times?(The Java approach.) Box everything implicitly.
This slows execution.I just want to add some clarification.This does not slows a whole execution.All that works fast will remain the same fast.Of course, new (generic) code will be slightly slower by the following reasons.1. Information about the types will be obtained not at the compile time but at the runtime (this requires a some time to access this information).
>> Go has to run in environments where runtime code-generation / modification is not allowed.
Where you find that I wrote about that?
>> The generic dilemma is this: do you want slow programmers, slow compilers and bloated binaries, or slow execution times?(The Java approach.) Box everything implicitly.
This slows execution.I just want to add some clarification.This does not slows a whole execution.All that works fast will remain the same fast.Of course, new (generic) code will be slightly slower by the following reasons.1. Information about the types will be obtained not at the compile time but at the runtime (this requires a some time to access this information).2. Type checking will be performed not at compile time but at the runtime (this also requires a some time to comparsion).This is true but...1. Information about the types can obtained at the compile time very fast
2. Requirements of performing the type checking at the runtime will not occur so often
P.S.
The slow code mostly goes in the instantiation process but all the benefits from the use of new types cost to put up with it (and this is not very slow, and do not forget that arrays and maps in Go also has the "generics code" but it hidden from programmers).
Finally.
Question: "Does this code (with two parametrized types) requires the runtime type checks?"
var slice []int{} var list List<int>{} var v1 int v1 = slice[1] v1 = list.Get(1) // List<E> with the argument `E` set to `int` always returns the `int`No. But will be executed sligtly slower than this code:
v1 = funcWhichAlwaysReturnsOnlyInt(1)
But why?
This is because generic function always returns the pointers that need to dereference:
>> Go has to run in environments where runtime code-generation / modification is not allowed.Where you find that I wrote about that?The generic code (not a parametrized code) executed in the same manner as the reflection works.
But with a single and very big differnce.The reflection is slow because it designed to be "for all occasions".The generic code are:- Analysed by the compiler- Optimised by the compiler- Unnecessary code eliminated by the compiler (maximally reduced some procedures to minimal overhead)
This is a main reason why the compiled generic code (not a parametrized code) in the most cases not a much more slower than a non-generic code (but not always).
P.S.Seriously, where you found that I wrote something about the requirements of the "runtime code-generation / modification" in my examples?Yes, my English is terrible but I never did not mention these notions.
How does the compiler know sizeof(T)?
If it's passed as an argument or saved together with the struct or something in that regard, you end up with the "java approach", if you have the compiler specializes the function per size, you end up with the "C++ approach" (also, in either case, there is no need for the "only pointers are allowed" kludge), from
// mapaccess1 returns a pointer to h[key]. Never returns nil, instead // it will return a reference to the zero object for the value type if // the key is not in the map. // NOTE: The returned pointer may keep the whole map live, so don't // hold onto it for very long. func mapaccess1(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
Returned value will be obtained in parametrized code (where m is map[Foo]Baz) from the pointer because both (caller and callee which is instance of the map[Foo]Baz) operates with the values of the same types.
The `map` does not know the types and its sizes. It only know that it instantiated (by the type argements) at the runtime "to be ready to work for these types".
This approach is called "generices programming" and it used in Go language in the following types:
- Channels
- Slices
- Maps
P.S.
// NOTE: The returned pointer may keep the whole map live, so don't // hold onto it for very long.
This note not for the programmers. This not for the developers.
This is because programmers always gets a value from the dereferenced pointers but developers can call this function directly.
How does the compiler know sizeof(T)?
interesting question. About obtaining sizeof(T). The sizeof(T) is known from the call site calling the generic function, in particular from the type of variable that's passed to the generic function.
After obtaining sizeof(T), it needs to be passed to the function. I'm no call convention expert so In my experiment I've did the stupid simplest thing to make it work, Before a call to a generic function, I emitted runtime setter to store it, then in function i get it from runtime. there is race, sure but can be swept under the rug by putting it to thread specific place, maybe G(goroutine) .
If it's passed as an argument or saved together with the struct or something in that regard, you end up with the "java approach", if you have the compiler specializes the function per size, you end up with the "C++ approach" (also, in either case, there is no need for the "only pointers are allowed" kludge), from
If go goes the Java way but does it somehow differently. Something clever but very practical. I believe this is possible.
Perhaps modify Golang's 2.0 calling convention, that EVERY function must get upon compilation one extra invisible-to programmer argument that is pointer that
when said function is generic, points to sizeof(T) storage place .
If you're calling to non-generic function it's just NULL, and it seems like a waste? but does it slow it down everything so much??
>> How does the generic function know the size of the return value?All your judgement are not correct only because you ask not a correct questions.
They return pointers to appropriate values.
Or you really think that the `map` also returns a value instead of pointer?
How it (map) can know the size of the return valueThe `map` in Go language also generic type.
Can I ask the question out of turn?Ok. I ask.What wrong in Java way?What wrong in C# way?
Of course, they have a boxing/unboxing concepts.
Does this means that boxing/unboxing is very, very, very slow operations and should not be used even in Java or in C#?But they used. They used at least in generics programming in the languages.
Does this means that generics programming in these languages are very, very, very slow?
Does this means that generics programming in other languages are very, very, very fast?
Generics programming are always compromise between the performance and universality.
Where universality always means an usability.
C++ way is out of the scope.
if h.buckets == nil { h.buckets = newarray(t.bucket, 1) }
How new array know the size?
This is easiest question in the world.
Answer is: From a given type.
// implementation of make builtin for slices func newarray(typ *_type, n uintptr) unsafe.Pointer { flags := uint32(0) if typ.kind&kindNoPointers != 0 { flags |= flagNoScan } if int(n) < 0 || (typ.size > 0 && n > _MaxMem/uintptr(typ.size)) { panic("runtime: allocation size out of range") } return mallocgc(uintptr(typ.size)*n, typ, flags) }
func mapaccess1(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
P.S.
The `map` is very well oprimized in Go language (it has more than one method for different groups of types).
But this does not means that the code generated for use "a single method for every groups of types" approach would be much more slower.
--
By the way, I at appreciate the civil and earnest tone of this thread, passion and politeness can go to together.
Here is my proposal for generic in go: https://docs.google.com/document/d/1nO7D15c2B3eq2kF62C0yUs_UgpkyPL2zHhMAmlq1l98/edit?usp=sharingMany 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!
The appeal of generics is probably a false appeal.