Link: Getting specific about generics

444 views
Skip to first unread message

Charlton Trezevant

unread,
Sep 2, 2018, 4:08:48 AM9/2/18
to golang-nuts
Link: [Getting specific about generics, by Emily Maier](https://emilymaier.net/words/getting-specific-about-generics/)

The interface-based alternative to contracts seems like such a natural fit- It’s simple, straightforward, and pragmatic. I value those aspects of Go’s philosophy and consider them to be features of the language, so it’s encouraging to see a solution that suits them so well. The author also does a great job of contextualizing the use cases and debate around generics, which I found incredibly helpful.

Any thoughts?

Tristan Colgate

unread,
Sep 2, 2018, 4:14:48 AM9/2/18
to Charlton Trezevant, golang-nuts
It's a great read, clarified stuff for me. An approach that embraces interfaces feels preferable to me.


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

wilk

unread,
Sep 2, 2018, 4:25:46 AM9/2/18
to golan...@googlegroups.com

On 02-09-2018, Tristan Colgate wrote:
> --000000000000633c2e0574df037c
> Content-Type: text/plain; charset="UTF-8"
> Content-Transfer-Encoding: quoted-printable
>
> It's a great read, clarified stuff for me. An approach that embraces
> interfaces feels preferable to me.

+1


--
William

haskell...@yahoo.de

unread,
Sep 2, 2018, 6:56:04 AM9/2/18
to golang-nuts
I prefer seeing the contract by example over having a combination of two dozens of interface names like Eq, Lesser, Adder, Muler, Convertible(x), Ranger, Lener, Caper, ... that have to be mentally mapped to their actual syntactic representation. This smells like taxonomy ("the lowest form of academic work" ;)

Sebastien Binet

unread,
Sep 2, 2018, 10:50:46 AM9/2/18
to haskell...@yahoo.de, golang-nuts
Hi,


On Sun, Sep 2, 2018, 12:55 haskell_mustard via golang-nuts <golan...@googlegroups.com> wrote:
I prefer seeing the contract by example over having a combination of two dozens of interface names like Eq, Lesser, Adder, Muler, Convertible(x), Ranger, Lener, Caper, ... that have to be mentally mapped to their actual syntactic representation. This smells like taxonomy ("the lowest form of academic work" ;)

I am all for original features and perhaps indeed using interfaces in lieu of contracts would be better.
And I guess I could live with a zoo of special interfaces to capture all the '+=', == and other special operators.
But, as it was mentioned in the FAQ of the draft proposal, how would one express the need for an explicit field (name + perhaps type too) ?

How would interfaces-as-contracts play out for example if I wanted to make sure the Image type passed to that parametric function is a struct, with a field Pixels that's a slice of quadruplets R, G, B, A (that hold some type of numerical value)
So redoing some of the x/image/draw kernels, sans the interfaces overhead?
And without declaring a bunch of SetFoo, SetBar and GetBar methods?

-s

Bakul Shah

unread,
Sep 2, 2018, 12:09:26 PM9/2/18
to golang-nuts
People may find this excellent paper “Datatype-Generic Programming” by Jeremy Gibbons useful in this discussion. It’s 72 pages long. https://www.cs.ox.ac.uk/jeremy.gibbons/publications/dgp.pdf

I want generics but I still don’t know if I like this go2 generics proposal. At least as an exercise it may be worth reimplementing go1’s builtin generics using the proposed features (not syntax but functionality). Plus standard generic collections including things like N dim. vectors. Hopefully this extended exercise would make it clear if the goal of making it easier to write correct code is better met with this proposal.

roger peppe

unread,
Sep 2, 2018, 6:19:56 PM9/2/18
to Bakul Shah, golang-nuts


On Sun, 2 Sep 2018, 5:09 pm Bakul Shah, <ba...@bitblocks.com> wrote:
People may find this excellent paper “Datatype-Generic Programming” by Jeremy Gibbons useful in this discussion. It’s 72 pages long. https://www.cs.ox.ac.uk/jeremy.gibbons/publications/dgp.pdf

I want generics but I still don’t know if I like this go2 generics proposal. At least as an exercise it may be worth reimplementing go1’s builtin generics using the proposed features (not syntax but functionality). Plus standard generic collections including things like N dim. vectors. Hopefully this extended exercise would make it clear if the goal of making it easier to write correct code is better met with this proposal.

Writing standard generic collections seems like a worthy goal but N-dimensional vectors seem to me like they might not fall within scope, as a large part of getting those right is making them suitably syntactically convenient to express, and that doesn't sound like a core generics goal.

Bakul Shah

unread,
Sep 2, 2018, 8:20:20 PM9/2/18
to roger peppe, golang-nuts

On Sep 2, 2018, at 3:19 PM, roger peppe <rogp...@gmail.com> wrote:



On Sun, 2 Sep 2018, 5:09 pm Bakul Shah, <ba...@bitblocks.com> wrote:
People may find this excellent paper “Datatype-Generic Programming” by Jeremy Gibbons useful in this discussion. It’s 72 pages long. https://www.cs.ox.ac.uk/jeremy.gibbons/publications/dgp.pdf

I want generics but I still don’t know if I like this go2 generics proposal. At least as an exercise it may be worth reimplementing go1’s builtin generics using the proposed features (not syntax but functionality). Plus standard generic collections including things like N dim. vectors. Hopefully this extended exercise would make it clear if the goal of making it easier to write correct code is better met with this proposal.

Writing standard generic collections seems like a worthy goal but N-dimensional vectors seem to me like they might not fall within scope, as a large part of getting those right is making them suitably syntactically convenient to express, and that doesn't sound like a core generics goal.

Two separate issues. The generics code can work out an implementation model for various common formats (dense, sparse etc,). Syntax is the easy part, done in N different languages in a very similar way.

M[i, j] -- an element
M[i] -- a row alternate: M[i, :]
M[,j] -- a column alternate: M[:, j]
M[a:b, c:d] -- a sub-matrix

Things like linear algebra and matrix multiplication etc. are integral to machine learning, a pretty important field these days. Initially it would be ok to use

M.Elem(i, j)
M.Row(i)
M.Col(j)
M.Sub(a, b, c, d)

But I am not sure this is doable in the present proposal....

May be there should be a separate proposal but I envision it would be built using some sort of generic code.

Robert Engels

unread,
Sep 3, 2018, 2:03:24 AM9/3/18
to golang-nuts
I do like using interfaces only, and having the language declare some built-in ones and automatically map them to the operators. A problem is that for consistency they should go both ways... and then you end up with operator overloading which I've never been a fan of because people use it to write really obtuse code.

Ian Lance Taylor

unread,
Sep 3, 2018, 7:19:59 PM9/3/18
to Charlton Trezevant, golang-nuts
It's a very nice writeup.

It's worth asking how the graph example in the design draft would be
implemented in an interface-based implementation. Also the syntactic
issues are real.

Ian

xingtao zhao

unread,
Sep 4, 2018, 12:52:07 PM9/4/18
to golang-nuts
My five cents:

1) the methods of the type template are defined by interface style
2) operators are retrieved implicitly from function body
3) function-calls inside are also retrieved implicitly from the function body

