Generics, 4 of 4
Inference and defaults
How the compiler works out type arguments, when you write some of them, and how generic code makes a value of T from nothing.
What you’ll build
Most generic calls don’t write their type arguments, because the compiler works them out. In
this tutorial you see each place it takes them from, write only the first of a call’s type
arguments, and use Defaultable to make values of a T that nothing passed in.
From the arguments
The compiler matches each argument’s type against the parameter’s. An Int[] passed for a T[]
gives T = Int. An argument whose type implements an instance of a generic Interface gives that
instance’s type arguments, so a Set<Int> passed for an Iterable<T> gives T = Int, and so
does an array.
Import std.core
Import std.collections
Function Total<T: Numeric>(values: Iterable<T>): T
Local total = T.Zero()
For v In values
total += v
End For
Return total
End Function
Local primes = Set<Int>()
primes.Add(2)
primes.Add(3)
primes.Add(5)
Print(Total(primes))
Print(Total([1.5, 2.5]))10 4
From literals
Number literals passed for the same type parameter agree on one type. Two whole numbers give
Int, and a number with a decimal point among them makes it Float.
Import std.core
Function Max<T: Comparable>(a: T, b: T): T => a > b ? a : b
Print(Max(1, 2))
Print(Max(1, 2.5))2 2.5
From lambdas and the expected type
A lambda’s parameters get their types once the type arguments they need are known, and its result then gives the rest. And when a call has no arguments to go by, the type the result is assigned to gives them.
Import std.core
Import std.collections
Function Transform<T, U>(items: T[], change: Func(T): U): U[]
Local results: U[] = []
For item In items
results.Add(change(item))
End For
Return results
End Function
Print(Transform([1, 2, 3], Function(n) => n * n))
Local pending: Stack<String> = Stack()
pending.Push("save")
Print(pending.Count)[1, 4, 9] 1
In Transform, T comes from the array, so n is an Int, and U from what the lambda gives,
n * n. std.collections’ Stack() takes T = String from the Local’s type.
Writing the first type arguments
A call can write its first type arguments and leave the rest to inference. Convert<T, A> takes
a value of any type and a function that changes it into a T. Writing Convert<String> gives
T, and A still comes from the argument.
Import std.core
Function Convert<T, A>(value: A, change: Func(A): T): T => change(value)
Print(Convert<String>(42, Function(n) => "#{n}"))
Print(Convert<Bool>(3, Function(n) => n > 2))#42 True
What you write is taken as given: Convert<Int>(42, Function(n) => "#{n}") is an error, since
the lambda gives a String where an Int is wanted.
When the compiler can’t tell a type argument, the call is an error that names the type parameter
and asks you to write the type arguments, as First([]) did in
Generic functions.
A value from nothing
A T with no constraint has no zero. Generic code that needs a value to start from, without one
being passed in, constrains T with Defaultable, whose T.DefaultValue() gives the type’s
default: 0 for numbers, False for Bools, "" for Strings, U+0000 for Chars, and an Enum’s
first member, or no flags for a @Flags Enum.
Import std.core
Function Filled<T: Defaultable>(count: Int): T[]
Local items: T[] = []
For i = 1 To count
items.Add(T.DefaultValue())
End For
Return items
End Function
Print(Filled<Int>(3))
Print(Filled<Bool>(2))
Local names: String[] = Filled(2)
Print(names.Count, names[0] == "")[0, 0, 0] [False, False] 2 True
Filled takes no argument of type T, so a call writes T, or assigns its result to a variable
whose type gives it, as names does.
What you learned
- Type arguments come from the arguments’ types, from literals, from lambdas, and from the type a result is assigned to.
- A call can write its first type arguments, as
Convert<String>(...), and the compiler infers the rest. - When a type argument can’t be told, the compiler asks you to write it.
<T: Defaultable>gives generic codeT.DefaultValue(), a value ofTto start from.
The rules are in the language reference.
Report an issue with this page or with FutureBlitz on GitHub.