Why cannot I use a function type as a generic constraint?

← Generics · Ref: Q647

EK9 does not allow function types as constraints in 'constrain by'. Only class types and traits can be used as constraints. Function types have a different dispatch model and cannot serve as type bounds.

PROHIBITED PATTERN

You cannot write:

  defines function
    CheckFunction()
      -> text as String
      <- rtn as Boolean: text?
    describeItem() of type T constrain by CheckFunction as open  // ERROR E06080
      -> item as T
      <- rtn as String: $item

Function types cannot be used with 'constrain by'.

WHY PROHIBITED

Function types in EK9 define signatures (input types and return type). They are not class hierarchies with methods. Constraining T to a function would mean T must be callable, but EK9's generic system works with method access, not call-ability.

CORRECT ALTERNATIVES

Use a trait or abstract class as the constraint:

  Handler of type T constrain by Processable

Define a trait with the methods you need, then constrain by that trait.

FUNCTION PARAMETERS

If you need to pass functions to generics, use function-typed parameters:

  defines function
    Mapper() as abstract
      -> inputText as String
      <- rtn as String?
    process()
      -> mapper as Mapper
      <- rtn as String: mapper("input")

Where Mapper is a defined function type, passed as a parameter rather than a constraint.

See Q195 for constrain by with classes. See Q646 for inference limits. See Q649 for generic function implementation.

Example

defines module qa.genericsdeep.nofunctionconstraint

  defines trait

    <?-
      Correct approach: define a trait with the methods you need.
      Then constrain by the trait, not by a function type.
    -?>
    Describable
      description() as pure abstract
        <- rtn as String?

  defines class

    Planet with trait of Describable
      planetName as String?

      default private Planet() as pure

      Planet() as pure
        -> n as String
        planetName :=? String(n)

      override description() as pure
        <- rtn as String: String(planetName)

      override operator ? as pure
        <- rtn as Boolean: planetName?

    Star with trait of Describable
      starName as String?

      default private Star() as pure

      Star() as pure
        -> n as String
        starName :=? String(n)

      override description() as pure
        <- rtn as String: String(starName)

      override operator ? as pure
        <- rtn as Boolean: starName?

  defines function

    <?-
      A function type - cannot be used as a constraint.
    -?>
    CheckFunction()
      -> text as String
      <- rtn as Boolean: text?

    <?-
      Correct: constrain by a trait, not a function.
      The trait provides the methods needed inside the generic.
    -?>
    describeItem() of type T constrain by Describable as open
      -> item as T
      <- rtn as String: item.description()

  defines program

    NoFunctionConstraintDemo()
      stdout <- Stdout()

      planet <- Planet("Mars")
      planetDesc <- describeItem(planet)
      stdout.println(`Planet: ${planetDesc}`)

      star <- Star("Polaris")
      starDesc <- describeItem(star)
      stdout.println(`Star: ${starDesc}`)

Common mistakes

E50010 — The constraining type must be a resolvable class or trait. Using a name that does not exist triggers a type-not-resolved error. See ek9 -h E50010 for details.

Incorrect:

constrain by NoSuchTrait as open

Correct:

constrain by Describable as open

E06080 — Generic types cannot be constrained by function types. Use a trait or class as the constraint. See ek9 -h E06080 for details.

Incorrect:

    describeItem() of type T constrain by CheckFunction as open

Correct:

    describeItem() of type T constrain by Describable as open
Other ways to ask this
  • What is E06080 constrained functions not supported?
  • Can I constrain a generic type parameter to a function type?
  • Why does EK9 prohibit function types in constrain by?

Coming from another language?

Java: can use functional interfaces as bounds '<T extends Function<S,R>>'. C++: concepts can constrain to callable. Rust: trait bounds with Fn/FnMut/FnOnce. Go: interfaces can include method signatures (callable). Kotlin: can bound by function types. EK9: function types excluded from 'constrain by', use traits instead.

Keywords: trait, constrain, bound, generic, function, E06080, type-parameter, constant, constraint, prohibited