Going thorough "Type parameters proposal" with Go 1.18 beta

338 views
Skip to first unread message

Kamil Ziemian

unread,
Feb 4, 2022, 7:58:49 PM2/4/22
to golang-nuts
Hello,

I title say, I download Go 1.18 beta (at this moment "go version go1.18beta2 linux/amd64") and I try to work through "Type Parameters Proposal" with it (https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md). This thread is about thing that I can't code properly into Go 1.18 beta.

I hope that you forgive me if I ask some questions before reading full proposal for the second time, previously I read in the September of the last year, and trying every solution obvious to good gopher. I have a little time and energy recently and I still want to try my hand with generics ASAP.

I read and try every example from "Tutorial: Getting started with generics" (https://go.dev/doc/tutorial/generics), but it didn't help me with my problems. Maybe I just not advanced enough gopher (I'm at most medicore gopher) or I'm to tired recently to think creatively about Go.

My first stumbling block is from section "Generic types" (https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#generic-types). At the end of it we have code.
> type StringableVector[T Stringer] []T
>
> func (s StringableVector[T]) String() string {
>         var sb strings.Builder
>         for i, v := range s {
>                 if i > 0 {
>                         sb.WriteString(", ")
>                 }
>                 // It's OK to call v.String here because v is of type T
>                 // and T's constraint is Stringer.
>                 sb.WriteString(v.String())
>         }
>         return sb.String()
> }

So, I try to code it.

> package main
>
> import (
>         "fmt"
>         "strings"
> )
>
> type Stringer interface {
>         String() string
> }
>
> type StringableVector[T fmt.Stringer] []T
>
> type stupidFloat float64
>
> func (sF stupidFloat) String() string {
>         return fmt.Sprintf("Stupid float %v", float64(sF))
> }
>
> func main() {
>         var varStupidFloat stupidFloat = -1.0
>
>         sliceStupidFloat := make([]stupidFloat, 3)
>
>         for i := 0; i < 3; i++ {
>                 sliceStupidFloat[i] = stupidFloat(float64(i))
>         }
>
>         fmt.Println("stupidFloat.String(): ", varStupidFloat.String())
>
>         fmt.Println("sliceStupidFloat:", sliceStupidFloat)
>
>         fmt.Println("sliceStupidFloat:", sliceStupidFloat)
        // fmt.Println("sliceStupidFloat.String():", sliceStupidFloat.String())
> }
>
> func (s StringableVector[T]) String() string {
>         var sb strings.Builder
>
>         for i, v := range s {
>                 if i > 0 {
>                         sb.WriteString(", ")
>                 }
>                 sb.WriteString(v.String())
>         }
>
>         return sb.String()
> }

It works fine and produce result.
> stupidFloat.String():  Stupid float -1
> sliceStupidFloat: [Stupid float 0 Stupid float 1 Stupid float 2]
> sliceStupidFloat: [Stupid float 0 Stupid float 1 Stupid float 2]

But, when I uncommon last line in function main I get
> ./Go-s11-08.go:46:61: sliceStupidFloat.String undefined
> (type []stupidFloat has no field or method String)

I try to change my code in several ways, but result is always the same. How correct code should look like?

Best regards,
Kamil

Ian Lance Taylor

unread,
Feb 4, 2022, 8:57:23 PM2/4/22
to Kamil Ziemian, golang-nuts
sliceStupidFloat is a variable of type []stupidFloat. As such, it
does not have a String method. That doesn't have anything to do with
generics, it's just how the Go language works.

Perhaps the line that you are looking for, rather than the commented
out line, is

fmt.Println("sliceStupidFloat.String():",
StringableVector[stupidFloat](sliceStupidFloat).String())

Ian

Kamil Ziemian

unread,
Feb 5, 2022, 5:44:06 AM2/5/22
to golang-nuts
Thank you very much for information. I think now I understand my problem. I was thinking about method just as a function ("methods are just a functions" is one line from "A Tour of Go", that I know but don't understand its depth-in-simplicity) and for some reason I assumed StringableVector[T Stringer] is a kind of constraint. A such my thinking was that sliceStupidFloat is of type []stupidFloat, when stupidFloat satisfying constraint Stringer, so function (method) find that such constraint is satisfied.

Good gopher probably from the start would understand that since StringableVector[T Stringer] is NOT an interface type, it is a concrete generic type, not constraint. (Is calling something "concrete generic type" correct statement?) My stupid excuse is that materials on generics in Go focus on using generic functions, not generic types. I don't want to be misunderstood, in materials about Go I find a lot of examples how to define and call generic function. There is also many examples of defining generic types, but very little of creating and using objects (?) of generic types.

For example "Tutorial: Getting started with generics" (https://go.dev/doc/tutorial/generics) deals only with generic functions. I guess some additional tutorial about generic types will be good thing for people like me.

Best regards,
Kamil

Kamil Ziemian

unread,
Feb 5, 2022, 7:25:32 PM2/5/22
to golang-nuts
Excuse me, but I'm lost again. I think I read it in the "A Tour of Go" and "Go FAQ", that "In Go types just are" and "There is no implicit conversion of types in Go". Because of that code below doesn't compile.

> package main
>
> import "fmt"
>
> type someFloat float64
>
> func main() {
>         var floatVar float64 = 1.0
>         var someFloatVar someFloat = floatVar
>
>         fmt.Println(someFloatVar)
> }

We need explicit type conversion "someFloat(floatVar)" to make it compile.

On the other hand this code compiled
> package main
>
> import "fmt"

>
> type Stringer interface {
>         String() string
> }
>
> type StringableVector[T Stringer] []T
>
> type someFloat float64
>
> func (sF someFloat) String() string {
>         return fmt.Sprintf("someFloat: %v", float64(sF))
> }
>
> func main() {
>         var varStringer Stringer = someFloat(7)
>         sliceSomeFloat := make([]someFloat, 3)
>
>         var varStringableVector StringableVector[someFloat] = sliceSomeFloat
>
>         fmt.Printf("varStringer type: %T\n", varStringer)
>         fmt.Printf("sliceSomeFloat type: %T\n", sliceSomeFloat)
>         fmt.Printf("varStringableVector type: %T\n", varStringableVector)
> }

and produced result

> stringerVar type: main.someFloat
> sliceScomeFloat type: []main.someFloat
> stringableVectorVar type: main.StringableVector[main.someFloat]

Variable stringableVectorVar is not of interface type, because in such case its type printed by fmt.Printf should be []main.someFloat. So, it looks like to me as []main.someFloat is implicitly conversed to main.StringableVector[main.someFloat].

Answer to my previous questions was that []stupidFloat/[]someFloat is not of type StringableVector[stupidFloat] so it doesn't have method String() string. But in my poor understanding of Go, this code shouldn't compile due to implicit conversion of two types.

Can anyone explain to me, where am I wrong?

Best,
Kamil

Ian Lance Taylor

unread,
Feb 5, 2022, 9:47:36 PM2/5/22
to Kamil Ziemian, golang-nuts
On Sat, Feb 5, 2022 at 4:27 PM Kamil Ziemian <kziem...@gmail.com> wrote:
>
> On the other hand this code compiled
> > package main
> >
> > import "fmt"
> >
> > type Stringer interface {
> > String() string
> > }
> >
> > type StringableVector[T Stringer] []T
> >
> > type someFloat float64
> >
> > func (sF someFloat) String() string {
> > return fmt.Sprintf("someFloat: %v", float64(sF))
> > }
> >
> > func main() {
> > var varStringer Stringer = someFloat(7)
> > sliceSomeFloat := make([]someFloat, 3)
> >
> > var varStringableVector StringableVector[someFloat] = sliceSomeFloat
> >
> > fmt.Printf("varStringer type: %T\n", varStringer)
> > fmt.Printf("sliceSomeFloat type: %T\n", sliceSomeFloat)
> > fmt.Printf("varStringableVector type: %T\n", varStringableVector)
> > }
>
> and produced result
>
> > stringerVar type: main.someFloat
> > sliceScomeFloat type: []main.someFloat
> > stringableVectorVar type: main.StringableVector[main.someFloat]
>
> Variable stringableVectorVar is not of interface type, because in such case its type printed by fmt.Printf should be []main.someFloat. So, it looks like to me as []main.someFloat is implicitly conversed to main.StringableVector[main.someFloat].
>
> Answer to my previous questions was that []stupidFloat/[]someFloat is not of type StringableVector[stupidFloat] so it doesn't have method String() string. But in my poor understanding of Go, this code shouldn't compile due to implicit conversion of two types.
>
> Can anyone explain to me, where am I wrong?

You are not permitted to assign directly from one named type to
another named type. But here you are assigning an unnamed type,
[]someFloat, to a named type, StringableVector[someFloat]. Assigning
an unnamed type to a named type is permitted if the underlying type of
the named is identical to the unnamed type, which in this case it is.
The same is true in non-generic Go. The exact rules are at
https://go.dev/ref/spec#Assignability.

Ian

Kamil Ziemian

unread,
Feb 6, 2022, 6:20:54 AM2/6/22
to golang-nuts
Thank you so much, about explanation. This is the first time that I hear about named type and unnamed type in Go.

Thank you even more for giving my another reason to learn Go Spec. I try it before, but then I was lost in section about runes, so I go to learn UTF-8. But soon enough I understand that to learn UTF-8 I need to go learn about Unicode and I try to find time for it.

Best regards,
Kamil

Kamil Ziemian

unread,
Feb 11, 2022, 12:37:48 PM2/11/22
to golang-nuts
Hello,

If you don't mind, I have few editorials suggestions to "Type parameters proposal".

Last code example in section "Type parameters" (https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#type-parameters) have very big and unecesary indentation, it should be removed.

At the end of "The any constraint" (https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#the-constraint) I would change "discussed separately)." to "discussed separately.)". This placement of dot before bracket is used in other parts of this document.

In first code example in "Mutually referencing type parameters"
(https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#mutually-referencing-type-parameters) we have function signature
"func New[Node NodeConstraint[Edge], Edge EdgeConstraint[Node]] (nodes []Node) *Graph[Node, Edge]".
It would be more consistent with Go style as I know it, if we remove white space placed before "(nodes []Node)".

Best,
Kamil

Kamil Ziemian

unread,
Feb 15, 2022, 5:16:09 AM2/15/22
to golang-nuts
Hello,

While reading "Type Parameters Proposal" (https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md) I can't find a direct answer to question stated below. I should notice that I have idea how it works, but I'm not sure if it is correct.

Consider slightly changed code (https://go.dev/play/p/R1gK9bdLXV0?v=gotip) from section "Element constraint example" (https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#element-constraint-example). It shows that after passing `sliceMySlice` to the generic function `Double` it change a type from `MySlice` to `[]int`. My question is, why this change of type happens?

Here how I understand this. Function `Double` has signature
`func Double[E MyInteger](s []E) []E`
so calling
`Double(sliceMySlice)`
requires function argument type inference. To do this we need to unify type of `MySlice` to `[]int`. This can be done because [_For type unification, two types that don‘t contain any type parameters are equivalent if they are identical, or if they are channel types that are identical ignoring channel direction, or if their underlying types are equivalent._](https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#type-unification). And from my understanding last case is applied here. Which give us `E` -> `int`. We now need to instantiated function `Double[int]` by replacing in all palaces type parameter `E` with type argument `int` and compiling such function.

In next step we take instantiated function `#Double[int](s []int) []int` and call `#Double[int](sliceMySlice)`. Assigning value of type `MySlice` to variable of type `[]int` is permissible in Go, so this is how this change of type happens. I hope that I correctly understood that `[]int` is unnamed (?) type.

I checked proposal for occurrence of word "instatiation", it find 13 results. Of them 3 referee to instatiation of Go generic function.

Section "Omissions". "No currying. There is no way to partially instantiate a generic function or type, other than by using a helper function or a wrapper type. All type arguments must be either explicitly passed or inferred at instantiation time.".

Question "Why not use the syntax F<T> like C++ and Java?". "When parsing code within a function, such as v := F<T>, at the point of seeing the < it's ambiguous whether we are seeing a type instantiation or an expression using the < operator." "Without type information, it is impossible to decide whether the right hand side of the assignment is a pair of expressions (w < x and y > (z)), or whether it is a generic function instantiation and call that returns two result values ((w<x, y>)(z))."

"Instantiating a function" (https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#instantiating-a-function). "Go normally permits you to refer to a function without passing any arguments, producing a value of function type. You may not do this with a function that has type parameters; all type arguments must be known at compile time. That said, you can instantiate the function, by passing type arguments, but you don't have to call the instantiation. This will produce a function value with no type parameters."

This is the whole text of "Instantiating a function" and it is not to helpful for me in understanding how it works. Ideas and notations that I used above come from Robert Griesemer's talk "Typing [Generic] Go" from GopherCon 2020 (https://www.youtube.com/watch?v=TborQFPY2IM), whiteout it I would probably have no clue what may happen. I still can understand it wrongly, but it is my fault.

I hope this is not too silly question.

Best,
Kamil

Axel Wagner

unread,
Feb 15, 2022, 7:21:49 AM2/15/22
to Kamil Ziemian, golang-nuts
On Tue, Feb 15, 2022 at 11:18 AM Kamil Ziemian <kziem...@gmail.com> wrote:
Hello,

While reading "Type Parameters Proposal" (https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md) I can't find a direct answer to question stated below. I should notice that I have idea how it works, but I'm not sure if it is correct.

Consider slightly changed code (https://go.dev/play/p/R1gK9bdLXV0?v=gotip) from section "Element constraint example" (https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#element-constraint-example). It shows that after passing `sliceMySlice` to the generic function `Double` it change a type from `MySlice` to `[]int`. My question is, why this change of type happens?

Here how I understand this. Function `Double` has signature
`func Double[E MyInteger](s []E) []E`
so calling
`Double(sliceMySlice)`
requires function argument type inference. To do this we need to unify type of `MySlice` to `[]int`. This can be done because [_For type unification, two types that don‘t contain any type parameters are equivalent if they are identical, or if they are channel types that are identical ignoring channel direction, or if their underlying types are equivalent._](https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#type-unification). And from my understanding last case is applied here. Which give us `E` -> `int`. We now need to instantiated function `Double[int]` by replacing in all palaces type parameter `E` with type argument `int` and compiling such function.

In next step we take instantiated function `#Double[int](s []int) []int` and call `#Double[int](sliceMySlice)`. Assigning value of type `MySlice` to variable of type `[]int` is permissible in Go, so this is how this change of type happens. I hope that I correctly understood that `[]int` is unnamed (?) type.

Yes, your understanding seems correct.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/546dbb9e-9868-4791-ac63-953cf067da79n%40googlegroups.com.

Axel Wagner

unread,
Feb 15, 2022, 7:27:42 AM2/15/22
to Kamil Ziemian, golang-nuts
Also, just as a side-note: The nomenclature "named type" is obsolete. The concept was renamed to "defined type" in Go 1.9. I assume Ian mistakenly used it out of habit. But just so you are not confused about the terms when reading other material.

And, I know that you asked to respect your wishes in regard to not reading the Go spec, but to be clear, you really don't need to know anything significant about Unicode or UTF-8 to understand the Go spec. I don't really know anything about it either, except that a) Unicode is a standard trying to assign a number to ~any character used in human languages and b) UTF-8 is a standard for encoding those numbers into bytes. I don't think you really need to know anything beyond that to understand the Go spec.

Kamil Ziemian

unread,
Feb 15, 2022, 8:47:19 AM2/15/22
to golang-nuts
"Also, just as a side-note: The nomenclature "named type" is obsolete. The concept was renamed to "defined type" in Go 1.9. I assume Ian mistakenly used it out of habit. But just so you are not confused about the terms when reading other material."

Thank you Axel for this information. I was thinking that they are both synonyms, because it was of these rare cases when I look at spec.

"And, I know that you asked to respect your wishes in regard to not reading the Go spec, but to be clear, you really don't need to know anything significant about Unicode or UTF-8 to understand the Go spec. I don't really know anything about it either, except that a) Unicode is a standard trying to assign a number to ~any character used in human languages and b) UTF-8 is a standard for encoding those numbers into bytes. I don't think you really need to know anything beyond that to understand the Go spec."

Yes, it is stupid excuse of not reading spec. I try to read it last October and after first paragraph I started reading Wikipedia page about EBNF notation, because I was so confused. After that I went back and stuck on the part of runes, so I go read about Unicode. Amount of materials on Unicode homepage is intimidating to me. For some reasons strings and runes are hardest part of Go to grasp for me. I can use them in the manner "shut up and write the code", but I don't feel satisfied. I read quite a lot about them, so next logical step is to read about Unicode. This is some stupid viscus circle for me.

"I don't think you really need to know anything beyond that to understand the Go spec." Thank you for words of encouragement, I will try to read spec soon. I hope that I won't again stuck on runes. Runes combined with UTF-8 make my brain into Mobius strip.

Best,
Kamil

Brian Candler

unread,
Feb 15, 2022, 10:15:28 AM2/15/22
to golang-nuts
As Axel says, for normal use there's nothing much to it.

* A rune is an int32, which represents a "character", roughly speaking.  More accurately this is a Unicode "code point". Unicode says that certain sequences of code points are treated in special ways when rendered on the display. But Go doesn't care about that.

* UTF8 is a way of packing runes into lists of bytes with a variable-length encoding.  In particular, runes from 0 to 127 pack into the corresponding single byte 0 to 127, which means that "normal" ASCII characters are just as they always were.

That's it.

If you have a particular character you want to use, then Google for it. e.g. Google "unicode lightening bolt" and you'll find it's code point decimal 9889 (hex 26A1), and in UTF8 that encodes to the three-byte sequence 0xE2 0x9A 0xA1. If you want to know *how* it's encoded like that, the Wikipedia UTF-8 page shows it clearly, but if you need to convert runes to UTF-8 or vice versa you'd use a library to do it.

Kamil Ziemian

unread,
Feb 15, 2022, 5:31:23 PM2/15/22
to golang-nuts
Thank you Brian Cadler for short summary. After reading "Strings, bytes, runes and characters in Go" blog post (https://go.dev/blog/strings), I tried to read "Text normalization in Go" blog post (https://go.dev/blog/normalization) and my brain twisted into Mobius strip. I hope one day I will understand what is going on with that.

I collected all things that I noticed to this point about "Type parameters proposal" and wrote on the side, I post it here. After that I will go and try to read the Go Spec, when I end it I will probably go back with more questions.

1) In the first paragraph of "Constraint type inference" (https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#constraint-type-inference) there is a sentence "Constraint type inference is useful when a function wants to have a type name for an element of some other type parameter, or when a function wants to apply a constraint to a type that is based on some other type parameter.". I'm not sure what "a function wants to have a type name for an element of some other type parameter" means. Does it means that we have some "generic type parameter" (type parameter that takes another type parameter as `NodeConstraind[Edge]`) or something else?

2) In section "Function argument type inference" (https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#function-argument-type-inference) we read that function argument type inference happens in two passes and "When constraint type inference is possible, as described below, it is applied between the two passes.".

But in the section "Constraint type inference" (https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#constraint-type-inference) we read that "When constraint type inference is possible, type inference proceeds as followed:". Below list of the stages from this section
a) Build the mapping using known type arguments.
b) Apply constraint type inference.
c) Apply function type inference using typed arguments.
d) Apply constraint type inference again.
e) Apply function type inference using the default types of any remaining untyped arguments.
f) Apply constraint type inference again.

3) Second code example in "Type inference" (https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#type-inference) has very big indentation. It should be removed.

4) In section "Function argument type inference" (https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#function-argument-type-inference) first, second and fourth code examples have very big indentation. They should be removed.

Best,
Kamil

Ian Lance Taylor

unread,
Feb 15, 2022, 5:49:33 PM2/15/22
to Kamil Ziemian, golang-nuts
On Tue, Feb 15, 2022 at 2:33 PM Kamil Ziemian <kziem...@gmail.com> wrote:
>
> Thank you Brian Cadler for short summary. After reading "Strings, bytes, runes and characters in Go" blog post (https://go.dev/blog/strings), I tried to read "Text normalization in Go" blog post (https://go.dev/blog/normalization) and my brain twisted into Mobius strip. I hope one day I will understand what is going on with that.

In case it isn't clear, the blog post
https://go.dev/blog/normalization is about using Go to do text
normalization. Text normalization is not a part of the Go language.


> 1) In the first paragraph of "Constraint type inference" (https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#constraint-type-inference) there is a sentence "Constraint type inference is useful when a function wants to have a type name for an element of some other type parameter, or when a function wants to apply a constraint to a type that is based on some other type parameter.". I'm not sure what "a function wants to have a type name for an element of some other type parameter" means. Does it means that we have some "generic type parameter" (type parameter that takes another type parameter as `NodeConstraind[Edge]`) or something else?

In a function like

func Insert[S ~[]E, E any](s S, i int, v ...E) S {

E is a type name for an element of the type parameter S.


> 2) In section "Function argument type inference" (https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#function-argument-type-inference) we read that function argument type inference happens in two passes and "When constraint type inference is possible, as described below, it is applied between the two passes.".
>
> But in the section "Constraint type inference" (https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#constraint-type-inference) we read that "When constraint type inference is possible, type inference proceeds as followed:". Below list of the stages from this section
> a) Build the mapping using known type arguments.
> b) Apply constraint type inference.
> c) Apply function type inference using typed arguments.
> d) Apply constraint type inference again.
> e) Apply function type inference using the default types of any remaining untyped arguments.
> f) Apply constraint type inference again.

Yes, we're still working on the exact details of how inference is
applied. The spec is the canonical reference here. When this has
settled I may go back and update the proposal document.


> 3) Second code example in "Type inference" (https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#type-inference) has very big indentation. It should be removed.

Hmmm, I don't see this. The second example is meant to be code that
appears inside a function body, and at least on my web browser it
seems to be indented the same as other code in function bodies.


> 4) In section "Function argument type inference" (https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#function-argument-type-inference) first, second and fourth code examples have very big indentation. They should be removed.

Same.

Ian
Reply all
Reply to author
Forward
0 new messages