For graph example, we may declare it as:

type Edgeser(type E) interface {
    Edges() []T
}
type Nodeser(type N} interface {
    Nodes() from, to N
}
type Graph(type Node Edgers(Edge), type Edge Nodeser(Node)) struct { ... }

xingtao zhao

unread,
Sep 4, 2018, 4:49:34 PM9/4/18
to golang-nuts


On Tuesday, September 4, 2018 at 9:52:07 AM UTC-7, xingtao zhao wrote:
My five cents:

1) the methods of the type template are defined by interface style
2) operators are retrieved implicitly from function body
3) function-calls inside are also retrieved implicitly from the function body

There is no need for 3). We only need to check if the parameter types satisfy the constraint for the functions that are called inside.

Steven Blenkinsop

unread,
Sep 4, 2018, 5:26:58 PM9/4/18
to xingtao zhao, golang-nuts

If we try to translate contracts into interfaces, the first thing we run into is that there's no way to refer to the dynamic type of the interface. Compare:

 

contract Fooer(t T) {

     interface{ Foo() T }(t)

}

 

to

 

type Fooer interface {

     Foo() ???

}

 

There would have to be some sort of self type:

 

type Fooer interface {

     Foo() self

}


Let's say there's a built in
convertibleTo constraint
 

type FooConvertible interface {

     convertibleTo(int)

     Foo() self

}

 

For one thing, this looks like a method. (Aside: This is a problem in the existing generics proposal as well if generic interfaces are allowed, since interface embedding would become ambiguous.) Presuming that we solve this ambiguity, there's a deeper challenge. If I have a generic interface:

 

type Taker(type T) interface {

     Take() T

}

 

In general, a given concrete type could only satisfy this interface one way. Having

 

type IntAndFloatTaker interface {

     Taker(type int)

     Taker(type float)

}

 

wouldn't work because the same type can't implement both func (...) Take() int and func (...) Take() float. convertibleTo (and convertibleFrom) would be unique in this regard, and could get in the way of (say) being able to infer the type parameters of

 

func Take(type T, U Take)(t T) U { t.Take() }

 

