Getting Started with EK9 · Chapter 20
Using Generics
You have been using generics since Collections, without the name. A List of String, a Dict of (String, Integer), and the Optional and Result from Optional and Result are all generic
types. This chapter names the idea and shows how to read it. Writing your own generic types is a
topic for the second volume; here you learn to use the ones EK9 provides.
One definition, every type
A list of strings, a list of integers and a list of whatever you invent next week all need the same
code. You do not want a separate StringList, IntegerList and ShapeList, the same code written out
again for each element type. A generic type avoids that by being a template with a gap in it.
Ask EK9 for help on List (ek9 -h List) and the definition begins:
List of type T
T is a type parameter: a placeholder for the element type, filled in when you use the list. Fill
it with String and you have a List of String; fill it with Integer and you have a
List of Integer. Both are the one List, used two ways:
#!ek9 defines module book.generics defines program Show() stdout <- Stdout() names <- List() of String names += "Ada" names += "Alan" primes <- List() of Integer primes += 2 primes += 3 stdout.println(`names (List of String): ${names}`) stdout.println(`primes (List of Integer): ${primes}`) scores <- {"Ada": 90, "Alan": 85} stdout.println(`Ada scored ${scores.getOrDefault("Ada", Integer())}`)
names (List of String): ["Ada", "Alan"]
primes (List of Integer): [2, 3]
Ada scored 90
List() of String makes a new list and fills the gap: the type after of takes the place of T. Some
generics have more than one gap. A Dict needs a type for its keys and a type for its values, so it
is written Dict of (K, V). scores above, built from {"Ada": 90, ...}, is a
Dict of (String, Integer): string keys, integer values. Read a generic type left to right and it
tells you exactly what it holds.
The parameter is a promise
The type in the gap is a rule the compiler enforces. A List of String holds strings and only
strings. Try to add an integer:
names <- List() of String names += 42
and it will not build (wrongElement.ek9, in the error-examples folder):
EK9 : Error : E50060: '+=' on line 8 position 12: In relation to type 'List of String', and '+=(Integer)', parameter mismatch. Possible method(s) public Void <- +=(item as String),public Void <- +=(other as List of String): method/function not resolved
See: ek9 -h E50060
forAI: methodResolution
This is what makes generics safe as well as convenient. Because you can never put an integer into a
List of String, you can never get one out. One List serves every element type at no cost in
safety, because the element type travels with the list and is checked wherever it is used.
The family you already know
The generic types EK9 provides are the ones you have been using all along:
List of T— an ordered collection of one element type (Collections).Dict of (K, V)— keys of one type, values of another (Collections).Optional of T— either nothing or oneT(Optional and Result).Result of (O, E)— a success value or an error value (Optional and Result).- and others, such as
PriorityQueue of T, built the same way.
You do not always have to write the parameter. A literal ["Ada", "Alan"] is inferred as a
List of String, and {"Ada": 90} as a Dict of (String, Integer): the compiler reads the parameter
from the contents. When the contents cannot tell it — an empty list, say — you name the type yourself:
List() of String.
Two ways to "work for many types"
Abstract Types, Subtypes and Polymorphism gave the first answer to the wish to "write it once, use it
for many types": one Shape variable could hold a Circle or a Rectangle — many types sharing one
contract, the right behaviour chosen when the method is called. That is subtype polymorphism.
Generics are parametric polymorphism: one definition — List — that works for any type you fill
it with, with that type carried along and checked. One says "many types, one contract"; the other says
"one definition, any type". You will use both constantly.
Try it yourself
- Make a
List of Boolean, add a couple of values, and print it. - Build a
Dict of (String, List of Integer)— a dictionary whose values are themselves lists — and read one entry. Generics nest. - Add a value of the wrong type to a typed list and read the
E50060error. It lists the parameter types it would have accepted.
Recap
A generic type is a template with a type parameter: List of type T becomes a List of String
or a List of Integer when you fill the gap; Dict of (K, V) has two. The parameter is enforced — a
List of String rejects anything that is not a string (E50060) — which is what makes the reuse safe.
You already know the built-in family — List, Dict, Optional, Result and others — and the
inference that often fills the parameter for you. Generics are parametric polymorphism, the
companion to the subtype polymorphism of the previous chapters. Defining your own generic types
waits for the second volume. Next, Using Operators: giving your own types the same operators the
built-in ones have.
In the EK9 reference: Generics/Templates in EK9