Hi there
I am aware that the proposal to add generics has been accepted
<https://github.com/golang/go/issues/43651#issuecomment-776944155>, so
the discussion of whether or not Go will get generics is answered. For
better or for worse.
I worked on a different approach since a few years, not very intensive,
and in spite of this fact, I now want to tell you about:
Binding interface types on import
<https://github.com/leiser1960/importbinding/blob/master/README.md>
See in: https://github.com/leiser1960/importbinding/blob/master/README.md
I did not speek up yet, because it simply was not ready for discussion
in my eyes.
Why do I think the accepted proposal is not good enough?
Technically it seem sound and consistent to me and has all the bells and
whistles you would expect. And all the Features. And close enough to the
way other languages define generics.
So what do i dislike?
Hmmm... Let me answer another question first:
What do I love about Go?
Its *simplicity. *And Simplicity is Complicated
<https://www.youtube.com/watch?v=rFejpH_tAHM>
And that is IMHO the problem of the accepted generics proposal, the lack
of simplicity.
To explain this, let me start with a language design question:
What is needed for adding "monomorphic generic types" to Go?
Go already has a polymorphic generic type mechanism: "interface types".
So you have to start from this (as the accepted proposal does) and add
to things:
- A way to define type parameters.
- A way to bind the type parameters to concrete types at compile time
The accepted proposal does so by:
1. Adding positional type parameters to type and function definitions
2. Adding a syntax for binding concrete types to the type
parameters on use of the types and functions.
With the proper brackets it is readable. And of course there are more
goodies in the propsal such as:
- An extension to the descriptive power of "interface types".
- type inference rules to eliminate the need of the additional type
parameters in certain cases.
Both are great for functional libraries such as "sort.go", but does not
help for container types such as "container/list.go".
And the proposal is long because any generic type mechanism is
complicated to implement and tricky to integrate in the language.
This is the obviously complicated part.
But the proposal lacks the simplicity and elegance of for example:
- the way Go integrates inheriting method from a typ by simply
omitting the attribute name in a struct definition
- the way Go defines the types of numerical constant expressions
for the purpose of type inference
incredibly simple to use and explain. No need for an additional
syntactic element at all.
And completely different from the way other languages treat this.
The accepted proposal more or less follows the syntactic path taken
since Ada, over C++ and Java and all the other languages.
It this good enough?
Can we do better?
I think so. But How? Remember we need to do two things:
- A way to define type parameters.
- A way to bind the type parameters to concrete types at compile time
Is suggest doing this on the package level, not the individual types or
functions:
1. Use named interface types as implicit type parameters of a package
2. Bind the named types on import
With the first I simply mean any declaration of the form:
type ElementType interface {}
e.g. The type Locker <https://golang.org/pkg/sync/#Locker>in sync.go.
You do not find such a declaration in "containter/list.go
<https://golang.org/pkg/container/list>" . But it would be simple task
to add such a line and replace all existing occurences of "interface{}"
by "ElementType". Not changing the behaviour at all.
As a go proverb says: interface nothing says nothing
<https://go-proverbs.github.io/>
But after we named it "ElementType" we can get a hold of it and bind it
in the package we intend to use e.G. a "list of strings":
import "container/list" type ElementType string
augmenting the existing "import" clause by this type binding. We say:
We bind (the binding) type "string" to (the bound type)
"list.ElementType".
We bind the type string to the type list.ElementType in the import
of package list.
It is as simple as this.
But what does this mean?
It means two things:
1. binding type must implement the bound type.
2. the bound type is treated as if defined by:
type BoundType BindingType
in the imported package.
1. is trivial in our example, because interface{} is implemented by any
time.
2. is trivial in our example, because "string" is a standard type.
The general case is not so trivial, think of the binding type being
defined locally and private to the package.
I tried to give a semantic description in my definition in Binding
interface types on import
<https://github.com/leiser1960/importbinding/blob/master/README.md> but
it is not as elaborate as the accepted proposal.
The semantics is defined by a set of program transformations back to a
Go 1 program.
Is this *simplicty*?
I think so, ok not as simple as inheritance, because I have to add a
syntactic element: The type binding on Import.
But definitely a simpler than the accepted proposal.
Last features. If you need two different bindings in one package, say a
list of string and a list of int you may use:
import "container/list" intlist type ElementType = int
import "container/list" stringlist type ElementType = string
And then simply pick either the package name: "intlist" or "stringlist" e.g.
myintlist := intlist.New()
mystringlist := stringlist.New()
This makes myintlist.Value of type int, whereas mystringlist.Value is of
type string.
And if you think of the type "Map" in "sync.go
<https://golang.org/pkg/sync/#Map>": You need a way to bind more than
one type on a single import: sync.Key and sync.Value:
import "sync.go" type Key string; Map mySyncdata
But now we have all of the syntax by example. Simple to use.
You may not bind all "named interface types" on the import, the above
does not bind the Type "Locker <https://golang.org/pkg/sync/#Locker>",
so the "Cond <https://golang.org/pkg/sync/#Cond>" type remains polymorphic.
The definition of a generic package is go 1 compatible.
You only have to "name" the parameter types for use by type binding
imports. Which improves the documentation as well.
Not convinced? Lets move from Go 1 polymorphic generics to Go 2
monomorphic use, again using the "container/list.go" example.
It requires two sorts of changes:
1. Add the required type binding to the import clause, see above
2. delete the type casts needed for accessing the "Value" Attribute
of the "Element" struct:
if "" = (string)mystringlist.Value {
has to be changed to:
if "" = mystringlist.Value {
Try the same with the accepted proposal.
But a last word:
Simplicity is Complicated <https://www.youtube.com/watch?v=rFejpH_tAHM>
So behind the scenes there is more to it:
- there is an additional restriction for "named interface types"
necessary in order to allow them to be used as type parameters.
- there are new semantic hurdles because of the type parameters
being on the package level.
- I did not describe any "type inference rules" yet, but it should
be possible to add this for typical functional libraries such as
"sort.go <https://golang.org/pkg/sort/>".
- type bindings my even be transitive, if the binding type is itself
a exported interface type.
So the a full fledged proposal may end up as long as the accepted
proposal. That is the complicated part anyway.
But it is this the kind of *simplicity *I want to see when talking about
monomorphic generic types. Perhaps someone can do even better?
Yours Martin Leiser
--
You received this message because you are subscribed to the Google Groups
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit
https://groups.google.com/d/msgid/golang-nuts/6d48e04e-eade-6dc5-fb05-f1c6a7151a43%40gmail.com.