Variables that can hold types (Generics proposal)

1,021 views
Skip to first unread message

Zé Luís

unread,
Feb 29, 2012, 2:17:28 PM2/29/12
to golang-nuts
The idea is to create a type that can hold *any type* and can be used
to create type variables, like the following.

package main

// new reserved word "generic" or "anytype" or whatever
// creates a variable that can hold a type
var GenTypeVar generic

func NewVector(T generic, length int) []T {
return make([]T, length)
}

func main() {
GenTypeVar = string // GenTypeVar holds type string
myvar1 := GenTypeVar("Blah") // ok, myvar gets type string
holded by GenTypeVar
myvar2 GenTypeVar = "Blah" // ok, the same
myvar1 = 1 // this panics

vec1 := NewVector(int32, 2) // the T parameter holds type
int32 and vec1 gets type []int32
vec1[0] = 1 // ok
vec1[1] = "Blah" // panic

vec2 := NewVector(GenTypeVar, 2)
vec2[0] = "Blah" // ok
vec2[1] = 1 // panic
}

Hotei

unread,
Feb 29, 2012, 6:21:09 PM2/29/12
to golang-nuts
Not to beat a dead horse or anything, but before generics get added I
think you'd have to convince the language designers that generics are
desirable. A lot of people are finding the language works just fine
without them. I for one don't want to give up either the type-
checking safety or awesome compile speed the current language
provides. Your mileage may vary.

Kyle Lemons

unread,
Feb 29, 2012, 6:27:45 PM2/29/12
to Hotei, golang-nuts
On Wed, Feb 29, 2012 at 3:21 PM, Hotei <hote...@gmail.com> wrote:
Not to beat a dead horse or anything, but before generics get added I
think you'd have to convince the language designers that generics are
desirable.   A lot of people are finding the language works just fine
without them.  I for one don't want to give up either the type-
checking safety or awesome compile speed the current language
provides.  Your mileage may vary.

I obviously can't speak for them, but I don't think they have any particular objection to adding generics to the language, beside the fact that pretty much every known solution has (at least) one of three problems.  Even my proposal, which I obviously thought was pretty cool, had the potential to slow compile and link times dramatically in a generic-heavy application due to multiple parses of syntax trees.

From Russ Cox' blog entry The Generic Dilemma:
The generic dilemma is this: do you want slow programmers, slow compilers and bloated binaries, or slow execution times?

SteveD

unread,
Mar 1, 2012, 5:50:41 AM3/1/12
to golang-nuts
On Mar 1, 1:27 am, Kyle Lemons <kev...@google.com> wrote:
> The generic dilemma is this: *do you want slow programmers, slow compilers
> and bloated binaries, or slow execution times?*

That remains a good one. However, with C++ the second option is hard
to avoid because the standard library uses generic solutions for
everything - e.g. strings and iostreams don't have to be generic
classes.[1] Clearly people got very excited and started using the
generic hammer everywhere. Not only a compile-time cost, but some of
the worst compiler errors ever issued.

If Go developed generics, then we'd hope that the team would show
their usual good taste and not overuse them in the standard library.

That being said, typesafe and efficient generic container classes are
great things, but the decision to use them should be in the hands of
programmers, not library developers.

steve d.

[1] which is what I did when I was naive, and man did it improve my
compile times and executable sizes. But such a tactic is anathema to C+
+ enthusiasts.

Paulo Pinto

unread,
Mar 1, 2012, 8:34:15 AM3/1/12
to golang-nuts
It fails the address the way Ada, Delphi, Modula-3, Eiffel, ML, D,
Haskell, .NET, just to name a few,
implement generic types.

Those languages/runtimes don't have the issues C++ has, because they
have proper module
systems, which allow for optimizations C++ cannot afford. C++ main
issue is that it is supposed
to be able to generate code compatible with archaic linkers.


--
Paulo

On Mar 1, 12:27 am, Kyle Lemons <kev...@google.com> wrote:
> On Wed, Feb 29, 2012 at 3:21 PM, Hotei <hotei1...@gmail.com> wrote:
> > Not to beat a dead horse or anything, but before generics get added I
> > think you'd have to convince the language designers that generics are
> > desirable.   A lot of people are finding the language works just fine
> > without them.  I for one don't want to give up either the type-
> > checking safety or awesome compile speed the current language
> > provides.  Your mileage may vary.
>
> I obviously can't speak for them, but I don't think they have any
> particular objection to adding generics to the language, beside the fact
> that pretty much every known solution has (at least) one of three problems.
>  Even my proposal <http://kylelemons.net/2011/11/generic-types-in-go/>,
> which I obviously thought was pretty cool, had the potential to slow
> compile and link times dramatically in a generic-heavy application due to
> multiple parses of syntax trees.
>
> From Russ Cox' blog entry The Generic Dilemma<http://research.swtch.com/generic>
> :
>
> The generic dilemma is this: *do you want slow programmers, slow compilers
> and bloated binaries, or slow execution times?*

André Moraes

