10 Generic Programming


10. Generic Programming

10.1 Type Parameters

Type parameters allow functions, structs, and traits to work with multiple types.

Generic Syntax: <T> where T is a type parameter

Examples:

// Generic struct
(T T --) { x: y: } ::Point<T> struct

// Generic union
(T --) { Some(T) None } ::Option<T> union

// Generic trait
{
    (Self T -- Self) append:
} ::Container<T> trait

Type Parameter Constraints:

When operations are performed on type parameters, they must be constrained by traits:

// Unconstrained - no operations on T
(T -- T) { } ::identity fn

// Constrained - T must support multiplication
(T:Multiplyable -- T) { dup * } ::square fn

Multiple Type Parameters:

(T E --) { Ok(T) Err(E) } ::Result<T E> union

10.2 Generic Functions

Generic functions work with multiple types by using trait constraints in their type tuples.

Syntax: (trait_constraints -- outputs) { body } ::name fn

Examples:

// Generic identity - works with any type (no operations, no constraint needed)
(T -- T) { } ::identity fn

// Requires Addable trait
(Addable Addable -- Addable) {
    +
} ::add_values fn

// Requires Number trait
(Number -- Number) {
    dup 0 > { } { 0 swap - } if
} ::abs fn

// Multiple constraints
(Number Number -- Number) {
    dup * swap dup * +  // a² + b²
} ::pythagorean fn

Type Inference: The compiler infers the actual type from usage:

5 identity          // T inferred as i64
"hello" identity    // T inferred as String
3 4 add_values      // Addable inferred as i64

10.3 Generic Data Structures

Structs and unions can be generic over type parameters:

Generic Structs:

// Point is generic over coordinate type
(T T --) { x: y: } ::Point<T> struct

// Use with different types
3.0 4.0 Point       // Point<f64>
3 4 Point           // Point<i64>

// Multiple type parameters
(T U --) { first: second: } ::Pair<T U> struct
5 "hello" Pair      // Pair<i64 String>

Generic Unions:

// Option is generic over contained type
(T --) { Some(T) None } ::Option<T> union

42 Option::Some     // Option<i64>::Some
"text" Option::Some // Option<String>::Some
Option::None        // Option<T>::None (T inferred from context)

// Result with two type parameters
(T E --) { Ok(T) Err(E) } ::Result<T E> union

Nested Generics:

// Array of Options
[Option::Some Option::None]           // Array of Option<T>

// Option of array
[1 2 3] Option::Some                  // Option<Array<i64>>

10.4 Generic Traits

Traits can be generic, allowing them to describe behavior parameterized over types.

Generic Trait Syntax:

// Container generic over element type
{
    (Self T -- Self) append:
    (Self -- T) pop:
} ::Container<T> trait

// Map generic over key and value types
{
    (Self K -- V) get:
    (Self K V -- Self) insert:
} ::Map<K V> trait

Generic Trait Inheritance:

When inheriting from generic traits, you must either:

  1. Make the inheriting trait similarly generic
  2. Specify concrete types for the generic parameters
// Inheriting trait is also generic
[ ::Container<T> ] ::Stack<T> inher
{
    (Self -- T) peek:
} ::Stack<T> trait

// Inheriting trait specifies concrete type
[ ::Container<i32> ] ::IntStack inher
{
    (Self -- i32) peek:
} ::IntStack trait

// Multiple generic inheritance
[ ::Selectable<T> ::Sized ::Sliceable ] ::ArrayOf<T> inher
{ } ::ArrayOf<T> trait

10.5 Type Parameter Enforcement

Current Behavior: Type parameters are currently suggestions when parsing code blocks. The compiler does not yet enforce that type parameters actually constrain how operators and functions act at parse time.

Example:

// Currently no error even without Multiplyable constraint
(T -- T) { dup * } ::square fn

// Should require constraint
(Multiplyable -- Multiplyable) { dup * } ::square fn

Future Enhancement: See Appendix F for planned type parameter enforcement at parse time.