Generics
Generic types and functions in Antril - type parameters and arguments, inference, every kind of constraint, static members of generic types, number generics with INumber, and variadic packs with static loop.
On this page
Types, functions, methods and operators can take type parameters, written in <…> after the name. Code that uses them is compiled for each type argument it is used with, and runs as fast as code written for that type.
import System;
public class Box<T>
{
T item;
public:
new(T item) { this.item = item; }
Get() : T => item;
Map<U>(func<T> : U f) : Box<U> => new Box<U>(f(item));
}
public interface IHas<T>
{
Value() : T;
}
public class Pair<K, V> : IHas<V>
{
K key;
V val;
public:
new(K key, V val) { this.key = key; this.val = val; }
Value() : V => val;
}
First<T>(List<T> items) : T => items[0];
Main()
{
Box<i32> box = new Box<i32>(1);
Box<str> text = box.Map(x => "#" + x);
Console.WriteLine(text.Get());
IHas<f64> pair = new Pair<str, f64>("pi", 3.14);
Console.WriteLine(pair.Value());
List<str> names = new List<str>();
names.Add("Ana");
Console.WriteLine(First(names) + " " + First<str>(names));
}
larva run
#1
3.14
Ana Ana
- A generic type is named with its arguments:
Box<i32>,Dictionary<str, List<i32>>. A wrong number of arguments is AN1012;voidis not a type argument (AN1041). - A generic function's arguments are usually inferred from the call's arguments (and, for a lambda's result, from its body). They can be written:
First<str>(names). When they cannot be inferred, the call is AN2093. - A generic method can have its own parameters besides its type's:
Map<U>above. - A nested type of a generic type sees the outer type's parameters.
- A type parameter can be used as a type anywhere in its declaration: fields, parameters, locals,
new T(…)(with a constructor constraint),typeof(T),sizeof(T),default(T)(withplain), andnameof(T), which gives the argument's name (Float64forf64).
Constraints
A constraint after : says what a type argument must be or have. Several constraints are separated by commas; after a comma, only constraint forms continue the list, and a plain name starts the next parameter.
Sort<T : IComparable<T>>(List<T> items) { … }
Make<T : Shape, new(str)>(str name) : T => new T(name);
Mix<K : IHashable<K>, V : copy>(K key, V value) { … }
| Constraint | The type argument must… |
|---|---|
T : IShape, T : IComparable<T> |
implement the interface. |
T : Shape |
be the class or derive from it. |
T : number |
be a number: System.INumber<T>, which gives + - * / % == < != >, T.Zero, T.One, T.FromF64(x), T.ToF64(x). |
T : snumber |
be a signed number: System.ISignedNumber<T>, which adds -x and T.NegativeOne. |
T : [u8, u16, u32] |
be one of the listed types. |
T : int, T : uint, T : integer, T : float, T : numeric |
be a primitive of that group: signed integers, unsigned integers, all integers, floats, or every number type. |
T : value |
be a primitive or a value type. |
T : class |
be an object type (a class, str, an array…). |
T : managed |
the same as class. |
T : plain |
be a type whose zero is a valid value (no objects inside), so default(T) works. |
T : copy |
be copyable: anything but an array. |
T : unmanaged |
hold no objects: numbers, bool, enums, pointers, and values made of them. |
T : fixedsize |
have a size known when compiling. |
T : fixed(16) |
be a type of exactly 16 bytes (AN0028 for a size that is not positive). |
T : new(), T : new(i32, str) |
have a constructor with those parameters, so new T(…) works (AN2125, AN2127). |
T : op +(T, T) : T |
have that operator. left op/right op require a one-operand operator on that side. |
T : (i64)T |
convert to i64 with a cast (AN2124). |
T : enum, T : bitflag enum |
be an enum, or a bitflag enum (which also gives & | ^ ~). |
T : exception, T : attribute |
be an exception or an attribute class. A T : exception value can be thrown. |
A type argument that does not meet a constraint is error AN2094 (an interface or class), AN2101 (a list), AN2102 (a word), AN2124 (a conversion) or AN2126 (a size). A constraint that names a type which is not an interface or class is AN1040.
import System;
public class Thing
{
public:
i32 Id = 7;
}
Twice<T : op +(T, T) : T>(T x) : T => x + x;
Make<T : new()>() : T => new T();
Bits<T : int>(T x) : i32 => (i32)sizeof(T) * 8;
Doubled<T : [u8, u16]>(T x) : T => x + x;
Zero<T : plain>() : T => default(T);
Total<T : number>(T[] values) : T
{
T sum = T.Zero;
loop (T v in values)
sum = sum + v;
return sum;
}
Main()
{
Console.WriteLine(Twice(21) + " " + Twice(1.25));
Console.WriteLine(Make<Thing>().Id);
Console.WriteLine(Bits<i16>(1) + " " + Doubled<u8>(200) + " " + Zero<f64>());
Console.WriteLine(Total([1, 2, 3]) + " " + Total([1.5, 2.25]));
}
larva run
42 2.5
7
16 144 0
6 3.75
Doubled<u8>(200) is 144: the sum is computed in u8, which wraps.
Members through a type parameter
A constraint by an interface makes the interface's members available on values of T, and its static members on T itself: T.Zero comes from INumber<T>. An interface declares a static requirement with base get or a bodiless static method, which each implementing type provides:
public interface IDefaulted<TSelf>
{
static TSelf Fallback { base get; }
}
A static member a type parameter's constraints do not declare is AN2037 (a field or property) or AN2092 (a method).
Numbers
INumber<T> and ISignedNumber<T> are implemented by every primitive number type, and can be implemented by a program's own value: it declares the operators and Zero, One, FromF64 and ToF64 as static members. Math is written with these constraints, so Math.Abs, Math.Max, Math.Sqrt, Math.Pow and the rest work on any number type, including such a value. A parameter constrained by numeric or a list of number types also meets number.
Static members of generic types
Each instance of a generic type has its own static fields: Box<i32> and Box<str> do not share them. A static member is reached through an instance of the type: Box<i32>.Made(). Naming it without type arguments is AN2091.
public class Box<T>
{
static i32 made = 0;
public:
new() { made = made + 1; }
static Made() : i32 => made;
}
new Box<i32>();
new Box<i32>();
new Box<str>();
Box<i32>.Made(); // 2
Box<str>.Made(); // 1
A static field of a generic type starts with a constant value (AN1022), and a generic type has no static constructor (AN1050).
Variadic generics
The last type parameter can be a pack, T...: it stands for any number of types. A pack can have constraints (TNums... : number), and fixed parameters can come before it (Keyed<K, TRest...>). Only the last parameter is a pack (AN1420).
| Inside a variadic declaration | Means |
|---|---|
T values (a parameter or field of the pack's type) |
one parameter or field per type of the pack |
static loop (E in T) statement |
the statement repeated for each type, with E that type and the pack's names meaning the element's |
static loop (E in T) { members } |
members declared once per type, numbered from 0 |
static loop (E in T from 1) { E Item; } |
the same, numbered from 1: Item1, Item2… |
Other<T> |
the pack as type arguments, expanded |
import System;
Describe<T...>(T values) : str
{
str text = "";
static loop (E in T)
text = text + typeof(E).Name + " ";
return text;
}
Count<T...>(T values) : i32
{
i32 n = 0;
static loop (E in T)
n++;
return n;
}
Main()
{
Console.WriteLine(Describe(1, "two", 3.5));
Console.WriteLine(Count(true, 'x'));
}
larva run
Int32 String Float64
2
The pack is expanded once for each number of types the program uses, before the program is checked, so each expansion is an ordinary generic declaration. System.ValueTuple and System.Tuple use a pack for tuples of more than 16 elements.
- The pack used as a single type is AN1421; a
static loopover another pack is AN1422;fromon a statement loop is AN0036. - A compiled library keeps the text of its variadic templates, and each program that uses it expands them for the sizes it needs.
Warning
Not yet available:
- default type arguments (
class Box<T = u16>). nameof(E)for the type of astatic loop; usetypeof(E).Name.
Edit this page on GitHub Report an issue