unread,
Mar 1, 2012, 11:35:08 AM3/1/12
to golang-nuts
> func main() {
>        GenTypeVar = string // GenTypeVar holds type string
>        myvar1 := GenTypeVar("Blah") // ok, myvar gets type string
> holded by GenTypeVar
>        myvar2 GenTypeVar = "Blah" // ok, the same
>        myvar1 = 1 // this panics
>
>        vec1 := NewVector(int32, 2)  // the T parameter holds type
> int32 and vec1 gets type []int32
>        vec1[0] = 1 // ok
>        vec1[1] = "Blah" // panic
Instead of panic this shouldn't even compile. I prefer type-safety
instead of generics, and you can do the same thing using interface{}
with a little runtime overhead.

But I think that in order to do that kind of checking the compiler
will slower that it is now, and I prefer fast compilers, otherwise
things like gorun wouldn't be useful.


--
André Moraes
http://andredevchannel.blogspot.com/

Eleanor McHugh

unread,
Mar 1, 2012, 11:47:12 AM3/1/12
to golang-nuts
I've spent a lot of time experimenting with different ways of implementing efficient generic container types, and the main problem I find is with code repetition. I think this could be considerably reduced by allowing the following statement to work as intended:

switch f := sometype.(type) {
case func(int), func(interface{}):
f(100)
}

rather than the current behaviour where f isn't recognised as callable and a compile error occurs.


Ellie

Eleanor McHugh
Games With Brains
http://feyeleanor.tel
----
if _, ok := reality.(Reasonable); !ok { panic(reality) }

Zé Luís

unread,
Mar 1, 2012, 11:54:57 AM3/1/12
to golang-nuts
In package container/vector we have 3 types of containers IntVector,
StringVector and Vector. There should exist only one type of Vector
that can hold any type of data.
The type vector.Vector is of type []interface{}. the Insert method has
a parameter of type interface{}.
So, a vector.Vector variable can hold different types of data at the
same time. I think this is not consistent.

André Moraes

unread,
Mar 1, 2012, 11:59:16 AM3/1/12
to golang-nuts
On Thu, Mar 1, 2012 at 1:54 PM, Zé Luís <joselui...@gmail.com> wrote:
> In package container/vector we have 3 types of containers IntVector,
> StringVector and Vector. There should exist only one type of Vector
> that can hold any type of data.

That package existed in the begining of the language where we don't
had append/copy builtin.

If you just want dynamic containers, stick with slices append/copy.

> The type vector.Vector is of type []interface{}. the Insert method has
> a parameter of type interface{}.
> So, a vector.Vector variable can hold different types of data at the
> same time. I think this is not consistent.

Sure, but in this case you have to be explicity when reading values,
either using a type-switch or a type-assert.

Zé Luís

unread,
Mar 1, 2012, 12:02:26 PM3/1/12
to golang-nuts
André, I agree when you say this shouldn't even compile. My bad. Let's
replace panic with compiler error.

Zé Luís

unread,
Mar 1, 2012, 12:12:33 PM3/1/12
to golang-nuts
Type assert looks like a cast (I know its not the same). It looks like
same casting problem ever. Type-switch is not in the case here, it
should be good to have a compiler error when we try to put a wrong
data type in the wrong place.

On 1 mar, 13:59, André Moraes <andr...@gmail.com> wrote:

Ian Lance Taylor

unread,
Mar 1, 2012, 12:28:34 PM3/1/12
to Paulo Pinto, golang-nuts
Paulo Pinto <paulo....@gmail.com> writes:

> It fails the address the way Ada, Delphi, Modula-3, Eiffel, ML, D,
> Haskell, .NET, just to name a few,
> implement generic types.

In what way do those languages avoid the question that Russ points out?

> Those languages/runtimes don't have the issues C++ has, because they
> have proper module
> systems, which allow for optimizations C++ cannot afford. C++ main
> issue is that it is supposed
> to be able to generate code compatible with archaic linkers.

It's not a module system that is an issue. Go is a compiled language,
so for this discussion we can restrict ourselves to that space. The
question then boils down to: if you have a generic function, do you
generate a version of it specialized for each type (slower compiler) or
do you generate a version that does type switches at runtime (slower
execution time)?

Ian

Zé Luís

unread,
Mar 1, 2012, 12:47:37 PM3/1/12
to golang-nuts
I prefer a slower compiler then a slow program. C and C++ have the
burden of the ancient header files, nothing can beat that. A generic
and modular featured language compiles faster than any language with
header files.

On 1 mar, 14:28, Ian Lance Taylor <i...@google.com> wrote:

Jan Mercl

unread,
Mar 1, 2012, 12:51:14 PM3/1/12
to golan...@googlegroups.com
On Thursday, March 1, 2012 5:47:12 PM UTC+1, Eleanor McHugh wrote:
I've spent a lot of time experimenting with different ways of implementing efficient generic container types, and the main problem I find is with code repetition. I think this could be considerably reduced by allowing the following statement to work as intended:

switch f := sometype.(type) {
case func(int), func(interface{}):
  f(100)
}

rather than the current behaviour where f isn't recognised as callable and a compile error occurs.

It works *as intended*. In the example above, 'f' in the only case clause can have only one type, 'interface{}' in this case.

This version IMO works "as intended":

package main

import "fmt"

func foo(x interface{}) {
        switch f := x.(type) {
        case func(int):
                f(100)
        case func(interface{}):
                f(200)
        }
}

func bar(i int) {
        fmt.Println("bar", i)
}

func baz(i interface{}) {
        fmt.Println("baz", i)
}

func main() {
        foo(bar)
        foo(baz)
}

Output:

bar 100
baz 200

Available also here: http://play.golang.org/p/Ic-wsDknwb 

chris dollin

unread,
Mar 1, 2012, 12:56:57 PM3/1/12
to Ian Lance Taylor, Paulo Pinto, golang-nuts
On 1 March 2012 17:28, Ian Lance Taylor <ia...@google.com> wrote:

> It's not a module system that is an issue.  Go is a compiled language,
> so for this discussion we can restrict ourselves to that space.  The
> question then boils down to: if you have a generic function, do you
> generate a version of it specialized for each type (slower compiler) or
> do you generate a version that does type switches at runtime (slower
> execution time)?

I don't think it's as dichotomous as that. ML, for example, has generic
(at least, polymorphic) functions that don't require specialised copies
and don't require type switches. What it /does/ require is uniform
representation, which is likely another route to your "slower execution"
but with different tradeoffs. (Go is fixed with non-uniform representations,
but I wonder if a good cut at useful genericity would be a layer around
interfaces, which are what you ahve to use for polymorphism in Go
anyway.)

Chris

--
Chris "allusive" Dollin

Eleanor McHugh

unread,
Mar 1, 2012, 2:10:12 PM3/1/12
to golang-nuts

On 1 Mar 2012, at 17:51, Jan Mercl wrote:

> On Thursday, March 1, 2012 5:47:12 PM UTC+1, Eleanor McHugh wrote:
> I've spent a lot of time experimenting with different ways of implementing efficient generic container types, and the main problem I find is with code repetition. I think this could be considerably reduced by allowing the following statement to work as intended:
> switch f := sometype.(type) {
> case func(int), func(interface{}):
> f(100)
> }
>
> rather than the current behaviour where f isn't recognised as callable and a compile error occurs.
>
> It works *as intended*. In the example above, 'f' in the only case clause can have only one type, 'interface{}' in this case.
>
> This version IMO works "as intended":

And IMO the current behaviour serves little (if any) practical purpose and is semantically confusing. The compiler knows the concrete type and can enforce type safety throughout the associated case, but instead it boxes the value in interface{} and effectively erases its type until a further type assertion is performed.

> func foo(x interface{}) {
> switch f := x.(type) {
> case func(int):
> f(100)
> case func(interface{}):
> f(200)
> }
> }
>

This is different to my example which would more correctly be:

func foo(x interface{}) {
switch f := x.(type) {
case func(int):
f(100)
case func(interface{}):

f(100)
}
}

It's the very fact that both code paths are identical save for the type of their function call which is of interest to me because that more closely approximates the use cases generics are intended to address, not to mention a considerable volume of the code I've written in recent months.

This simple change to the language spec would reduce code duplication when implementing generic algorithms and data structures without introducing an unreasonable compile-time overhead.

Ian Lance Taylor

unread,
Mar 1, 2012, 3:05:59 PM3/1/12
to Eleanor McHugh, golang-nuts
Eleanor McHugh <ele...@games-with-brains.com> writes:

> This simple change to the language spec would reduce code duplication
> when implementing generic algorithms and data structures without
> introducing an unreasonable compile-time overhead.

It may be a simple change to the language spec but it's not a simple
change to the compiler. In effect the compiler would have to introduce
the type switches that you prefer not to write. This is of course
doable but at least to me it sounds like surprising behaviour. The
simple expression f(100) would turn into a runtime type switch.

And in truth I'm not sure it's a simple change to the language spec.
After all, presumably you don't want any special treatment for
case func(int), int:

So you would have to define some notion of "types that are compatible
enough for a type switch but are not actually identical." There is no
such notion in the language at present.

Ian

Eleanor McHugh

unread,
Mar 1, 2012, 4:49:34 PM3/1/12
to golang-nuts
On 1 Mar 2012, at 20:05, Ian Lance Taylor wrote:
> Eleanor McHugh <ele...@games-with-brains.com> writes:
>> This simple change to the language spec would reduce code duplication
>> when implementing generic algorithms and data structures without
>> introducing an unreasonable compile-time overhead.
>
> It may be a simple change to the language spec but it's not a simple
> change to the compiler. In effect the compiler would have to introduce
> the type switches that you prefer not to write.

No, it need only introduce the additional cases to the existing type switch and then apply knowledge it already has to ensure that the expanded code is correct.

> This is of course
> doable but at least to me it sounds like surprising behaviour. The
> simple expression f(100) would turn into a runtime type switch.

case func(int), func interface{}:
f(100)

would expand to

case func(int):
f(100)
case func(interface{}):
f(100)

> And in truth I'm not sure it's a simple change to the language spec.


> After all, presumably you don't want any special treatment for
> case func(int), int:

If the two cases would expand to identical code save for the type of the variable itself then this case would be just as reasonable as

case func(int), func(interface{}):

or

case int, uint:

and if it doesn't then the compiler should report an error.

> So you would have to define some notion of "types that are compatible
> enough for a type switch but are not actually identical." There is no
> such notion in the language at present.


It isn't necessarily required because when the code is expanded it can be statically checked as mentioned above.

That's not to say that there wouldn't be value in bringing the reflect.Kind concept into the core language, but that's a discussion for another day.

Ian Lance Taylor

unread,
Mar 1, 2012, 11:00:03 PM3/1/12
to Eleanor McHugh, golang-nuts
Eleanor McHugh <ele...@games-with-brains.com> writes:

>> This is of course
>> doable but at least to me it sounds like surprising behaviour. The
>> simple expression f(100) would turn into a runtime type switch.
>
> case func(int), func interface{}:
> f(100)
>
> would expand to
>
> case func(int):
> f(100)
> case func(interface{}):
> f(100)

Fair enough, I suppose.

Still definitely makes me uneasy. It seems strange to write code that
is expanded multiple times with different types. Sort of generics
through the backdoor.

Ian

Paulo Pinto

unread,
Mar 2, 2012, 3:13:44 AM3/2/12
to golang-nuts


On Mar 1, 6:28 pm, Ian Lance Taylor <i...@google.com> wrote:
All the languages referred by me are also compiled.

Having a module system resolves the compile time issue. The major
problem with C++ compile times is due to the #include<> which have
to be parsed multiple times, as the compiler cannot be sure if their
content
remains constant.

So, most languages with module systems that also have their own
linkers, can
have internal representations that are pretty efficient. Also for the
representation
of generic types.

Usually the most common solution is to generate a concrete type for
each set of
types. There are no duplicates of generic types instantiations, thus
reducing the
code bloat that templates in C++ are known for.

What I think is that many people lack the proper CS background when
discussing
language issues like generic types, operators as method names and so
on.

Usually people are only aware of what C++, Java and .NET offer, and
even then, I
see misconceptions all the time being described.

Other not so mainstream languages show that is possible to have fast
compile times,
with generic types, while avoiding code bloat.

Now I am not able to, but I can search for a few papers I might have
lying around and
post their reference here.

--
Paulo


Eleanor McHugh

unread,
Mar 2, 2012, 7:02:43 AM3/2/12
to golang-nuts

Whereas to me it seems entirely natural, but I have spent more than a decade in the Ruby world so my instinctive response to unavoidable code duplication is to abstract it behind a code generator for consistency.

> Sort of generics through the backdoor.

I suspect the CS-literate would disagree as this doesn't address such issues as callability, indexability, etc. To do that the concept expressed in the runtime by reflect.Kind would somehow have to be exposed at the type-system level and whilst I've pondered the use of a .(kind) switch it's a bit of a rabbit-hole which probably leads nowhere.

However this modification to type switches expresses a simple contract between programmer and compiler, which makes it very easy to reason about. The programmer is making the assertion that two types will behave the same way in a particular context and the compiler is already knowledgeable enough to put that assertion to the test. I think that's very much in keeping with the spirit of Go even if it might have a slightly spooky deus ex machina feel for people used to the current behaviour.

I also have a vested interest as I'm working on a package which would greatly benefit from such a feature.

Sequences seeks to be a generalised container library which provides optimised performance for slices and channels of all the basic scalar types Go provides, along with interfaces that allow user-defined types to implement their own optimisations. The most tedious aspect of implementing this is the code repetition involved: there are 20 basic types (including reflect.Value, which isn't really a basic type but is useful) and whilst there are cases which deviate I suspect I could reduce the code for handling their commonalities by 90% if my type switches could use cases as described above. This equates to several thousand conceptually redundant lines of code being pruned from the codebase, making it much easier to read and maintain.

roger peppe

unread,
Mar 2, 2012, 7:42:40 AM3/2/12
to Eleanor McHugh, golang-nuts
On 1 March 2012 21:49, Eleanor McHugh <ele...@games-with-brains.com> wrote:
> On 1 Mar 2012, at 20:05, Ian Lance Taylor wrote:
>> Eleanor McHugh <ele...@games-with-brains.com> writes:
>>> This simple change to the language spec would reduce code duplication
>>> when implementing generic algorithms and data structures without
>>> introducing an unreasonable compile-time overhead.
>>
>> It may be a simple change to the language spec but it's not a simple
>> change to the compiler.  In effect the compiler would have to introduce
>> the type switches that you prefer not to write.
>
> No, it need only introduce the additional cases to the existing type switch and then apply knowledge it already has to ensure that the expanded code is correct.
>
>> This is of course
>> doable but at least to me it sounds like surprising behaviour.  The
>> simple expression f(100) would turn into a runtime type switch.
>
>  case func(int), func interface{}:
>    f(100)
>
> would expand to
>
>  case func(int):
>    f(100)
>  case func(interface{}):
>    f(100)

it seems to me that this would be a hard thing to define well.

everywhere in Go, types are crystal clear - something is
either of a particular type or it's not. the types in
this proposal are not clear - they're a superposition
of the types in the case statement.

you could have a situation where this is OK:

switch x := x.(type) {case S, T: x.Foo()}

and this is OK:

switch x := x.(type) {case S, T: x.Bar()}

but this is not OK:

switch x := x.(type) {case S, T: x.Foo(); x.Bar()}

i think that would be weird.

we've got reflection for this kind of thing.

Eleanor McHugh

unread,
Mar 2, 2012, 8:25:53 AM3/2/12
to golang-nuts
On 2 Mar 2012, at 12:42, roger peppe wrote:
> On 1 March 2012 21:49, Eleanor McHugh <ele...@games-with-brains.com> wrote:
>> case func(int), func interface{}:
>> f(100)
>>
>> would expand to
>>
>> case func(int):
>> f(100)
>> case func(interface{}):
>> f(100)
>
> it seems to me that this would be a hard thing to define well.

Possibly. I haven't given much thought to how it would be expressed in the spec.

> everywhere in Go, types are crystal clear - something is
> either of a particular type or it's not. the types in
> this proposal are not clear - they're a superposition
> of the types in the case statement.

Go already allows for type superpositions in the form of interfaces so this argument is misleading.

switch x := x.(type) {
case S:
case T, U:
case V, W, X:
default:
}

In this snippet both [T, U] and [V, W, X] have an effective type of interface{}, so their values have effectively been superposed in a type singularity which must be actively introspected (with a type switch, type assertion or as a reflect.Value) to be unboxed and manipulated. That leads to code such as:

switch x := x.(type) {
case S:
case T, U:
if t, ok := x.(T) {
} else {
}
case V, W, X:
switch x := x.(type) {
...
}
default:
}

But who would write this code?

> you could have a situation where this is OK:
>
> switch x := x.(type) {case S, T: x.Foo()}
>
> and this is OK:
>
> switch x := x.(type) {case S, T: x.Bar()}
>
> but this is not OK:
>
> switch x := x.(type) {case S, T: x.Foo(); x.Bar()}

Yes, you could. If either S or T had a Bar() method but the other didn't then this would be an error. An error which could be reported at compile time just as any other attempt to call Bar() on a type which doesn't support it would be reported.

> i think that would be weird.

And I think it would be helpful.

> we've got reflection for this kind of thing.


Reflection is orders of magnitude slower than using a type switch or assertion, and until that changes I'd prefer not to have to incur its cost unnecessarily. In the case we're discussing all the information needed for type checking is available at compile time so the overhead associated with reflection is completely unnecessary.

Jan Mercl

unread,
Mar 2, 2012, 9:00:20 AM3/2/12
to golan...@googlegroups.com
On Friday, March 2, 2012 2:25:53 PM UTC+1, Eleanor McHugh wrote:
On 2 Mar 2012, at 12:42, roger peppe wrote:
> On 1 March 2012 21:49, Eleanor McHugh <> wrote:
>>  case func(int), func interface{}:
>>    f(100)
>>
>> would expand to
>>
>>  case func(int):
>>    f(100)
>>  case func(interface{}):
>>    f(100)
>
> it seems to me that this would be a hard thing to define well.
 

> everywhere in Go, types are crystal clear - something is


> either of a particular type or it's not. the types in
> this proposal are not clear - they're a superposition
> of the types in the case statement.

Go already allows for type superpositions in the form of interfaces so this argument is misleading.

switch x := x.(type) {
  case S:
  case T, U:
  case V, W, X:
  default:
}

No. Go entities do have always exactly one static type. Interface type *additionally* have also a dynamic type. The type switch statement already does something similar to your proposal (the type-assert-else-if-chain "expansion" was presented already somewhere earlier in this thread, IIRC). The difference and/or confusion, perhaps caused at least partially by injecting Go-alien terminology like "type superposition", is  - the newly introduced, in switch statement's scope, 'x' has again exactly one static type withing each of the case clauses.

The proposal would, as the only such place in the language, introduce an entity with, de facto, no static type in a particular place - the "scope" of the specific case clause. I can only imagine all problems this may cause, but it intuitively terrifies me quite a lot.

I think it's very much against some of the root principles Go is designed upon.

In a summary: The type switch statement already *is* a syntactic sugar (cf. "expansion" above). As seen endless times before, people go after syntactic sugar constructs, in any language, and demand to make them (much) more sweat. Sadly, if such efforts succeeds, many times the originally handy constructs are no more "edible" afterwards.

If some code repeatedly repeats itself (as claimed in 90% of time) - why not just use any kind of preprocessing one is comfortable with? There are so many readily available tools for doing such things out there. Also particularly this discussed case can be handled by using the standard parser/AST-rewrite/writer easily in, say, a plain stdin->stdout fashion w/o need for any type checking (postponed to be performed by the compiler) in the first approximation, so it could be like a one screenfull of code, I guess?

Ian Lance Taylor

unread,
Mar 2, 2012, 1:13:12 PM3/2/12
to Paulo Pinto, golang-nuts
Paulo Pinto <paulo....@gmail.com> writes:

> Having a module system resolves the compile time issue. The major
> problem with C++ compile times is due to the #include<> which have
> to be parsed multiple times, as the compiler cannot be sure if their
> content
> remains constant.

I know. But that is separate from the issue of generics.


> Usually the most common solution is to generate a concrete type for
> each set of
> types. There are no duplicates of generic types instantiations, thus
> reducing the
> code bloat that templates in C++ are known for.

There is no code bloat when using a modern C++ compiler. But there is
compilation time bloat: every template function is recompiled for every
type for which it is instantiated.

I don't know what you mean by say "a concrete type for each set of
types." What are the sets of types you are referring to?


> Other not so mainstream languages show that is possible to have fast
> compile times,
> with generic types, while avoiding code bloat.

This fact does not resolve the issue that Russ pointed out.

I agree with an earlier poster that you can reduce the compile time
impact somewhat if all types are the same size. Unfortunately that is
not the case for Go, because Go is designed to allow more precise
control over how types are laid out in memory.

Ian

Ian Lance Taylor

unread,
Mar 2, 2012, 1:25:30 PM3/2/12
to Eleanor McHugh, golang-nuts
Eleanor McHugh <ele...@games-with-brains.com> writes:

>> Sort of generics through the backdoor.
>
> I suspect the CS-literate would disagree as this doesn't address such
> issues as callability, indexability, etc. To do that the concept
> expressed in the runtime by reflect.Kind would somehow have to be
> exposed at the type-system level and whilst I've pondered the use of a
> .(kind) switch it's a bit of a rabbit-hole which probably leads
> nowhere.

Right, but wouldn't a real generics solution cover what you need and
also be generally better? What I mean is, I don't think we should patch
the language to get around the fact that it doesn't have have generics.
At least, we shouldn't do it unless we definitely decide that the
language is not going to get generics.

Ian

Eleanor McHugh

unread,
Mar 2, 2012, 5:03:34 PM3/2/12
to golang-nuts
On 2 Mar 2012, at 18:25, Ian Lance Taylor wrote:
> Eleanor McHugh <ele...@games-with-brains.com> writes:
>>> Sort of generics through the backdoor.
>>
>> I suspect the CS-literate would disagree as this doesn't address such
>> issues as callability, indexability, etc. To do that the concept
>> expressed in the runtime by reflect.Kind would somehow have to be
>> exposed at the type-system level and whilst I've pondered the use of a
>> .(kind) switch it's a bit of a rabbit-hole which probably leads
>> nowhere.
>
> Right, but wouldn't a real generics solution cover what you need and
> also be generally better?

Generics might cover what I need but to date no proposal has proven attractive.

> What I mean is, I don't think we should patch
> the language to get around the fact that it doesn't have have generics.

However if a simple, elegant extension to existing idioms were able to express many of the type isomorphisms that real-world code exhibits, then surely that would be a good thing?

> At least, we shouldn't do it unless we definitely decide that the
> language is not going to get generics.

Then decide not to add generics and focus on exploring how the existing type system can be generalised.

Simples! :) [0]


Ellie

[0] for non-UK readers: http://en.wikipedia.org/wiki/Aleksandr_Orlov_(advertising)

Paulo Pinto

unread,
Mar 3, 2012, 1:53:53 PM3/3/12
to golang-nuts


On Mar 2, 7:13 pm, Ian Lance Taylor <i...@google.com> wrote:
> Paulo Pinto <paulo.jpi...@gmail.com> writes:
> > Having a module system resolves the compile time issue. The major
> > problem with C++ compile times is due to the #include<> which have
> > to be parsed multiple times, as the compiler cannot be sure if their
> > content
> > remains constant.
>
> I know.  But that is separate from the issue of generics.
>
> > Usually the most common solution is to generate a concrete type for
> > each set of
> > types.  There are no duplicates of generic types instantiations, thus
> > reducing the
> > code bloat that templates in C++ are known for.
>
> There is no code bloat when using a modern C++ compiler.  But there is
> compilation time bloat: every template function is recompiled for every
> type for which it is instantiated.

Which might be avoided by a module system. Hence my reference to
module systems modern languages by oposition to how C++ compilers
work.

>
> I don't know what you mean by say "a concrete type for each set of
> types."  What are the sets of types you are referring to?

I was referring to generics instantiations for each set of argument
types. Meaning
that if multiple modules (packages) refer to the same generic
instantiation, lets
say Stack<int>, the final executable will have the corresponding code
generated only once.


>
> > Other not so mainstream languages show that is possible to have fast
> > compile times,
> > with generic types, while avoiding code bloat.
>
> This fact does not resolve the issue that Russ pointed out.

I have to recognise it is not so straightforward as I thought.

My initial arguments were based in false premisses and as I promised
to look for
some papers, I have to say it is not so easy to find papers related to
generics implementations
that are freely available.

Anyway I can leave here some of my findings, as they can contribute to
the way as Go
might eventually earn some form of generics.

A blog entry which describes how generics are compiled by the NET JIT
http://msdn.microsoft.com/de-de/magazine/cc163683%28en-us%29.aspx#S5

This paper references that Modula-3 and ML compilers make use
of type erasure, while C++ and Ada duplicate generics instantiations
and .NET uses a mixed approach.

http://cs.fit.edu/media/TechnicalReports/cs-2004-06.pdf

A way to implement generics in ML
http://jfla.inria.fr/2001/actes/07-furuse.ps

Rémy Oudompheng

unread,
Mar 3, 2012, 5:28:53 PM3/3/12
to Paulo Pinto, golang-nuts
Le 3 mars 2012 19:53, Paulo Pinto <paulo....@gmail.com> a écrit :
> On Mar 2, 7:13 pm, Ian Lance Taylor <i...@google.com> wrote:
>> There is no code bloat when using a modern C++ compiler.  But there is
>> compilation time bloat: every template function is recompiled for every
>> type for which it is instantiated.
>
> Which might be avoided by a module system. Hence my reference to
> module systems modern languages by oposition to how C++ compilers
> work.

I'm not sure what you mean...

>> I don't know what you mean by say "a concrete type for each set of
>> types."  What are the sets of types you are referring to?
>
> I was referring to generics instantiations for each set of argument
> types. Meaning
> that if multiple modules (packages) refer to the same generic
> instantiation, lets
> say Stack<int>, the final executable will have the corresponding code
> generated only once.

Are you saying that you can avoid "instantiating templates for each type" by
"instantiating for each type" ?

Rémy.

Steve McCoy

unread,
Mar 3, 2012, 8:10:49 PM3/3/12
to golan...@googlegroups.com, Paulo Pinto
I believe that Paulo is trying to explain this:

There are two aspects to C++ templates that slow build time: The first is in compile time, scanning, parsing, and analyzing template definitions over and over, because they're fully defined in header files. The second aspect is omitting duplicate code during link time, because each object file that uses the same template specialization will contain its code. In C++11, it's possible to manually avoid the link-time penalty with "extern" template specialization declarations. For example, "extern std::template<int>;" instructs the compiler not to generate the relevant code for a vector<int>, and some other file can contain an explicit instantiation so that the linker can find the definitions. This is something that could be automated, but since header files don't necessarily correspond with certain object files or libraries, you would have to introduce your own tools and conventions to accomplish it.

With modules, there is no awkward disconnect between header files and libraries, because there are no header files and the interface can be directly stored in the library. Hypothetically, a module system would solve both of these problems in some manner like this: When a compilation unit contains a template specialization, the compiler could look in a special location for an archive containing the definition of that specialization. If found, nothing else needs to be done, and it can be used for type-checking, etc. like any other library archive. If not found, it would have to be created and placed in this special location. This would be enough to prevent the redundant parsing and symbol elimination penalties.

Now, this still leaves that the template definition would be re-parsed for each different specialization, but the majority of the pain comes from redundant specializations. Consider a header file containing this class definition: class X{ std::vector<int> frogs; }; Every file that uses this class is going to have some code for vector<int> in it.

All of that said, I'm not sure if templates/generics will ever be a good fit for Go, for a variety of reasons. This could become a whole other essay, so I'll just touch on some things that I've wanted to get off my chest. ML has come up in this thread — if Go had ML-style generics, I would certainly never use them, because they're so limited as to be almost worthless. ML's functors, on the other hand, would be more useful. C++'s templates are conceptually not far removed from functors, as they are compile-time functions that map input types to output types, with the added convenience of implicit instantiation. Combined with other C++ features like function overloading and argument-dependent lookup, they're insanely powerful. However, with all of this power comes semantic complexity. Even Java's generics, which are heavily toned down from C++ templates and marginally more useful than ML's generics, add a lot of complexity. I couldn't tell you from memory all of the details and limitations of wildcards, for example. This complexity is because all generic programming is metaprogramming, which is complex because writing a metafunction requires thinking about input/output *types* along side input/output values. Two of Go's goals are simplicity and power. Templates/generics could raise the power, but simplicity will certainly take a hit.


On Saturday, March 3, 2012 5:28:53 PM UTC-5, Rémy Oudompheng wrote:

Paulo Pinto

unread,
Mar 3, 2012, 5:52:22 PM3/3/12
to golang-nuts


On Mar 3, 11:28 pm, Rémy Oudompheng <remyoudomph...@gmail.com> wrote:
> Le 3 mars 2012 19:53, Paulo Pinto <paulo.jpi...@gmail.com> a écrit :
>
> > On Mar 2, 7:13 pm, Ian Lance Taylor <i...@google.com> wrote:
> >> There is no code bloat when using a modern C++ compiler.  But there is
> >> compilation time bloat: every template function is recompiled for every
> >> type for which it is instantiated.
>
> > Which might be avoided by a module system. Hence my reference to
> > module systems modern languages by oposition to how C++ compilers
> > work.
>
> I'm not sure what you mean...


Module systems have the code stored in some kind of AST, or at least
that
is a possible representation. As such, the compiler can fetch the type
information
from a module quite fast in comparison with parsing multiple times, as
it is the
case with C++. This means using generics in a language like Go, which
has
packages, does not need to impose compile time penalties.


>
> >> I don't know what you mean by say "a concrete type for each set of
> >> types."  What are the sets of types you are referring to?
>
> > I was referring to generics instantiations for each set of argument
> > types. Meaning
> > that if multiple modules (packages) refer to the same generic
> > instantiation, lets
> > say Stack<int>, the final executable will have the corresponding code
> > generated only once.
>
> Are you saying that you can avoid "instantiating templates for each type" by
> "instantiating for each type" ?

In many C++ compilers that lack smart linkers, you will end up will
multiple
copies of Stack<int> for each object file that needs to instantiate
the Stack<> template
for the int type. So in a way, the removal of redundant copies is
implementation dependent.

While other languages have definitions that allow an implementation
where this instantiation
duplication does not occur. As far as I know, this is the case for Ada
for example.

Anyway, my search for generics implementations papers proved that some
of the things I took for
granted are not that easy to confirm.

--
Paulo

Steven Blenkinsop

unread,
Mar 4, 2012, 3:16:29 AM3/4/12
to roger peppe, Eleanor McHugh, golang-nuts
On Fri, Mar 2, 2012 at 7:42 AM, roger peppe <rogp...@gmail.com> wrote:
it seems to me that this would be a hard thing to define well.

everywhere in Go, types are crystal clear - something is
either of a particular type or it's not. the types in
this proposal are not clear - they're a superposition
of the types in the case statement.

The particular type in this case would be best expressed by a union-like type. Of course, if Go had these, there'd be no need to change the behaviour of type switches since you'd just use the union type. Of course, unions have been rejected as being unnecessary since interfaces can handle most use cases. They do, however, seem like the missing piece of abstraction that would make generics useful in Go, since you could handle all built-in integer types, for example, with one piece of code.

On Fri, Mar 2, 2012 at 1:25 PM, Ian Lance Taylor <ia...@google.com> wrote:
Eleanor McHugh <ele...@games-with-brains.com> writes:

Right, but wouldn't a real generics solution cover what you need and
also be generally better?  What I mean is, I don't think we should patch

the language to get around the fact that it doesn't have have generics.
At least, we shouldn't do it unless we definitely decide that the
language is not going to get generics.

Ian

I think, though, that this could be part of a generics solution for Go, as well as being a nice feature on its own, especially if it were achieved by adding types to Go that have the desired behaviour. These union-like types, in addition perhaps to a package providing special union types that would be otherwise impossible to express (such as a union of all built-in or derived numeric types), could basically cover the abstraction part of generics, and all that would be left would be the type consistency part.

Of course, there's always the question of implementation, which is really the main issue with any generics proposal. The core team seems to be of the opinion that all possible implementations are undesirable for inescapable reasons. It seems to me, though, that the perceived drawbacks of each implementation already exist in Go whenever you need to write code that handles multiple different types. The language is just more tedious and less expressive in these cases than if it had generics.

Paulo Pinto

unread,
Mar 4, 2012, 1:35:29 AM3/4/12
to golang-nuts
Yep you got it right. Thanks

Paulo Pinto

unread,
Mar 4, 2012, 4:45:12 AM3/4/12
to golang-nuts
This is why I voice for generics in Go.

I am old enough to have worked with C++ since the early days, and
remember the hacks most compilers had to try to offer some kind
of genericity, from pre-processor tricks up to separate tools.

Nowadays all mainstream static languages support some form of
generics,
with exception of C, so going back to the hacks the C++ community had
to
create in the late 80's, early 90's, seems quite strange for a young
language
that is still looking for its place in the computer landscape.

--
Paulo

On Mar 4, 9:16 am, Steven Blenkinsop <steven...@gmail.com> wrote:
> On Fri, Mar 2, 2012 at 7:42 AM, roger peppe <rogpe...@gmail.com> wrote:
> > it seems to me that this would be a hard thing to define well.
>
> > everywhere in Go, types are crystal clear - something is
> > either of a particular type or it's not. the types in
> > this proposal are not clear - they're a superposition
> > of the types in the case statement.
>
> The particular type in this case would be best expressed by a union-like
> type. Of course, if Go had these, there'd be no need to change the
> behaviour of type switches since you'd just use the union type. Of course,
> unions have been rejected as being unnecessary since interfaces can handle
> most use cases. They do, however, seem like the missing piece of
> abstraction that would make generics useful in Go, since you could handle
> all built-in integer types, for example, with one piece of code.
>
> On Fri, Mar 2, 2012 at 1:25 PM, Ian Lance Taylor <i...@google.com> wrote:
>
> > Eleanor McHugh <elea...@games-with-brains.com> writes:
>
> > Right, but wouldn't a real generics solution cover what you need and
> > also be generally better?  What I mean is, I don't think we should patch
> > the language to get around the fact that it doesn't have have generics.
> > At least, we shouldn't do it unless we definitely decide that the
> > language is not going to get generics.
>
> > Ian
>
> I think, though, that this could be *part of* a generics solution for Go,

Kyle Lemons

unread,
Mar 4, 2012, 12:41:09 PM3/4/12
to Paulo Pinto, golang-nuts
Nowadays all mainstream static languages support some form of
generics,
with exception of C, so going back to the hacks the C++ community had
to
create in the late 80's, early 90's, seems quite strange for a young
language
that is still looking for its place in the computer landscape.

Go isn't designed by committee.  The designers won't add a feature until they know it will carry its own weight.  The avoidance of generics, in my opinion, is more an admission that they don't know the best approach that works predictably and orthogonally to the other features of go.  It seems they'd rather not provide generics at all than provide a poor implementation, one that interacts poorly with the other Go constructs, or one that doesn't adequately cover the cases for which interfaces don't already suffice.  When you consider that C++ and Java went without templates and generics (respectively) for years and years (and, some would say, still didn't get it right) I see no reason why Go should be in a rush.

Paulo Pinto

unread,
Mar 4, 2012, 1:02:54 PM3/4/12
to golang-nuts
In many CS circles, the late addition of generics to C++ and Java is
seen
as the root cause of their behaviour, as compromises had to be had to
avoid breaking existing code.

If story is to repeat itself, something similar might happen to Go, if
the language ever gets generics. As I doubt anyone will want to have
breaking changes with Go 1 code.

--
Paulo

Ian Lance Taylor

unread,
Mar 4, 2012, 3:46:35 PM3/4/12
to Paulo Pinto, golang-nuts
Paulo Pinto <paulo....@gmail.com> writes:

> Nowadays all mainstream static languages support some form of
> generics,

> with exception of C, [....]

Even C has generics. They are implemented via the preprocessor. Here
is an example of a generic vector implementation in pure C:

http://gcc.gnu.org/viewcvs/trunk/gcc/vec.h?revision=181258&content-type=text%2Fplain&view=co

Ian

Paulo Pinto

unread,
Mar 4, 2012, 4:03:18 PM3/4/12
to golang-nuts
That is not generics. The preprocessor is not part of the compiler.

Generics are type safe and compiler supported as per CS definition.

--
Paulo

On Mar 4, 9:46 pm, Ian Lance Taylor <i...@google.com> wrote:
> Paulo Pinto <paulo.jpi...@gmail.com> writes:
> > Nowadays all mainstream static languages support some form of
> > generics,
> > with exception of C, [....]
>
> Even C has generics.  They are implemented via the preprocessor.  Here
> is an example of a generic vector implementation in pure C:
>
> http://gcc.gnu.org/viewcvs/trunk/gcc/vec.h?revision=181258&content-ty...
>
> Ian

Rémy Oudompheng

unread,
Mar 4, 2012, 5:08:50 PM3/4/12
to Paulo Pinto, golang-nuts
Le 4 mars 2012 22:03, Paulo Pinto <paulo....@gmail.com> a écrit :
> That is not generics. The preprocessor is not part of the compiler.
>
> Generics are type safe and compiler supported as per CS definition.
>

What do you mean by "definition" of generics?

Rémy.

Jan Mercl

unread,
Mar 4, 2012, 5:12:06 PM3/4/12
to golan...@googlegroups.com
On Sunday, March 4, 2012 10:03:18 PM UTC+1, Paulo Pinto wrote:
That is not generics. The preprocessor is not part of the compiler.

Would you have said "not part of the language" it could have been a valid point. But not in the case of C, where the preprocessor is part of the language's standard specification.
 
Generics are type safe and compiler supported as per CS definition.

[citation needed]

Paulo Pinto

unread,
Mar 4, 2012, 5:19:45 PM3/4/12
to golang-nuts

Paulo Pinto

unread,
Mar 4, 2012, 5:19:31 PM3/4/12
to golang-nuts
http://en.wikipedia.org/wiki/Generic_programming

On Mar 4, 11:08 pm, Rémy Oudompheng <remyoudomph...@gmail.com> wrote:

Jan Mercl

unread,
Mar 4, 2012, 5:50:00 PM3/4/12
to golan...@googlegroups.com
On Sunday, March 4, 2012 11:19:45 PM UTC+1, Paulo Pinto wrote:
http://en.wikipedia.org/wiki/Generic_programming

Quoted from the very start:

In a broad definition, generic programming is a style of computer programming in which algorithms are written in terms of to-be-specified-later types that are then instantiated when needed for specific types provided as parameters. This approach, pioneered by Adain 1983, permits writing common functions or types that differ only in the set of types on which they operate when used, thus reducingduplication

How the C standard and/or Ian's example don't fit this "definiton"? ;-)

Paulo Pinto

unread,
Mar 5, 2012, 1:36:31 AM3/5/12
to golang-nuts
You can twist the definition as much as you want, but I don't see any
reference to C there.

I give up.

On Mar 4, 11:50 pm, Jan Mercl <0xj...@gmail.com> wrote:
> On Sunday, March 4, 2012 11:19:45 PM UTC+1, Paulo Pinto wrote:
>
> >http://en.wikipedia.org/wiki/Generic_programming
>
> Quoted from the very start:
>
> In a broad definition, *generic programming* is a style of computer
> programming <http://en.wikipedia.org/wiki/Computer_programming> in which
> algorithms are written in terms of *to-be-specified-later* types that are
> then *instantiated* when needed for specific types provided as parameters<http://en.wikipedia.org/wiki/Parameter_(computer_programming)>.
> This approach, pioneered by Ada<http://en.wikipedia.org/wiki/Ada_(programming_language)>in
> 1983, permits writing common functions<http://en.wikipedia.org/wiki/Function_(computer_science)>
>  or types <http://en.wikipedia.org/wiki/Type_(computer_science)> that
> differ only in the set of types on which they operate when used, thus
> reducingduplication <http://en.wikipedia.org/wiki/Duplicate_code>.

Ian Lance Taylor

unread,
Mar 5, 2012, 1:49:24 AM3/5/12
to Paulo Pinto, golang-nuts
Paulo Pinto <paulo....@gmail.com> writes:

> You can twist the definition as much as you want, but I don't see any
> reference to C there.

Obviously I was being slightly facetious in claiming that C supports
generic programming, but the fact is that the preprocessor is part of
the C language, and the preprocessor can be used in a way that supports
the "broad definition" from the wikipedia page. It's not an important
point in any discussion of generics, but I believe it is a true
nonetheless.

Ian

Eleanor McHugh

unread,
Mar 5, 2012, 7:57:21 AM3/5/12
to golang-nuts

I have sympathy with both sides of this argument. However as a coder what really matters to me is DRYness. Any Go code which seeks to expose the same API for a number of different numeric, map, function or slice types results in a commensurate expansion of duplicated code and that's a real long-term maintenance issue.

Now as Jan suggested this could be addressed by using an external templating tool to generate the code at compile-time, but personally I find this unsatisfying in the same way that C's macros are unsatisfying. Instead I'd much rather have some form of type composition mechanism built directly into the language that works at the same scale as the existing type switch idiom but addresses the core functional properties of type.

The reflection system can be used to address much of this but at a considerable performance cost and the resulting code can become somewhat dense. For the places where runtime introspection of code is necessary these are acceptable tradeoffs, but for the cases generics are concerned with this can become a significant anti-pattern.

Steven's suggestion of Union types would be another way of addressing this, and is rather elegantly symmetrical to interfaces (which could be considered a form of Intersection type :). In the past I've also raised the possibility of some form of Category typing. Interfaces are a form of this based on method signatures, but interfaces can't express the fundamental type axioms of callability, indexability etc. expressed by Go's standard types. If the language had idiomatic statements for dealing with these - and I concede that perhaps extra type-switch semantics aren't the right place for such an idiom - then most of the use cases for generics would be addressed.

One thing I think we can all agree on though is that there are well-known cases related to type in which Go is currently more verbose than it needs to be. Solving this elegantly would make the language a bit more productive to work with - especially when implementing generalised APIs - and somewhat simplify long-term maintenance of large codebases.

So whilst generics are not a panacea and the language can probably survive without them, enabling DRYness within the existing framework of Go's features would be a solid, pragmatic win for real-world development.


Ellie

roger peppe

unread,
Mar 5, 2012, 8:40:53 AM3/5/12
to Steven Blenkinsop, Eleanor McHugh, golang-nuts
On 4 March 2012 08:16, Steven Blenkinsop <stev...@gmail.com> wrote:
> On Fri, Mar 2, 2012 at 7:42 AM, roger peppe <rogp...@gmail.com> wrote:
>>
>> it seems to me that this would be a hard thing to define well.
>>
>> everywhere in Go, types are crystal clear - something is
>> either of a particular type or it's not. the types in
>> this proposal are not clear - they're a superposition
>> of the types in the case statement.
>
>
> The particular type in this case would be best expressed by a union-like
> type.

i'm not sure what you mean by union type in this context.
C-style union types are not type safe, and can be discounted, i think.
discriminated union types wouldn't do the job either, as you'd
have to decide which branch to choose when using the type.

as i mentioned before, i find it hard to think of a coherent type model
that would allow x.Foo or x.Bar but not both. the only decent explanation
is macro expansion, and i'm not keen on that.

si guy

unread,
Mar 5, 2012, 5:39:54 PM3/5/12
to golan...@googlegroups.com
might as well weigh in.

Currently writing a large program which is half c#.net and half go.

c# advantages:
generics - fast thanks to .net JIT
excellent IDE
easy dynamic typing
linq - would like to see something like this in go
extremely flexible collections library (mostly to make up for poor sync support)

go advantages:
no 180 character wide generic def hell
no cumbersome IDE
Much easier dep mgmt
no goofy manifests
much smaller codebase
no public/private static/nostatic visibility
no synchronization mess
thousands of threads
easier to understand memory model
better net support

most of my headaches in c# come about as a result of poorly planned generics and bad support for threading.
take your pick :)

