What does a generic require of its type argument when its body uses an operator on T (E07620, E07621)?

← Generics · Ref: Q2249

Every operator the generic's body applies to 'T' becomes a requirement on each type argument, checked where the generic is parameterised ('sumOf() of Total'), not inside the generic.

The body is checked once, against the operators every 'T' is assumed to have, and each has a fixed return type: '+', '-', '*', '/', 'abs', '~', '#<', '#>' return 'T' itself; 'mod', 'rem', 'length', '#?', '<=>' return an Integer; 'empty', 'contains', 'matches', '?' and the comparisons return a Boolean. So for each operator the body uses, the type argument must:
- HAVE it, taking the argument type where the body passes a 'T' - otherwise E07620;
- RETURN what the body uses it as, that return type with 'T' replaced by the argument (or a sub-type of it) - otherwise E07621.

A 'Total' whose '+' returns an Integer has the operator, but 'sumOf' would hand that Integer back as a Total: E07621. An INHERITED operator counts too: a 'Leaf' that has its super class Base's '+', returning Base, does not return a Leaf. Parameterise with the super type ('sumOf() of Base' still accepts Leaf values), or override the operator with a covariant return.

No promotion ('#^') is made, because the body was written for the assumed return type. A constrained 'T' ('of type T constrain by Total') is different: its body uses Total's own operators with their real return types, so nothing is assumed and nothing more is checked.

Example

#!ek9
defines module qa.genericsdeep.operator.requirements

  defines function

    //The body uses '+' on T as returning T
    sumOf() of type T
      ->
        first as T
        second as T
      <- rtn as T: first + second

  defines class

    Total
      amount as Integer?

      Total() as pure
        amount :=? Integer()

      Total() as pure
        -> initial as Integer
        amount :=? Integer(initial)

      amountOf() as pure
        <- rtn as Integer: Integer(amount)

      //A Total plus a Total is a Total, as 'sumOf' needs
      operator + as pure
        -> other as Total
        <- rtn as Total: Total(amount + other.amountOf())

      default operator ?

      operator $ as pure
        <- rtn as String: $amount

    Base as open
      amount as Integer?

      Base() as pure
        amount :=? Integer()

      Base() as pure
        -> initial as Integer
        amount :=? Integer(initial)

      amountOf() as pure
        <- rtn as Integer: Integer(amount)

      operator + as pure
        -> other as Base
        <- rtn as Base: Base(amount + other.amountOf())

      default operator ?

      operator $ as pure
        <- rtn as String: $amount

    //Inherits Base's '+', which returns a Base: 'sumOf() of Leaf' would be E07621
    Leaf is Base
      Leaf() as pure
        super()

      Leaf() as pure
        -> initial as Integer
        super(initial)

  defines program

    OperatorRequirements()
      stdout <- Stdout()

      totals <- sumOf() of Total
      stdout.println(`Total: ${totals(Total(3), Total(4))}`)

      integers <- sumOf() of Integer
      stdout.println(`Integer: ${integers(3, 4)}`)

      //Parameterise with the super type: it still accepts Leaf values
      bases <- sumOf() of Base
      stdout.println(`leaves as Base: ${bases(Leaf(3), Leaf(4))}`)

Common mistakes

E07621 — 'sumOf' uses '+' on T as returning T, so with 'sumOf() of Total' the '+' on Total must return a Total; an Integer would be handed back as a Total. Return the type itself, or constrain the generic's T by Total. See ek9 -h E07621.

Incorrect:

        <- rtn as Integer: amount + other.amountOf()

Correct:

        <- rtn as Total: Total(amount + other.amountOf())
Other ways to ask this
  • Why is 'sumOf() of Total' an error when Total has a '+' operator?
  • Why does E07621 say my operator does not return the type the generic uses it as?
  • Can I give a generic a type whose '+' returns an Integer?
  • Why can't I use a subclass as the type argument when its super class has the operator?

Coming from another language?

Java generics cannot use operators on a type parameter at all; an interface bound ('T extends Addable<T>') states the requirement in the declaration. C++ templates check each use at instantiation, with long error messages from deep inside the template. EK9 checks each operator the body uses against the type argument where the generic is parameterised, return type included, and names the generic, the operator and both types in one error.

Keywords: E07620, E07621, constrain by, generic, operator on T, parameterise, return type, type argument