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>^
@Type

For 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

SyntaxSemanticsDescription
(A, B)ANDAll constraints must be satisfied
A | BORAt 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 None when 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.