Steven Blenkinsop

unread,
Mar 5, 2012, 9:22:03 PM3/5/12
to roger peppe, Eleanor McHugh, golang-nuts
On Mar 5, 2012, at 8:40 AM, roger peppe <rogp...@gmail.com> wrote:
i'm not sure what you mean by union type in this context.
C-style union types are not type safe, and can be discounted, i think.
discriminated union types wouldn't do the job either, as you'd
have to decide which branch to choose when using the type.

The union would have to be discriminated by the value's dynamic type,
the way interfaces are. In order to be a useful abstraction, though,
it would have to support the intersection of the operations supported
by its component types.

as i mentioned before, i find it hard to think of a coherent type model
that would allow x.Foo or x.Bar but not both.

I'm not entirely sure what you mean by this. If you're asking how to
restrict the possible fields (for example) to those shared by all
component types in the union, then it seems that the same as asking
how to have interfaces restrict their method sets to less than that of
a given implementing type. Otherwise, I can't see under what
circumstances you'd need a type that has one of two fields but never
both. Accessing one field should never impair your ability to access
another.


the only decent explanation
is macro expansion, and i'm not keen on that.

It's possible to keep field offsets in the type information. Other
operations could be similarly abstracted, interfaces already do this
with methods. A nice thing about Go is that types support a limited
set of operations, and vice versa, which helps limit the scope of
these abstractions, and several kinds of types are already
generalized, which helps.

