How do generic functions work in EK9?
← Getting Started · Ref: Q58
EK9 functions can have type parameters using 'of type T' for single-parameter or 'of type (S, T)' for multi-parameter generics. Generic functions serve as templates that create concrete function types when instantiated with specific types.
SINGLE TYPE PARAMETER
transformer() of type T as open -> item as T <- result as T?
MULTI TYPE PARAMETER
mapper() of type (S, T) as open -> item as S <- result as T := T()
The 'as open' modifier allows these to be extended with concrete types.
INSTANTIATING GENERIC FUNCTIONS
Create a concrete dynamic function from a generic template by specifying types:
intDoubler <- () is transformer of Integer as function result:=? item * 2
intToString <- () is mapper of (Integer, String) as function result: $item
IMPLICIT SUPER GENERATION
When you write '() is transformer of Integer as function', the compiler AUTOMATICALLY creates a concrete function type 'transformer of Integer' as the super type. You do not need to declare this type separately — it is generated implicitly from the generic template. This is unique to EK9.
The auto-generated super has the correct parameterised signature: 'transformer of Integer' has '-> item as Integer, <- result as Integer?'. This ensures type safety without boilerplate.
CONSTRAINING TYPE PARAMETERS
Use 'constrain by' to restrict which types can be used:
shapeHandler() of type T constrain by Shape as open -> shape as T <- result as Boolean := false
With the constraint, you can call methods defined on Shape within the function body. Without constraints, only standard operators are available.
GENERIC FUNCTIONS WITH 'extends'
Both 'is' and 'extends' work:
otherDoubler <- () extends transformer of Integer as function result:=? item + item
NAMED GENERIC FUNCTION IMPLEMENTATIONS
doubleInteger() is transformer of Integer -> item as Integer <- result as Integer: item * 2
In Java, generics use type erasure and cannot create new types at runtime. In Rust, generics are monomorphised but closures cannot participate in generic hierarchies. In Go, generics (since 1.18) have no function type hierarchies. EK9's generic functions with implicit super generation provide type-safe, nominal, zero-boilerplate generic function hierarchies.
See Q51 for abstract functions. See Q52 for dynamic functions. See Q53 for variable capture in generic function instances. See Q54 for built-in generic function types (Predicate, Comparator, UnaryOperator). See Q55 for passing generic function instances as delegates. See Q89 for stream pipeline basics. See Q194 for generic classes. See Q195 for generic constraints. See Q235 for how generic function types are used as stream pipeline stages.
Example
defines module qa.genericfunction defines function transformer() of type T as abstract -> item as T <- result as T? mapper() of type (S, T) as abstract -> item as S <- result as T? defines program GenericFunctionDemo() stdout <- Stdout() // Instantiate generic function with Integer intDoubler <- () is transformer of Integer as function result:=? item * 2 stdout.println(`doubled: ${intDoubler(21)}`) // Multi-parameter generic — Integer to String intToString <- () is mapper of (Integer, String) as function result: $item stdout.println(`mapped: ${intToString(42)}`) // Using 'extends' instead of 'is' intTripler <- () extends transformer of Integer as function result:=? item * 3 stdout.println(`tripled: ${intTripler(10)}`) // Store generic function instances in a list transforms <- [intDoubler, intTripler] for transform in transforms stdout.println(`transform(5): ${transform(5)}`)
Common mistakes
E50100 — Implementing a generic function without specifying the type parameter triggers E50100 — type is generic but no parameters were supplied. You must specify the concrete type when implementing a generic function. See ek9 -h E50100 for details.
Incorrect:
intDoubler <- () is transformer as function
Correct:
intDoubler <- () is transformer of Integer as function
E06020 — If a generic function has two type parameters, providing only one triggers E06020 — incorrect number of parameters supplied. Match the number of type arguments to the generic declaration. See ek9 -h E06020 for details.
Incorrect:
intToString <- () is mapper of Integer as function
Correct:
intToString <- () is mapper of (Integer, String) as function
Other ways to ask this
- How do I define a function with type parameters in EK9?
- What are implicit super functions in EK9?
- How does EK9 auto-create parameterised function supers?
Coming from another language?
Java: generics use type erasure (no runtime type information), functional interfaces can be generic but lambdas do not create named types, no implicit super generation, wildcard complexity (? extends T, ? super T). Python: no generics until 3.12, typing.Generic is runtime-invisible, no compile-time enforcement, no function type hierarchies. JavaScript: no generics, TypeScript has structural generics but no function type hierarchies. Rust: generics are monomorphised (good performance), impl Trait for generic returns, but closures cannot form generic type hierarchies, complex lifetime annotations with generics. Go: generics since 1.18, no function type hierarchies, limited type constraints. C#: generics with runtime type information, delegates can be generic, but no function type inheritance. Kotlin: generics similar to Java with reified keyword for some runtime info, no function type hierarchies, variance annotations (in/out). Swift: generics with associated types and protocols, no function type hierarchies. EK9: generic functions with 'of type T' or 'of type (S, T)', implicit super generation creates parameterised function types automatically, 'constrain by' for type bounds, 'as open' for extensibility, nominal typing throughout.
Keywords: template, implicit, generation, super, first, type, instantiate, start, parameter, open, migrate, parameterised, intro, generic, function, beginner, constrain