T can be inferred from the argument, and you ought to be able to infer U from T, since T can only implement Taker(type U) for exactly one U. But, if convertibleTo exists, this isn't true in general.


This doesn't address operators. You could have builtin constraints for each operator, or each set of operators. Or you could have some sort of builtin method corresponding to each operator. These are certainly doable, but designing and naming the constraints/methods is a whole undertaking which the contracts draft seeks to avoid.


--

xingtao zhao

unread,
Sep 4, 2018, 6:07:32 PM9/4/18
to golang-nuts


On Tuesday, September 4, 2018 at 2:26:58 PM UTC-7, Steven Blenkinsop wrote:

If we try to translate contracts into interfaces, the first thing we run into is that there's no way to refer to the dynamic type of the interface. Compare:

 

contract Fooer(t T) {

     interface{ Foo() T }(t)

}

 

to

 

type Fooer interface {

     Foo() ???

}

 

There would have to be some sort of self type:

 

type Fooer interface {

     Foo() self

}


Let's say there's a built in
convertibleTo constraint
 

type FooConvertible interface {

     convertibleTo(int)

     Foo() self

}

 

For one thing, this looks like a method. (Aside: This is a problem in the existing generics proposal as well if generic interfaces are allowed, since interface embedding would become ambiguous.) Presuming that we solve this ambiguity, there's a deeper challenge. If I have a generic interface:

 

type Taker(type T) interface {

     Take() T

}

 


I was assume that generic interfaces are allowed, which we do not need "self" type. In terms of convertableTo, it can be put into the operator category, which will be implicitly retrieved from the function body. 
 

In general, a given concrete type could only satisfy this interface one way. Having

 

type IntAndFloatTaker interface {

     Taker(type int)

     Taker(type float)

}


I think this is still not allowed, as they have the same function name while different types.
 

 

wouldn't work because the same type can't implement both func (...) Take() int and func (...) Take() float. convertibleTo (and convertibleFrom) would be unique in this regard, and could get in the way of (say) being able to infer the type parameters of

 

func Take(type T, U Take)(t T) U { t.Take() }

 

T can be inferred from the argument, and you ought to be able to infer U from T, since T can only implement Taker(type U) for exactly one U. But, if convertibleTo exists, this isn't true in general.


This doesn't address operators. You could have builtin constraints for each operator, or each set of operators. Or you could have some sort of builtin method corresponding to each operator. These are certainly doable, but designing and naming the constraints/methods is a whole undertaking which the contracts draft seeks to avoid.


As we do not have operator overloading, there are only a few sets of operations for each type, for example Numeric, Addable, Hashable, Equality, etc, and they can never be changed or extended. These are completely hiden/invisible from user as operator constraints are retrieved implicitly. So we do not have to name them.

The only thing a contract can do while interface can not, is that we can not have constraints on structure fiels, as only methods are allowed in interface.

Steven Blenkinsop

unread,
Sep 4, 2018, 9:30:16 PM9/4/18
to golang-nuts
On Tuesday, September 4, 2018 at 6:07:32 PM UTC-4, xingtao zhao wrote:
I was assume that generic interfaces are allowed, which we do not need "self" type.

 

This is not equivalent. If you have an interface defined as

 

type Fooer(type T) interface {
     
Foo() T
}


this corresponds to a contract written as

 

contract Fooer(s S, t T) {
     
interface{ Foo() T }(s)
}


You have two separate type parameters when you're really just trying to talk about a single type. You had to write it like this because your language is poorly designed. When using the interface, you'd always have to write 

 

func DoFoo(type T Fooer(T)) ...


Requiring a generic trait to be used reflexively just so you can talk about operations involving multiple values with the same type isn't a good design. If would be like saying "we don't have to be able to name the receiver variable inside a method because we can always pass the receiver value as a separate parameter, like v.String(v)!". This is technically true, but if your language is designed like this, I don't want to use it. Requiring type constraints to be written like this is the same. You can't use the unfamiliarity of a new feature to justify making decisions that would be clearly bad ones in a more familiar context.

 

In terms of convertableTo, it can be put into the operator category, which will be implicitly retrieved from the function body. 

 

"Can" and "should" are two different things. Assume when reading my previous and current response that I'm talking about a design where all operators and conversions supported by a type parameter have to be specified by its contract/interface constraint and can't be implied by the body of the function. Having only a partially specified interface makes it very easy to accidentally change the interface of your function by, for example, calling a function which directly or indirectly performs an operation not previously present in your code.

 

In general, a given concrete type could only satisfy this interface one way. Having
 
type IntAndFloatTaker interface {
     Taker(type int)
     Taker(type float)
}  