That said, this could introduce unreasonable runtime inefficiencies in
some cases, and the way to solve that is through macro expansion (or
jit, I suppose), which has its own drawbacks. Optimizing this is the
inevitable result of introducing generality, though. The goal shouldn't
be to make a feature that's perfect for all cases, but instead, as with
maps and goroutines, to include a reasonable standard implementation
that is generally useful.

As for what do you gain over using reflection, reflection forces you to
throw out information is statically knowable in a generics use case. Its
good for what its made for, reflection, but for making code more generic,
its the wrong tool for the job.

roger peppe

unread,
Mar 6, 2012, 5:48:59 AM3/6/12
to Steven Blenkinsop, Eleanor McHugh, golang-nuts
On 6 March 2012 02:22, Steven Blenkinsop <stev...@gmail.com> wrote:
> On Mar 5, 2012, at 8:40 AM, roger peppe <rogp...@gmail.com> wrote:
>>
>> i'm not sure what you mean by union type in this context.
>> C-style union types are not type safe, and can be discounted, i think.
>> discriminated union types wouldn't do the job either, as you'd
>> have to decide which branch to choose when using the type.
>
>
> The union would have to be discriminated by the value's dynamic type,
> the way interfaces are. In order to be a useful abstraction, though,
> it would have to support the intersection of the operations supported
> by its component types.

