The proposal extends the Go 2 contracts (generics) draft design in four ways:
The gist of it is that the use of contracts should be explicit. For this contracts should act as their own adapters. Fundamental and custom types can be coerced to behave in a common way. The proposed contract switch is essential for this. Contracts need to become real executable code with a few special rules.
Contracts can declare their intended outcomes. They become real code for adapting types to this outcome:
contract WithLength(a T) int {
return len(a)
}
Contract outcomes can be applied directly:
func Max(type T Less)(first T, other ...T) T {
max := first
for _, val := range other {
if Less(max, val) {
max = val
}
}
return max
}
var x = Max(5, 3, 8)
Contracts with no declared outcomes cannot be applied.
As types can abide multiple contracts it is helpful to know which contract is applied where. Using the contract name for that purpose led to a design with contracts becoming their own adapters.
Contract switches react on the semantics of their case statements.
contract Less(a,b T) bool {
switch {
case: return a.Less(b)
case: return a < b
}
}
Case statements with semantic errors are silently ignored. Syntax errors are treated as always. The first applicable case statement is taken.
A type is considered to abide a contract if there is a valid code path to an explicit return statement. Contracts without declared outcomes don't need the return statement though. This allows contracts to steer types to behave in the most suitable way if any.
In contracts the control flow must not change outside of contract switch case statements.
Contracts may customize error reporting for specific cases. A code path that ends in a panic() reports its message as error:
contract Hash(t T) uint {
switch {
case: t = []byte{}; panic("byte slice hashing is not supported yet")
case: return t.Hash()
case: return uint(t)
}
}
Conformance to multiple contracts is a common trait of types. Requiring extra contracts just to combine them is bothersome, so a syntax for declaring multiple contracts per generic type is proposed:
type HashMap(type K Equal*Hash, type V) struct {
buckets [][]struct{key K; val V}
}
contract Equal(a, b T) bool {
switch {
case: return a.Equals(b)
case: return a == b
case: return string(a) == string(b)
}
}
contract Hash(t T) uint {
switch {
case: return t.Hash()
case: return uint(t)
}
}
Many thanks for your positive feedback and the ideas!
First I'd like to make some design decisions more clear
Having a way to ensure that the expressions/statements in each branch are equivalent would be very nice. But if this can only be accomplished by restricting the branches too tightly then the price is high, especially if we'd lose