In the Go 2 generics draft design , my only real issue with contracts is their readability; contracts today are too clever . Contracts have been carefully set up to cover the type constraints that people will want to express for generics, and I think that something like contracts is a better approach to type constraints for generics than something like interfaces (but that's another entry ). Can we make contracts more readable without significantly changing the current Go generics proposal? I believe we can, through use of social convention and contract embedding.
For being read and written by people, the problem with contracts today is that they do not always clearly say what they mean. Instead they say it indirectly, through inferences and implicit type restrictions. To make contracts more readable, we need to provide a way to say tricky things directly. One obvious way to do this is with a standard set of embeddable contracts that would be provided in a package in the standard library. Let's call this package
generic/require
(because there probably will be a standard
generic
package of useful generic functions and so on).
The
require
package would contain contracts for standard and broadly useful properties of types, such as
require.Orderable()
,
require.String()
, and
require.Unsigned()
. When writing contract bodies it would be standard practice to use the
require
versions instead of writing things out by hand yourself. For example:
contract Something(t T) {
require.Unsigned(t)
// instead of: 1 << t
}
Similarly, you would use '
require.Integer(t)
' instead of any of the various operations that implicitly restrict
t
to being an integer (signed or unsigned), and
require.Numeric
if you just needed a number type, and so on. Of course, contracts from the
require
package could be used on more than direct type parameters of your contract; they could be used for return values from methods, for example.
The obvious advantage of this for the reader is that you have directly expressed what you require in a clear way. For cases like
require.Integer
, where there are multiple ways of implementing the same restriction, you've also made it so that people don't have to wonder if there's some reason that you picked one over the other.
It's likely that not everything in contracts would be written using things from the
require
package. Some restrictions on types would be considered obvious (comparability is one possible case), and we would probably find that
require
contracts don't make other cases any easier to read or write. One obvious set of candidates for
require
contracts are cases where there are multiple ways of creating the same type restriction.
(In the process the Go developers might find that there were gaps in the type restrictions that you could express easily, as exposed by it being difficult or impossible to write a
require
contract for the restriction. For example, I don't think there's currently any easy way to say that a type must be a signed integer type.)
Use of the
require
package would be as optional as the use of Go's standard formatting style, and it would be encouraged in much the same way, by the tacit social push of tools. For instance, '
gofmt -s
' could rewrite your contracts to use
require
things instead of hand-rolled alternatives, and
golint
or even
vet
could complain about 'you should use
require.<X>
instead of ...'. Faced with this push towards a standard and easy way of expressing type restrictions, I believe that most people would follow it, much as how
gomft
has been accepted and become essentially universal.
An open question is if this would add enough more readability to significantly improve contracts as they stand. I'm not sure that a number of the trickier restrictions in the full draft design could be implemented in this sort of reusable contract restriction in a way that both is usable and is amenable to a good, clear name. People may also disagree over the relative readability of, say:
contract stringer(x T) {
var s string = x.String()
// equivalent:
require.String(x.String())
// or perhaps:
require.Returns(x.String, string)
}
At a certain point it feels like one would basically be spelling out phrases and sentences in the form of
require
package contracts. I'm not sure that's a real improvement, or if a more fundamental change to how contracts specify requirements is needed for true readability improvements.
Sidebar: Augmenting the language through the back door
The
require
package would also provide a place to insert special handling of type restrictions that can't be expressed in normal Go, in much the way that the
unsafe
package is built in to the compiler (along with various other things ). People might find this too awkward for some sorts of restrictions, though, since using
require
has to at least look like normal Go and even then, certain sorts of potentially implementable things might be too magical.