yes. that's the hard bit.

for instance, Eleanor's original example was this:

switch f := sometype.(type) {
case func(int), func(interface{}):
f(100)
}

how do we define the intersection between
func(int) and func(interface{}) in such a way
that the above function call is legal?

what about the intersection between
[]string and map[uint64]string ?

if i've got a value of this intersection type, can
i pass it to another function? if so, how do i describe it?

>> as i mentioned before, i find it hard to think of a coherent type model
>>
>> that would allow x.Foo or x.Bar but not both.
>
>
> I'm not entirely sure what you mean by this. If you're asking how to
> restrict the possible fields (for example) to those shared by all
> component types in the union, then it seems that the same as asking
> how to have interfaces restrict their method sets to less than that of
> a given implementing type. Otherwise, I can't see under what
> circumstances you'd need a type that has one of two fields but never
> both. Accessing one field should never impair your ability to access
> another.

i was referring to a previous example where the selected cases had
mutually distinct sets of fields.

> It's possible to keep field offsets in the type information. Other
> operations could be similarly abstracted, interfaces already do this
> with methods. A nice thing about Go is that types support a limited
> set of operations, and vice versa, which helps limit the scope of
> these abstractions, and several kinds of types are already
> generalized, which helps.

we've got interfaces. they seem sufficient to me. if you want
an intersection of operations, we can define an interface that
encapsulates that intersection.

