Generics (Templates)
XXML supports generic programming through templates, allowing you to write type-safe, reusable code that works with multiple types.
Template Classes
Template parameters are declared inline with the class name using angle brackets:
box.xxml
[ Class <Box> <T Constrains None> Final Extends None
[ Public <>
Property <value> Types T^;
Constructor Parameters (Parameter <v> Types T^) ->
{
Set value = v;
}
Method <get> Returns T^ Parameters () Do
{
Return value;
}
]
]Multiple Type Parameters
Classes can have multiple type parameters separated by commas:
xxml
[ Class <Pair> <First Constrains None, Second Constrains None> Final Extends None
[ Public <>
Property <first> Types First^;
Property <second> Types Second^;
Constructor Parameters (
Parameter <f> Types First^,
Parameter <s> Types Second^
) ->
{
Set first = f;
Set second = s;
}
]
]Template Instantiation
XXML uses two syntaxes for template instantiation:
<Type>For type declarations
Box<Integer>^@TypeFor constructor calls
Box@Integer::Constructor()xxml
// Type declaration uses angle brackets
Instantiate Box<Integer>^ As <intBox> =
// Constructor uses @ syntax
Box@Integer::Constructor(Integer::Constructor(42));
Instantiate Pair<String, Integer>^ As <entry> =
Pair@String, Integer::Constructor(
String::Constructor("age"),
Integer::Constructor(25)
);Note
The
@ syntax for constructors helps disambiguate generic type instantiation from comparison operators in complex expressions.Template Constraints
Constraints ensure that type parameters have required methods or properties. They provide compile-time guarantees about what operations are available on generic types.
Defining a Constraint
constraints.xxml
[ Constraint <Printable> <T Constrains None> (T a)
Require (F(String^)(toString)(*) On a)
]
[ Constraint <Comparable> <T Constrains None> (T a)
Require (F(Bool^)(lessThan)(T^) On a)
Require (F(Bool^)(equals)(T^) On a)
]Using Constraints
xxml
// Single constraint
[ Class <Printer> <T Constrains Printable> Final Extends None
[ Public <>
Method <print> Returns None Parameters (Parameter <value> Types T^) Do
{
Run Console::printLine(value.toString());
}
]
]
// Constraint with template arguments (two equivalent syntaxes)
[ Class <HashSet> <T Constrains Hashable<T>> Final Extends None
// ...
]
[ Class <HashSet> <T Constrains Hashable@T> Final Extends None
// ...
]Multiple Constraints
| Syntax | Semantics | Description |
|---|---|---|
(A, B) | AND | All constraints must be satisfied |
A | B | OR | At least one constraint must be satisfied |
xxml
// AND semantics - all constraints must be satisfied
[ Class <SortedMap> <K Constrains (Hashable<K>, Comparable@K), V Constrains None> Final Extends None
// K must be both Hashable AND Comparable
]
// OR semantics - at least one constraint must be satisfied
[ Class <FlexibleContainer> <T Constrains Printable | Comparable> Final Extends None
// T must be either Printable OR Comparable (or both)
]Method Templates
Methods can have their own type parameters independent of the class:
xxml
[ Class <Converter> Final Extends None
[ Public <>
Constructor = default;
Method <identity> Templates <T Constrains None> Returns T^ Parameters (
Parameter <value> Types T^
) Do
{
Return value;
}
Method <convert> Templates <From Constrains None, To Constrains None> Returns To^ Parameters (
Parameter <value> Types From^
) Do
{
// Conversion logic
}
]
]
// Calling template methods
Instantiate Converter^ As <c> = Converter::Constructor();
Instantiate Integer^ As <result> = c.identity<Integer>(Integer::Constructor(42));Lambda Templates
Lambdas can also be generic:
xxml
// Generic lambda
Instantiate __function^ As <identity> = [ Lambda [] Templates <T Constrains None> Returns T^ Parameters (
Parameter <x> Types T^
) {
Return x;
}];
// Calling with type argument
Instantiate Integer^ As <intResult> = identity<Integer>.call(Integer::Constructor(42));
Instantiate String^ As <strResult> = identity<String>.call(String::Constructor("Hello"));Tip
Generic lambdas are useful for creating reusable utility functions that work with any type, like
identity, compose, or pipe.Complete Example: Generic Stack
stack.xxml
1 [ Class <Stack> <T Constrains None> Final Extends None 2 [ Private <> 3 Property <items> Types Collections::List<T>^; 4 ] 5 6 [ Public <> 7 Constructor Parameters() -> 8 { 9 Set items = Collections::List@T::Constructor(); 10 } 11 12 Method <push> Returns None Parameters (Parameter <value> Types T^) Do 13 { 14 Run items.add(value); 15 } 16 17 Method <pop> Returns T^ Parameters () Do 18 { 19 Instantiate Integer^ As <lastIdx> = items.size().subtract(Integer::Constructor(1)); 20 Instantiate T^ As <value> = items.get(lastIdx); 21 Run items.removeAt(lastIdx); 22 Return value; 23 } 24 25 Method <isEmpty> Returns Bool^ Parameters () Do 26 { 27 Return items.size().equals(Integer::Constructor(0)); 28 } 29 ] 30 ] 31 32 [ Entrypoint 33 { 34 Instantiate Stack<String>^ As <stack> = Stack@String::Constructor(); 35 Run stack.push(String::Constructor("First")); 36 Run stack.push(String::Constructor("Second")); 37 38 Run Console::printLine(stack.pop()); // Prints: Second 39 Run Console::printLine(stack.pop()); // Prints: First 40 41 Exit(0); 42 } 43 ]
Best Practices
- Use constraints to document and enforce type requirements
- Prefer specific constraints over
Constrains Nonewhen possible - Use meaningful type parameter names like
Key,Value,Element - Consider ownership when designing generic containers
Next Steps
Learn about classes and lambdas to see how generics integrate with other XXML features.