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.

Last updated 9 min read Edit on GitHub

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.

AntrilRun
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));
}
Terminal
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; void is 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) (with plain), and nameof(T), which gives the argument's name (Float64 for f64).

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.

Antril
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.

AntrilRun
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]));
}
Terminal
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:

Antril
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.

Antril
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
AntrilRun
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'));
}
Terminal
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 loop over another pack is AN1422; from on 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 a static loop; use typeof(E).Name.

Was this page helpful?

No address is stored; your user name is, if you are signed in.

Esc