Interface constraints

The previous page discussed structural constraints, but those are limited to constraints based on the fields of a struct. What about its function receivers? Let’s first rewrite the example from the previous page as a distinct constraint that is not inlined to the function that uses it:

  1. // Ledgerish expresses a constraint that may be satisfied by types that have
  2. // ledger-like qualities.
  3. type Ledgerish[T ~string, K Numeric] interface {
  4. ~struct {
  5. ID T
  6. Amounts []K
  7. SumFn SumFn[K]
  8. }
  9. }

Again, we know that Ledger[T, K] does have a function receiver:

  1. // PrintIDAndSum emits the ID of the ledger and a sum of its amounts on a
  2. // single line to stdout.
  3. func (l Ledger[T, K]) PrintIDAndSum() {
  4. fmt.Printf("%s has a sum of %v\n", l.ID, l.SumFn(l.Amounts...))
  5. }

So how do we write the following function to match Ledgerish in such a way that we can also invoke PrintIDAndSum:

  1. // PrintLedger emits a ledger's ID and total amount on a single line
  2. // to stdout.
  3. func PrintLedger[T ~string, K Numeric, L Ledgerish[T, K]](l L) {
  4. l.PrintIDAndSum()
  5. }

If we tried it as it is now, it would fail (Golang playground):

  1. func main() {
  2. PrintLedger(Ledger[string, complex64]{
  3. ID: "fake",
  4. Amounts: []complex64{1, 2, 3},
  5. SumFn: Sum[complex64],
  6. })
  7. }

The above example will produce the following compilier error:

  1. ./prog.go:60:4: l.PrintIDAndSum undefined (type L has no field or method PrintIDAndSum)

The secret this time is to remember that Ledgerish is a Go interface, and as such, can have methods defined on it:

  1. // Ledgerish expresses a constraint that may be satisfied by types that have
  2. // ledger-like qualities.
  3. type Ledgerish[T ~string, K Numeric] interface {
  4. ~struct {
  5. ID T
  6. Amounts []K
  7. SumFn SumFn[K]
  8. }
  9. PrintIDAndSum()
  10. }

Now the above example works as intended (Golang playground):

  1. fake has a sum of (6+0i)

But hey, wait a minute! If interfaces can be used to express constraints, when and why would the strictness of a structural constraint be desired over a traditional, functional, interface-based constraint?


Next: Careful constructors