Hey! I would like to join the discussion and add my 5 cents here, since I 
have been criticizing the contracts draft and I would like to show what 
were my points in order to support the current design draft.


I believe the original problem for the generics is to allow the same 
function to work on values of different types. So that You do not need to 
write something like AddInt, AddInt32, AddInt64 and so on. This is an 
example of situation when we would like to write something like Add(T) and 
make that T to be one of the list: int, int32, int64 - all of the types 
what we have the Add function defined for.


In this case Add(T) acts much like an expression evaluating to a different 
function depending on the T.


This can be called “parametrization by type” or “type parametrization” and 
I believe it is an exceptionally simple and straightforward way to do it.


Let me say a bit more about problems where need of contracts arise.


When we are writing in a context of type-parametrized function something 
like:


var a []T = []int{1, 2, 4}
a[2] = 3


It is okay to write []T[2] = T (assign a value of type T to an element of 
array of type T by index 2) and we do not need to care what is T.


But when we change a bit:


var a T = []int{1, 2, 4}
a[2] = 3


It becomes hard to judge whether we are allowed to write T[2] = 3 (assign 3 
to an element of type T by index 2). In order it to work, we need to state 
somehow [2] is allowed for T. There are a few ways to achieve it and the 
first one is the contracts:


type T interface{
    require(a, b) {
        a[2] = b
    }}


Okay, now, let’s say, we are allowed to write T[2] = 3. But let’s try a 
harder example


var a T = []int{1, 2, 4}
a[2] = 3
a = append(a, 4)


This will not work with the previously defined contract T, since a 
map[int]int value also fits it.


Should we write something like this:


type T interface{
    require(a, b) {
        a[2] = b
        a = append(a, b)
    }}


But what is the point in such syntax? How the compiler could guess and how 
exactly it should work? What if we write random garbage in the require body, 
will it allow us to write it right in the code without any checks?


type T interface{
    require(a, b) {
        a *@#&@#$= b
    }}
var a T *@#&@#$= b


Of course, we can restrict it to allow using only operators like [], == and 
so on, still how can we distinguish between a map and an array?


type T interface{
    require(a, b, i) {
        a[:] // only arrays and slices have this operator
        i++ // i is an integer
        a[i] = b // b is an element of array a
    }}


Is this enough to define what we want? Such a contract would also require 
the append builtin to be changed, but I believe it will only clutter up the 
implementation with unnecessary type assertions.


These “requires” act very much like predicates and are just tricky way to 
“shortly” define a list of types. That is to say, a++ means any type having 
a ++ operator defined for it no matter how exactly (which is a problem 
itself). Anyway, it is very easy, to define such a contract with a list, 
which is the second way of saying T[2] = 3 statement is allowed:


type Incrementable interface{
    type int, int32, ...}


Let’s get to Your example:


type structField interface {
  type struct { a int; x int },
    struct { b int; x float64 },
    struct { c int; x uint64 }}


It is clearly visible, that we need to parametrize by a single field type:


type Custom interface{
    type int, float64, uint64}
type structField(type T Custom) interface {
  type struct { a int; x T },
    struct { b int; x T },
    struct { c int; x T }}


Depending on the context, we also can write it like this:


type structField(type T Incrementable) interface {
  type struct { a int; x T },
    struct { b int; x T },
    struct { c int; x T }}


And even further, we can consider such a change:


type structField(type T) struct {
    a int
    x T}


And such a refactoring becomes now a pattern.


Instead of writing


type T interface{
    type map[int]string, map[int]bool}


We almost always should write:


type Z(type T) map[int]T


It is a way stricter way of defining not only “contracts” but also types 
allowing us to perform such things like appending elements to arrays, 
taking element of a map by its key, accessing a struct field and much more 
while remaining generic enough.


Thanks for the attention, I hope it helps.

-- 
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/164a69fd-50f3-4a0e-ad9d-5e243c3b007bo%40googlegroups.com.

Reply via email to