defining intersection on other type properties seems like
a hard problem, and probably not one that's going to lead
towards a better language IMHO.

Eleanor McHugh

unread,
Mar 6, 2012, 7:08:58 AM3/6/12
to golang-nuts
On 6 Mar 2012, at 10:48, roger peppe wrote:
> On 6 March 2012 02:22, Steven Blenkinsop <stev...@gmail.com> wrote:
>> On Mar 5, 2012, at 8:40 AM, roger peppe <rogp...@gmail.com> wrote:
>>>
>>> i'm not sure what you mean by union type in this context.
>>> C-style union types are not type safe, and can be discounted, i think.
>>> discriminated union types wouldn't do the job either, as you'd
>>> have to decide which branch to choose when using the type.
>>
>>
>> The union would have to be discriminated by the value's dynamic type,
>> the way interfaces are. In order to be a useful abstraction, though,
>> it would have to support the intersection of the operations supported
>> by its component types.
>
> yes. that's the hard bit.

It is in the general case, so let's not go down that rabbit-hole.

We already know a lot about Go's type categories from the design of the reflection system, so we should let that guide our thinking on this. Indeed consistency with the type guarantees provided by reflection would be essential to consistent type behaviour.

> for instance, Eleanor's original example was this:
>
> switch f := sometype.(type) {
> case func(int), func(interface{}):
> f(100)
> }
>
> how do we define the intersection between
> func(int) and func(interface{}) in such a way
> that the above function call is legal?

