Hi Marvin, I think a lot of us like the fact that when we see "a == b" in Go we know what it is without looking up a redefinition somewhere. To me, "a (==) b" is better if there is operator overloading, but I don't like it in any case.
One problem with operator overloading is that, to use operator overloading for numeric types, one needs to be able to overload constants (like Pi) in addition to operators for many numeric representations. Non numeric operator overloading is not something I want to see proliferate in Go code. Operator overloading for numeric types, on the other hand, I think would be nice. Also, wanted to suggest that there is already in the draft proposal an alternative to the contract-local keywords you propose below: there could be a core Go library package with a list of such contracts defined there that could grow over time without changing the parser. If you take your declarative keyword idea as embedding and have a standard package of available contracts, then there isn't, to me, much difference between your proposal and the draft proposal, except to add operator overloading. In the draft proposal, one could either use examples or build on a set of standard supplied contracts by embedding. Scott On Wednesday, 17 October 2018 21:57:48 UTC+2, Marvin Renich wrote: > > * Ian Lance Taylor <ia...@golang.org <javascript:>> [181016 17:59]: > > The contract system in the generics design draft does not have this > > problem. It may have other problems, but it doesn't have this one. > > I haven't finished reading the draft (I got to the beginning of the > Contract Details section when it was first mentioned on this list, but > then got sidetracked) nor have I read any of the counter proposals, > which is why I have not responded before now. > > I very much like the idea of contracts, but I do not like the specific > design. Programming by example is a cool idea, but it is simply a bad > practice when you want to clearly and unambiguously express the intended > behavior. While the draft design allows the compiler to unambiguously > determine the correct behavior, as has been pointed out many times in > the various discussions on this list, the specific code which the > programmer must use to convey his/her intent to the compiler is > extremely ambiguous to the programmer, and the meaning conveyed by a > previously written contract to a different programmer reading the code > is at least equally ambiguous. The example in the draft document > concerning value method vs. pointer method clearly demonstrates this. > This is contrary to the stated goals of the Go language with respect to > simplicity, scalability and maintainability. > > On the other hand, if you replace the body of the contract with a simple > declarative syntax, you regain these core attributes of the Go language. > > contract Readable(T) { > T: Implements(io.Reader) > } > > contract ComparableInt(U) { > U: EqualMethod(IsSame), ConvertableTo(int), ConvertableFrom(int) > } > > contract SortableRecord(V, W) { > V: IsSliceOf(W), Implements(sort.Interface) > W: Implements(Record) > } > > Each constraint that a programmer might want to use in a contract must > be specified by the language as a contract keyword. These keywords are > only special inside a contract body, so they do not interact in any way > with other Go code (e.g. there can be a method Implements on a type). > The contract syntax is such that it is unambiguous what tokens are > contract keywords and what are other language identifiers, such as > interfaces and types; e.g. in Implements(Implements) the first > Implements is a contract keyword and the second is the name of an > interface in the current package. > > Some contract keywords may not have any arguments. Comparable might > mean that the type is comparable using the primitive == operator. > > contract Comparable(X) { > X: Comparable > } > > Whether to require, allow, or forbid the empty parentheses is a detail > for later discussion. I.e. the constraint might be X: Comparable(). > > There would be no ambiguity between the above contract name Comparable > and the contract keyword Comparable, since the contract keyword has no > relevance outside the contract body. > > A contract keyword might have more than one argument, such as > M: IsMapOf(K, V). > > In the above examples, the contract keyword Implements declares that > type T must implement interface io.Reader. The keyword EqualMethod > declares that type U must have a method IsSame that has an argument and > result signature appropriate for overloading the == operator. > > I see no technical or readability reason to require, or even allow, the > extra names on contract arguments, such as in the draft example contract > convertible(_ To, f From). It is just as expressive to write contract > convertible(To, From). The form in the draft was necessary to allow > writing Go expressions that used both values and types to describe what > was permissible. My declarative syntax does not need this. > > It might be convenient to have a predefined contract for each contract > keyword that can be used without explicitly declaring the contract. The > predefined contract would be shadowed by an explicit contract with the > same name. The predefined contract will have an additional argument > preceding the arguments required by the keyword. E.g. > > func Massage(type T ConvertableTo(T, int))(item T) error {...} > > would use the predefined ConvertableTo contract that is equivalent to a > contract with one constraint, T: ConvertableTo(int). > > To switch gears to operator overloading, I would like to start by saying > that one of the fundamental beauties of Go is that looking at a small > piece of code out of context, a programmer can still glean a substantial > amount of information about what the code does, _especially_ about when > external code is called. I would very much dislike for the language to > be changed such that looking at «a == b» would leave me wondering if > some method of a or b would be called to evaluate that expression. > > I had thought of «a ==() b» as a possible syntax for "if the type of a > has a method that overloads ==, use it, otherwise if the primitive == > operator can be applied, use it, otherwise fail to compile." Someone > else (was it Michael Jones?) suggested «a (==) b». I kind of like my > syntax better because it has a "function call" look to it, but either > works for me. > > The point is that possibly overloaded operators should be explicitly > called out by the programmer writing the code. There should be no > reason to use such syntax outside of functions that have type > parameters. Whether that is allowed by the language (and just uses the > primitive) or produces a compiler error is a detail to be considered. > > Now to get to EqualMethod above. This contract keyword specifies that > the type has a method with the given name, and that the method takes one > argument of the same type and returns a bool. Each operator that the > language designers wish to allow to be overloaded will have a contract > keyword that associates the operator with the method specified in the > contract declaration. The method must have a signature given by the > language specification for that particular operator. The type may > forego implementing the method iff the compiler already knows how to > apply the operator to the type. > > So, for «type MyInt int» the EqualMethod(IsSame) would be satisfied > without any IsSame method, but if the type has the IsSame method, it > must have the correct signature or the compiler will complain, and the > method will be used in preference to the intrinsic == operator when the > overloaded ==() is used in the code. Using the non-overloaded == will > always ignore the IsSame method, producing a compiler error if the type > is not comparable. > > A similar keyword will be defined for each operator that the language > designers wish to allow to be overloaded. Rules such as in «a ==() b» > which operand is used as the method receiver and which is used as the > method argument are specified in the contract keyword definition. > > If it makes any sense (and I am not sure it does) to worry about > commutativity or associativity, the behavior will be specified in the > contract keyword definition. > > The specific list of contract keywords is left for discussion. > > This declarative syntax is much easier to write, read, and understand > than the "declaration by example" used in the draft. It allows for a > much larger variety of constraints than the "generics by interface" > proposals that I have seen described on this list, and it allows for > flexible operator overloading with only a trivial addition to the > language syntax that does not invalidate the Go 1 Compatibility Promise > while ensuring that non-overloaded operators will never be applied as > overloaded operators (no hidden function calls). > > The biggest (only?) downside is that it requires a predetermined, but > expandable, list of constraints. This is mitigated by the fact that > adding a new constraint in a future version cannot break existing code. > > The fact that each constraint requires a keyword is, to me, a non-issue, > since the contract keyword namespace is completely separate from all > other identifier namespaces. > > ...Marvin > > -- 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. For more options, visit https://groups.google.com/d/optout.