I think this is still not allowed, as they have the same function name while different types.

 

That's my point. I'm saying that this could be a useful property. Given a parameterized interface I and a type parameter T that implements I(U), there can only be exactly one value of U for a given T. This could be used to assist type inference. If an interface can contain conversions, however, then there are multiple ways the same type can satisfy that interface—since a type can support conversions to multiple different types—which means this property wouldn't hold. I know you don't think conversions should go in interfaces/contracts, but I disagree, so here we are. This property also wouldn't hold if generic methods were added to the language. Hypothetically, if you could have


type Setter(type T) interface {
     
Set(T)
}

type S
struct{}
func
(s S) Set(type T C)(t T) { ... }


and if this allowed S to satisfy Setter(T) for any type T that satisfies C, then the above property wouldn't hold. The current proposal doesn't suggest this, and I think it's probably incompatible with the design goal of being implementation agnostic, but it would create a problem if this functionality were ever enabled in the future and the previous behaviour had been used to inform type inference. The solution used by other languages to solve this dilemma is to have type aliases in their equivalent of interfaces:


type Setter interface {
     type T
     
Set(T)
}

type S
struct{}
func
(s S) Set(type T C)(t T) { ... }

// This would fail to compile because the compiler can't
// infer the identity of `Setter.T` for `S` based on
// `S.Set`, since the method can work for any type `T`
// that satisfies `C`:
//
//     var _ Setter = S{}
//


Note that my intent here is just to explore the design space. I'm not sure whether this is a direction the Go team would be willing to go.

roger peppe

unread,
Sep 5, 2018, 2:11:06 AM9/5/18
to xingtao zhao, golang-nuts
On Tue, 4 Sep 2018, 5:52 pm xingtao zhao, <zhaox...@gmail.com> wrote:
My five cents:

1) the methods of the type template are defined by interface style
2) operators are retrieved implicitly from function body
3) function-calls inside are also retrieved implicitly from the function body

For graph example, we may declare it as:

type Edgeser(type E) interface {
    Edges() []T
}
type Nodeser(type N} interface {
    Nodes() from, to N
}
type Graph(type Node Edgers(Edge), type Edge Nodeser(Node)) struct { ... }

Yes, that's entirely the solution I would have suggested were I to have been asked that question a month ago. But since then, contracts have enlightened me. :-)

The beauty of contracts is that they allow one to bundle up all of those relationships into one easy-to-call bundle. We no longer have to worry about the specific type parameters in common between Edgers and Nodesers.

The unique thing that contracts bring is the relationship between several different types - a contract is a bit like a function that returns several values - this is something that no other language construct in Go can do, even after tweaking.

Even if contracts only allowed interface conversions, they'd still be unique and useful.

 Operators and other constructs are syntax sugar - perhaps nice to have but not essential to contracts, in my view.

em...@emilymaier.net

unread,
Sep 9, 2018, 6:14:35 PM9/9/18
to golang-nuts
Great discussion, thanks for the comments! I wrote a bit more responding to things I saw here and elsewhere: https://emilymaier.net/words/another-generic-post/

Emily

Jonathan Amsterdam

unread,
Sep 10, 2018, 9:55:04 AM9/10/18
to golang-nuts
From the blog post:

For example there could be an eq interface that’s equivalent to a contract with an x == x. 

Actually, there can't. If eq were an interface, then 

   func f(x, y eq) bool { return x == y }

would be legal. And so would the call

     var a int
     var b float64
     f(a, b)

 since both int and float64 support ==. 

The problem is that the spec says that the operands of == have to be the same type, so this code is invalid. 
It's hard to see how to fix the problem without changing something fundamental about the language.

On Sunday, September 2, 2018 at 4:08:48 AM UTC-4, Charlton Trezevant wrote:

Jonathan Amsterdam

unread,
Sep 10, 2018, 10:01:53 AM9/10/18
to golang-nuts

The problem is that the spec says that the operands of == have to be the same type

Correction: one operand must be assignable to the type of the other. 

The example still stands: you can't compare an int and a float64. 

Michael Jones

unread,
Sep 10, 2018, 12:42:03 PM9/10/18
to jbams...@gmail.com, golang-nuts
also...if you want to get really specific...for float32 and float64, x==x may not be true. (NaNs)

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


--
Michael T. Jones
michae...@gmail.com

Jonathan Amsterdam

unread,
Sep 10, 2018, 6:59:05 PM9/10/18
to golang-nuts
Sorry, I'm wrong about `eq`. It could be an interface, because `==` is treated specially for interface types.

But you couldn't have an interface for any other operator, like `<` or `+`.
Reply all
Reply to author
Forward
0 new messages