This one's fairly easy to describe. The intersection is that both functions are callable with a single parameter of one of a range of types, and the code which follows should be able to provide that one parameter in any of the types required by the specified functions.

> what about the intersection between
> []string and map[uint64]string ?

In the general case the two could be considered keyable and we could interpret that as a shared property, however reflection has already separated keyability and indexability so their intersection must be interpreted as producing an empty set.

> if i've got a value of this intersection type, can
> i pass it to another function? if so, how do i describe it?

I can see the benefit of being able to write functions which only work on a particular kind of type (I often do this in reflected code) but we would need to add additional meta-types to the core language which are equivalent to the various reflect.Kind values to make that possible in this sense. And if we did that then there's the question of user-defined types based on them: not necessarily a bad idea, but one I don't currently have time to think through in depth.

And in any event I think the real value of the type intersection would be in passing through the original type. Remember that this is a compile-time intersection and not type reflection so we're using concealed code generation to improve run-time performance compare to a reflection-based approach and at the same time seeking to increase both simplicity of expression and DRYness.

As a localised type-safe macro expansion this would be a coder win.

<snip/>

> we've got interfaces. they seem sufficient to me. if you want
> an intersection of operations, we can define an interface that
> encapsulates that intersection.

Only for user-defined types.

> defining intersection on other type properties seems like
> a hard problem, and probably not one that's going to lead
> towards a better language IMHO.


