Generics, 2 of 4
Generic types
Classes, Structs, and Interfaces with type parameters, and Methods with type parameters of their own.
What you’ll build
In this tutorial you build a Stack<T>, a Class that holds items of any one type, give it a
generic Method, and then write a generic Struct and a generic Interface. These are the same
building blocks std.collections uses for Map<K, V>, Set<T>, and the rest.
A generic Class
A Class takes type parameters after its name, as a function does. T is then a type throughout
the Class: in its fields, its Methods’ parameters, and their results.
Import std.core
Class Stack<T>
Field items: T[] = []
Method Push(item: T)
items.Add(item)
End Method
Method Pop(): T?
If items.Count == 0 Then Return Null
Local top = items[items.Count - 1]
items.RemoveAt(items.Count - 1)
Return top
End Method
Property Count: Int
Get => items.Count
End Property
End Class
Local undo = Stack<String>()
undo.Push("draw line")
undo.Push("fill")
Print(undo.Count)
Print(undo.Pop() ?? "nothing")
Print(undo.Pop() ?? "nothing")
Print(undo.Pop() ?? "nothing")2 fill draw line nothing
Stack<String>() makes a stack of Strings. Stack<String> and Stack<Int> are two different
types, each its own instance of the Class, so pushing an Int onto undo is a compile error.
A generic type is always written with its type arguments where a type goes. A parameter declared
as s: Stack is an error:
Import std.core
Class Stack<T>
Field items: T[] = []
End Class
Function Size(s: Stack): Int => s.items.Count
Print(Size(Stack<Int>()))Compiling main.fb main.fb:7:18: error: Stack<T> is generic, so it needs type arguments, like Stack<Int> | 7 | Function Size(s: Stack): Int => s.items.Count | ^^^^^ error: couldn't build main.fb because of 1 error
When you make an object, the compiler can infer the type arguments from what you pass to New,
as it does for a function’s call. With nothing passed, as for Stack<String>(), you write them,
unless the variable it’s assigned to has a type that gives them, as
Inference and defaults shows.
A generic Method
A Method can have type parameters of its own, after its Class’s. Map makes a new stack by
changing each item, so it needs a second type, U, for what the items become.
Import std.core
Class Stack<T>
Field items: T[] = []
Method Push(item: T)
items.Add(item)
End Method
Method Pop(): T?
If items.Count == 0 Then Return Null
Local top = items[items.Count - 1]
items.RemoveAt(items.Count - 1)
Return top
End Method
Method Map<U>(change: Func(T): U): Stack<U>
Local result = Stack<U>()
For item In items
result.Push(change(item))
End For
Return result
End Method
End Class
Local sizes = Stack<Int>()
sizes.Push(3)
sizes.Push(14)
Local labels = sizes.Map(Function(n) => "{n} px")
Print(labels.Pop() ?? "")
Print(labels.Pop() ?? "")14 px 3 px
T comes from the object, sizes, so it’s Int, and U comes from the lambda, so it’s
String. A generic Method has a copy for each set of type arguments, so it can’t be Abstract
or overridden.
A generic Struct
Structs take type parameters too. Pair<A, B> holds two values of any two types.
Import std.core
Struct Pair<A, B>
Field First: A
Field Second: B
End Struct
Local born = Pair("Ada", 1815)
Print("{born.First} was born in {born.Second}")
Local moved: Pair<String, Int> = Pair("Grace", 1906)
Print(moved.First)Ada was born in 1815 Grace
Pair("Ada", 1815) infers A = String and B = Int from its arguments. The second Local
writes its type, Pair<String, Int>, and the compiler checks the value against it.
A generic Interface
An Interface’s type parameters are types in its members. A Class implements one instance of
it, like Source<Int>, and its members then have Int where the Interface has T.
Import std.core
Interface Source<T>
Method Take(): T
Method TakeTwo(): (T, T) => (Take(), Take())
End Interface
Class Counter Implements Source<Int>
Field next: Int
Method Take(): Int
next += 1
Return next
End Method
End Class
Local counter = Counter()
Print(counter.TakeTwo())
Local source: Source<Int> = counter
Print(source.Take())(1, 2) 3
TakeTwo is a default, written in the Interface, so Counter gets it without writing it, and
for Source<Int> it gives an (Int, Int). A type implements at most one instance of each
generic Interface, so a Class can’t be both a Source<Int> and a Source<String>.
What you learned
- Classes, Structs, and Interfaces take type parameters, and each set of type arguments makes an instance, a type of its own.
- A generic type is always written with its type arguments, like
Stack<Int>, though making one can infer them fromNew’s arguments. - A Method can have type parameters of its own, after its type’s.
- A generic Interface’s instance, like
Source<Int>, is an Interface like any other.
The rules are in the language reference.
Report an issue with this page or with FutureBlitz on GitHub.