It's already been solved for reflection, so it is possible and much less of a deviation from Go's current design than a full-blown generics system would be.

roger peppe

unread,
Mar 6, 2012, 7:39:27 AM3/6/12
to Eleanor McHugh, golang-nuts
On 6 March 2012 12:08, Eleanor McHugh <ele...@games-with-brains.com> wrote:
> On 6 Mar 2012, at 10:48, roger peppe wrote:
>> On 6 March 2012 02:22, Steven Blenkinsop <stev...@gmail.com> wrote:
>>> On Mar 5, 2012, at 8:40 AM, roger peppe <rogp...@gmail.com> wrote:
>>>>
>>>> i'm not sure what you mean by union type in this context.
>>>> C-style union types are not type safe, and can be discounted, i think.
>>>> discriminated union types wouldn't do the job either, as you'd
>>>> have to decide which branch to choose when using the type.
>>>
>>>
>>> The union would have to be discriminated by the value's dynamic type,
>>> the way interfaces are. In order to be a useful abstraction, though,
>>> it would have to support the intersection of the operations supported
>>> by its component types.
>>
>> yes. that's the hard bit.
>
> It is in the general case, so let's not go down that rabbit-hole.
>
> We already know a lot about Go's type categories from the design of the reflection system, so we should let that guide our thinking on this. Indeed consistency with the type guarantees provided by reflection would be essential to consistent type behaviour.
>
>> for instance, Eleanor's original example was this:
>>
>> switch f := sometype.(type) {
>> case func(int), func(interface{}):
>>   f(100)
>> }
>>
>> how do we define the intersection between
>> func(int) and func(interface{}) in such a way
>> that the above function call is legal?
>
> This one's fairly easy to describe. The intersection is that both functions are callable with a single parameter of one of a range of types, and the code which follows should be able to provide that one parameter in any of the types required by the specified functions.

does the intersection go deeper than the top level?
what about func()func(int) vs func()func(interface{}) ?

or func()interface{Foo() int; Bar(int)} vs func() interface{Foo()
interface{}); Bar(interface{})}?

if you can come up with a coherent one- or two-paragraph specification
of how this could work, i'll be surprised.

IMHO it would introduce lots of complexity with limited applicability.
type switches aren't that common. i think that by working a lot on
"wannabe generic" code, you perhaps have a slightly unusual
perspective on how useful this feature would be.

> And in any event I think the real value of the type intersection would
> be in passing through the original type.

how can you pass through the original type if you don't know what it is?

if i've got some code like:

switch y := x.(type) {
case A, B:
Foo(y)
}

how can i write Foo such that the above code can compile?

the only way i can see this working is if y has x's original
type, which is the current behaviour.

Steven Blenkinsop

unread,
Mar 6, 2012, 4:51:14 PM3/6/12
to roger peppe, Eleanor McHugh, golang-nuts
On Mar 6, 2012, at 7:39 AM, roger peppe <rogp...@gmail.com> wrote:
does the intersection go deeper than the top level?
what about func()func(int) vs func()func(interface{}) ?

or func()interface{Foo() int; Bar(int)} vs func() interface{Foo()
interface{}); Bar(interface{})}?

if you can come up with a coherent one- or two-paragraph specification
of how this could work, i'll be surprised.

IMHO it would introduce lots of complexity with limited applicability.
type switches aren't that common. i think that by working a lot on
"wannabe generic" code, you perhaps have a slightly unusual
perspective on how useful this feature would be.

I think, in order to pursue this train of thought, we have to reframe the discussion a bit. What you're talking about, in this case, is interface and function polymorphism. This is admittedly non-trivial but also has much broader applicability than just type switches. 

It would be easy enough to specify by adding assignability cases along the lines of:
• x's type V and T are function types with the same number of parameters and return values, each parameter of V is assignable to the corresponding parameter of T, each return value of T is assignable to the corresponding return value of V, and at least one of V and T is unnamed.
• T is an interface type, and for each method on T, x's type V has a method of the same name with the same number of parameters and return values such that each parameter of the method on V is assignable to the corresponding parameter of the method on T, and each return value of the method on T is assignable to the corresponding return value of the method on V.
(this second one may be better handle in the definition of "implements" but I stated it this way for simplicity)

The obvious implementation would be to generate wrappers that compute the cross-type assignments, though there could be better ones. The question would then be how to refer to the polymorphic type as a concrete type, or as an abstraction? With interfaces, you already have a distinction, but none exists for function types. Making this distinction may be necessary in order to limit the depth of wrappers you may need, as well as making them nicer to use in type switches and perhaps easing implementation.

how can you pass through the original type if you don't know what it is?

if i've got some code like:

switch y := x.(type) {
case A, B:
  Foo(y)
}

how can i write Foo such that the above code can compile?

the only way i can see this working is if y has x's original
type, which is the current behaviour.

If A and B are both compatible with some interface/abstraction T ⊈ V, where V is the type of x, then `func Foo(t T)`. 

roger peppe

unread,
Mar 7, 2012, 11:52:27 AM3/7/12
to Steven Blenkinsop, Eleanor McHugh, golang-nuts
On 6 March 2012 21:51, Steven Blenkinsop <stev...@gmail.com> wrote:
> On Mar 6, 2012, at 7:39 AM, roger peppe <rogp...@gmail.com> wrote:
>
> does the intersection go deeper than the top level?
> what about func()func(int) vs func()func(interface{}) ?
>
> or func()interface{Foo() int; Bar(int)} vs func() interface{Foo()
> interface{}); Bar(interface{})}?
>
> if you can come up with a coherent one- or two-paragraph specification
> of how this could work, i'll be surprised.
>
> IMHO it would introduce lots of complexity with limited applicability.
> type switches aren't that common. i think that by working a lot on
> "wannabe generic" code, you perhaps have a slightly unusual
> perspective on how useful this feature would be.
>
>
> I think, in order to pursue this train of thought, we have to reframe the
> discussion a bit. What you're talking about, in this case, is interface and
> function polymorphism. This is admittedly non-trivial but also has much
> broader applicability than just type switches.
>
> It would be easy enough to specify by adding assignability cases along the
> lines of:
> • x's type V and T are function types with the same number of parameters and
> return values, each parameter of V is assignable to the corresponding
> parameter of T, each return value of T is assignable to the corresponding
> return value of V, and at least one of V and T is unnamed.

it seems to me that that would mean that

case func(int), func(int32):

would have give rise to nothing callable, which kinda loses everything
that Eleanor was aiming towards AFAICS.

> • T is an interface type, and for each method on T, x's type V has a method
> of the same name with the same number of parameters and return values such
> that each parameter of the method on V is assignable to the corresponding
> parameter of the method on T, and each return value of the method on T is
> assignable to the corresponding return value of the method on V.
> (this second one may be better handle in the definition of "implements" but
> I stated it this way for simplicity)

same kind of problem.

> If A and B are both compatible with some interface/abstraction T ⊈ V, where V is the type of x, then `func Foo(t T)`.

i remain unconvinced. go currently has no notion of "compatible" type.
i can't see this working
without large degrees of added complexity, which doesn't seem worth it to me.

Reply all
Reply to author
Forward
0 new messages