# Types Every value has a type, and understanding the type system is fundamental to mastering any language. Types aren't merely labels - they form a rich hierarchy that governs how values flow through your program, what operations are permitted, and how the compiler reasons about your code. The type system combines static verification with practical flexibility, catching errors at compile time while still allowing sophisticated patterns of code reuse and abstraction. At the top of this hierarchy sits `any`, the universal supertype from which all other types descend. At the bottom lies `false`, the empty type that contains no values at all (the uninhabited type). Between these extremes exists a carefully designed lattice of types, each with its own capabilities and constraints. ## Understanding Subtyping Subtyping is the foundation of the type hierarchy. When we say that type A is a subtype of type B, we mean that every value of type A can be used wherever a value of type B is expected. This relationship creates a natural ordering among types, from the most specific to the most general. Consider the relationship between `rational` and `int`. Every integer is a rational number, but not every rational is an integer. Therefore, `int` is a subtype of `rational`. This means you can pass an `int` to any function expecting a `rational`, but not vice versa: ```verse GetInt(X:int):void = Print("Integer: {X}") GetRat(X:rational):void = Print("Rational") MyRat:rational = 1/3 MyInt:int = -10 GetRat(MyInt) # OK -- int is a subtype of rational GetInt(MyRat) # Compile error - rational is not a subtype of int ``` The subtyping relationship extends to composite types in sophisticated ways. Arrays and tuples follow covariant subtyping rules for their elements. This means that `[]int` is a subtype of `[]rational`. Similarly, `tuple(int, int)` is a subtype of `tuple(rational, rational)`. This covariance allows collections of more specific types to be used where collections of more general types are expected. Maps exhibit more complex subtyping behavior. A map type `[K1]V1` is a subtype of `[K2]V2` when `K2` is a subtype of `K1` (contravariant in keys) and `V1` is a subtype of `V2` (covariant in values). The contravariance in keys might seem counterintuitive at first, but it ensures type safety: if you can look up values using a more general key type, you must be able to handle more specific key types as well. Classes and interfaces introduce nominal subtyping through inheritance. When a class inherits from another class or implements an interface, it explicitly declares a subtyping relationship: ```verse vehicle := class: Speed:float = 0.0 car := class(vehicle): # car is a subtype of vehicle NumDoors:int = 4 sports_car := class(car): # sports_car is a subtype of car (and vehicle) Turbo:logic = true ``` This inheritance hierarchy means that a `sports_car` can be used anywhere a `car` or `vehicle` is expected, but not the reverse. The subtype inherits all fields and methods from its supertypes while potentially adding new ones or overriding existing ones. ## Numeric and String Conversions All type conversions must be explicit, a design choice that eliminates entire categories of bugs while making the programmer's intent clear. Converting between numeric types illustrates this principle clearly. To convert an integer to a float, you multiply by 1.0: ```verse MyI:int = 42 MyF:float = MyI * 1.0 # Explicit conversion to float ``` !!! note The strongest reason for disallowing implicit conversions is that they can cause code to break when new overloadings to a function are added. Imagine a call to function `f` that takes a float such as `f(1)`, if the integer argument was implicitly converted to a float and, in some future library release, an overload `f(:int)` was added, the call would silently invoke that new function and potentially change the result of the computation. The reverse conversion, from float to integer, requires choosing a rounding strategy: ```verse MyF:float = 3.7 Opt1:int = Floor[MyF] # Results in 3 Opt2:int = Ceil[MyF] # Results in 4 Opt3:int = Round[MyF] # Results in 4 (rounds to nearest) ``` These conversion functions are failable - they have the `` effect and will fail if passed non-finite values like `NaN` or `Inf`. The explicit failure forces you to handle edge cases: ```verse SafeConvert(Value:float):int = if: Value <> NaN Value <> Inf Result:= Floor[Value] then: Result else: 0 # Assuming that this is safe value ``` String conversions follow similar principles. The `ToString()` function converts various types to their string representations, while string interpolation provides a convenient syntax for embedding values in strings: ```verse Score:int = 1500 Msg:string = "Your score: {Score}" # Implicit ToString() call ``` ## Type `any` Type `any` is at the top of the type hierarchy it is the universal supertype that can hold a value of any type. Every type in Verse is a subtype of `any`, making it the most permissive type. It serves as an escape hatch when you genuinely need to work with values of unknown or varying types. Once a value is typed as `any`, you've effectively told the compiler "I don't know what this is," and the compiler responds by preventing most operations. This is by design—without knowing the actual type, the compiler cannot verify that operations are safe. You can explicitly coerce any value to `any` using function call syntax, `any(42)`. Verse automatically coerces values to `any` when their types would otherwise be incompatible. Understanding these rules help when working with heterogeneous data. Mixed-type arrays and maps automatically coerces to the most specific shared type, if no common type is found, the array coerces to `any`: ```verse MixedArray := array{42, "hello", true, 3.14} # []comparable MixedMap := map{0=>"zero", 1=>1, 2=>2.0} # [int]comparable ConfigMap := map{"count"=>42, "process"=>SomeFunction, "name"=>"Player"} # [string]any ``` Conditional expressions with disjoint branch types produce `any`: ```verse # If branches return different types GetValue(UseString:logic):any = if (UseString?): "text result" else: 42 ``` Logical OR with disjoint types coerces to `any`: ```verse # Returns either int or string OneOf(Flag:logic, I:int, S:string):any = (if (Flag?) then {option{I}} else {1=2}) or S ``` The `any` type has restrictions that reflect its role as a generic container: - You cannot use equality operators with `any` - Because `any` is not comparable, it cannot be used as a map key type - Because `any` is not castable, it is a sticky type. ### Generic Functions and Type Preservation Generic functions with `where t:type` constraints behave fundamentally differently from functions that accept `any`. Understanding this difference is crucial for writing type-safe code. When you pass a value to a function with parameter type `any`, the type information is lost: ```verse AcceptAny(X:any):any = X MyMap:[int]string = map{1 => "one"} Result := AcceptAny(MyMap) # Result has type any - type info lost ``` In contrast, generic functions preserve exact types: ```verse Identity(X:t where t:type):t = X MyMap:[int]string = map{1 => "one"} Result := Identity(MyMap) # Result has type [int]string - type preserved MyMap = Result # Succeeds - same type ``` This preservation extends to all container types, including arrays, maps, tuples, and structs. The generic type parameter captures the complete type, including: - Map key and value types - Array element types - Tuple component types - Struct field types **Practical implications:** Container types passed through generic functions maintain their structure completely: ```verse Identity(X:t where t:type):t = X # All key types are preserved IntMap:[int]int = map{1 => 2, 3 => 4} IntMap = Identity(IntMap) # Same type FloatMap:[float]string = map{1.0 => "one", 2.5 => "two"} FloatMap = Identity(FloatMap) # Same type TupleMap:[tuple(int, string)]int = map{(1, "a") => 100} TupleMap = Identity(TupleMap) # Same type # Iteration and equality work as expected for (Key->Value : IntMap): Identity(IntMap)[Key] = Value # All lookups succeed ``` This makes generic functions the preferred approach when you need to write reusable code that works with containers while maintaining type safety. ## Class and Interface Casting Verse provides two distinct casting mechanisms for classes and interfaces: fallible casts for runtime type checking, and infallible casts for compile-time verified conversions. Understanding when and how to use each is essential for working with inheritance hierarchies and polymorphic code. Fallible casts use square bracket syntax `TargetType[value]` to perform runtime type checks. These casts succeeds and return the casted value (`TargetType`), and failing if the value is not of a valid target type or a subtype: ```verse # Define a class hierarchy component := class: Name:string = "Component" physics_component := class(component): Velocity:float = 0.0 render_component := class(component): Material:string = "default" # Runtime type checking with fallible casts ProcessComponent(Comp:component):void = if (PhysicsComp := physics_component[Comp]): # Successfully cast - PhysicsComp is physics_component Print("Physics velocity: {PhysicsComp.Velocity}") else if (RenderComp := render_component[Comp]): # Different type - RenderComp is render_component Print("Render material: {RenderComp.Material}") else: # Neither type matched Print("Unknown component type") ``` The cast expression evaluates to `false` if the runtime type doesn't match, allowing you to use it directly in conditionals. The optional binding pattern `(Variable := Expression)` both performs the cast and binds the result to a variable when successful. For classes marked ``, fallible casts preserve identity—a successful cast returns the same instance, not a copy: ```verse entity := class: ID:int player := class(entity): Name:string # Create an instance P := player{ID := 1, Name := "Alice"} # Cast to base type if (E := entity[P]): E = P # True - same instance ``` Fallible casts work **only with class and interface types**. You cannot dynamically cast from or to primitive types, structs, arrays, or other value types: ```verse component := class{} # Error: cannot cast from primitives Comp := component[42] # int to class - not allowed Comp := component[3.14] # float to class - not allowed Comp := component["text"] # string to class - not allowed Comp := component[array{1,2}] # array to class - not allowed # Error: cannot cast to non-class types Value := int[component{}] # class to int - not allowed Value := logic[component{}] # class to logic - not allowed Value := (?int)[component{}] # class to option - not allowed ``` The restriction exists because fallible casts rely on runtime type information that only classes and interfaces maintain. Value types like integers and structs don't have runtime type tags. *Infallible* casts use parenthesis syntax `TargetType(value)` for conversions that the compiler can verify will always succeed. These casts require the source type to be a compile-time subtype of the target type: ```verse component := class: Name:string = "Component" physics_component := class(component): Velocity:float = 0.0 # Upcasting: always safe, always succeeds Base:physics_component = physics_component{Velocity := 10.0} BaseComp:component = component(Base) # upcast during expression # or AlsoBaseComp:component = Base # upcast during assignment ``` Any type can be infallibly cast to `void`, which discards the value: ```verse void(42) # Discard an integer void("result") # Discard a string void(component{}) # Discard an object ``` This implicitly happens when you call a function for its side effects and want to ignore its return value. ### Dynamic Type-Based Casting Types in Verse are first-class values, which means you can store types in variables and use them dynamically for casting. This enables powerful patterns for runtime polymorphism: ```verse # Type hierarchy component := class{} physics_component := class(component){} render_component := class(component){} # Store types as values ComponentType:castable_subtype(component) = physics_component # Cast using the stored type Test(Comp:component, ExpectedType:castable_subtype(component)):logic = if (Specific := ExpectedType[Comp]): true # Component matches expected type else: false # Use with different types P := physics_component{} Test(P, physics_component) # true Test(P, render_component) # false ``` This pattern is particularly powerful when the type to check isn't known until runtime: ```verse # Select type based on configuration GetComponentType(Config:string):castable_subtype(component) = if (Config = "physics"): physics_component else if (Config = "render"): render_component else: component # Use the dynamically selected type RequiredType := GetComponentType(LoadedConfig) for (Comp : Components): if (Specific := RequiredType[Comp]): # Process components of the required type ProcessSpecific(Specific) ``` This bridges compile-time type safety with runtime flexibility, allowing type decisions to be made based on program state while maintaining type correctness. ## Where Clauses Where clauses are the mechanism for constraining type parameters in generic code. They appear after type parameters and specify requirements that types must satisfy to be valid arguments. This creates a powerful system for writing generic code that is both flexible and type-safe. ```verse # Simple subtype constraint Process(Value:t where t:subtype(comparable)):void = if (Value = Value): # We know it supports equality Print("Value equals itself") ``` Using the same type in multiple constraints is not yet supported, when implemented, it will allow to write code such as: ```verse # Multiple constraints on the same type F(In:t where t:subtype(comparable), t:subtype(printable)):t = # Not supported Print("Processing: {In}") In ``` Where clauses become more powerful when working with multiple type parameters: ```verse # Independent constraints on different parameters Combine(A:t1, B:t2 where t1:type, t2:type):tuple(t1, t2) = (A, B) # Related constraints Convert(From:t1, Converter:type{_(:t1):t2} where t1:type, t2:type):t2 = Converter(From) ``` Where clauses can express sophisticated relationships between types: ```verse # Constraint that ensures compatible types for an operation Merge(Container1:[]t, Container2:[]t where t:subtype(comparable)):[]t = var Result:[]t = Container1 for (Element : Container2, not Contains[Result, Element]): set Result += array{Element} Result # Function type constraints ApplyTwice(F:type{_(:t):t}, Value:t where t:type):t = F(F(Value)) ``` Where clauses enable sophisticated generic programming patterns: ```verse MapFunction(F:type{_(:a):b}, Container:[]a where a:type, b:type):[]b = for (Element : Container): F(Element) ``` ## Refinement Types While `where` clauses constrain type parameters in generic code, **refinement types** use `where` to constrain the *values* a type can hold. This creates subtypes that only accept values satisfying specific conditions, enabling domain-specific constraints enforced by the type system. A natural question is: why fail on out-of-range values when you could just clamp? The answer is that clamping silently propagates wrong values, which is acceptable for some domains (UI opacity) but dangerous in others. In algorithms where exact values matter — bit manipulation, hashing, Unicode code point operations, coordinate system math — silently clamping an out-of-range value produces incorrect results that are extremely hard to track down. Refinement types make the constraint explicit and force the caller to handle violations, catching bugs at their source rather than letting them propagate. In practice, type aliases like `positive_int` or `zero_to_one_float` make refinement types convenient to reuse across a codebase without repeating the constraint expression each time. A refinement type defines a constrained subtype using value predicates: ```verse # Percentages: floats between 0.0 and 1.0 # percent := type{_X:float where 0.0 <= _X, _X <= 1.0} # Valid assignments Opacity:percent = 0.5 Alpha:percent = 1.0 # Invalid: out of range (runtime check fails) # BadPercent:percent = 1.5 # Fails at assignment ``` **Syntax structure:** ```verse TypeName := type{_Variable:BaseType where Constraint1, Constraint2, ...} ``` - `_Variable` is a placeholder for the value being constrained - `BaseType` is `int` or `float` - Constraints are comparison expressions using `<=`, `<`, `>=`, or `>` Integer refinements restrict int values to specific ranges: ```verse # Age between 0 and 120 age := type{_X:int where 0 <= _X, _X <= 120} ValidAge:age = 25 # InvalidAge:age = 150 # Fails constraint # Positive integers positive_int := type{_X:int where _X > 0} Count:positive_int = 42 # Zero:positive_int = 0 # Fails: not positive # Range with single bound small_int := type{_X:int where _X < 100} ``` Float refinements handle continuous ranges with IEEE 754 semantics: ```verse # Unit interval [0.0, 1.0] normalized := type{_X:float where 0.0 <= _X, _X <= 1.0} # Positive floats positive := type{_X:float where _X > 0.0} # Temperature in Celsius above absolute zero celsius := type{_X:float where _X >= -273.15} ``` Finite Floats (Excluding Infinity): ```verse # Finite values only (no ±Inf) finite := type{_X:float where -Inf < _X, _X < Inf} # Maximum and minimum finite IEEE 754 doubles MaxFinite:finite = 1.7976931348623157e+308 MinFinite:finite = -1.7976931348623157e+308 # Invalid: infinities excluded # Infinite:finite = Inf # Fails constraint ``` ### IEEE 754 Edge Cases **Negative and Positive Zero:** IEEE 754 distinguishes between `+0.0` and `-0.0`. In verse Zero is just Zero, with no distinction between positve or negative. When any expression evaluates to Zero, the sign is discarded: ```verse # Integer Zero (type{0}) Value1 := -0 Value2 := +0 Value1 = Value2 # Succeeds -0 = +0 # Succeeds # Float Zero (type{0.0}) Value3 := -0.0 Value4 := +0.0 Value3 = Value4 # Succeeds -0.0 = +0.0 # Succeeds ``` **Floating-Point Precision:** Constraints respect exact IEEE 754 representations: ```verse # Values strictly less than 0.1 small_float := type{_X:float where _X < 0.1} # Valid: largest float before 0.1 Tiny:small_float = 0.09999999999999999167332731531132594682276248931884765625 # Invalid: 0.1's actual representation is slightly above 0.1 # NotSmall:small_float = 0.1000000000000000055511151231257827021181583404541015625 ``` The decimal `0.1` cannot be represented exactly in binary floating-point, so the actual stored value is slightly above the mathematical 0.1. ### Constraint Expression Restrictions Refinement type constraints have strict limitations on what expressions are allowed: **Only Literal Values:** Constraints must use literal numbers, not variables or expressions: ```verse # Valid: literal float bounded := type{_X:float where _X < 100.0} # Invalid: cannot use variables Limit:float = 100.0 bad_type := type{_X:float where _X < Limit} # ERROR # Invalid: cannot use function calls GetMax():float = 100.0 bad_type := type{_X:float where _X < GetMax()} # ERROR # Invalid: cannot use qualified names Config := module{Max:float = 100.0} bad_type := type{_X:float where _X < (Config:)Max} # ERROR ``` This ensures constraints are statically known at compile time. **Float Literals Required for Float Types:** When constraining floats, bounds must be float literals (with decimal point): ```verse # Invalid: integer literal in float constraint # bad_float := type{_X:float where _X <= 142} # ERROR 3502 # Valid: float literal good_float := type{_X:float where _X <= 142.0} ``` **NaN Not Allowed:** Not a Number cannot appear in constraints: ```verse # Invalid: NaN in constraint # nan_type := type{_X:float where _X <= NaN} # ERROR 3502 # nan_type := type{_X:float where NaN <= _X} # ERROR 3502 # nan_type := type{_X:float where 0.0/0.0 <= _X} # ERROR 3502 ``` Since `NaN` comparisons are always false, such constraints would be meaningless. **Allowed Literal Forms:** - Float literals: `1.0`, `3.14`, `-2.5`, `1.7976931348623157e+308` - Integer literals: `0`, `42`, `-100` (for int refinements) - Special float values: `Inf`, `-Inf` ### Fallible Casts Refinement types are checked at assignment and through fallible casts: ```verse percent := type{_X:float where 0.0 <= _X, _X <= 1.0} # Direct assignment (compile-time known) Valid:percent = 0.5 # OK # Runtime check with fallible cast UserInput:float = GetInputFromUser() if (Value := percent[UserInput]): # UserInput was in [0.0, 1.0] ProcessPercent(Value) else: # Out of range ShowError() ``` The cast `percent[UserInput]` returns `percent` succeeding if the value satisfies the constraint, or failing otherwise. ### Examples Refinement types work as parameter and return types: ```verse finite := type{_X:float where -Inf < _X, _X < Inf} # Parameter with constraint Half(X:finite):float = X / 2.0 Half(100.0) # Returns 50.0 Half(1.0) # Returns 0.5 # Cannot pass infinity # Half(Inf) # ERROR 3509: Inf not in finite ``` **Coercion and Negation:** ```verse percent := type{_X:float where 0.0 <= _X, _X <= 1.0} negative_percent := type{_X:float where _X <= 0.0, _X >= -1.0} MakePercent():percent = 0.5 # Negation preserves constraint compatibility NegValue:negative_percent = -MakePercent() # -0.5 valid # Multiple negations NegValue2:negative_percent = ---0.7 # Triple negation = -0.7 ``` ### Overloading Restrictions Overlapping refinement types cannot be used for function overloading—they're ambiguous: ```verse percent := type{_X:float where 0.0 <= _X, _X <= 1.0} not_infinity := type{_X:float where Inf > _X} # ERROR 3532: Cannot distinguish - percent ⊂ not_infinity # F(X:percent):float = 0.0 # F(X:not_infinity):float = X # Calling F(0.5) would be ambiguous - which overload? ``` However, **disjoint** refinement types can overload: ```verse positive := type{_X:float where _X > 0.0} negative := type{_X:float where _X < 0.0} # Valid: ranges don't overlap (zero excluded from both) F(X:positive):float = X F(X:negative):float = X + 1.0 F(1.0) # Returns 1.0 (positive overload) F(-1.0) # Returns 0.0 (negative overload) # F(0.0) # Would fail - neither overload matches ``` ## Comparable and Equality The `comparable` type represents a special subset of types that support equality comparison. Not all types can be compared for equality - this is a deliberate design choice that prevents meaningless comparisons and ensures that equality has well-defined semantics. A type is comparable if its values can be meaningfully tested for equality. The basic scalar types are all comparable: `int`, `float`, `rational`, `logic`, `char`, and `char32`. Compound types are comparable if all their components are comparable. This means arrays of integers are comparable, tuples of floats and strings are comparable, and maps with comparable keys and values are comparable. The equality operators `=` and `<>` are defined in terms of the comparable type: ```verse operator'='(X:t, Y:t where t:subtype(comparable)):t operator'<>'(X:t, Y:t where t:subtype(comparable)):t ``` The signatures require that both operands be subtypes of comparable and the return type is the least upper bound of their types. ```verse 0 = 0 # Succeeds - both are int 0.0 = 0.0 # Succeeds - both are float 0 = 0.0 # Fails - there is no implicit conversion from int to float ``` Here is an example that highlights how the return type of `=` is computed: ```verse I:int=1 R:rational=1/3 X:rational= (I=R) # Compiles and fails at runtime I:int=1 S:string="hi" Y:comparable= (I=S) # Compiles and fails at runtime ``` In the case of variable `X`, its type can be either `rational` or `comparable`. For variable `Y`, the only common type between `int` and `string` is `comparable`. Classes require special handling for comparability. By default, class instances are not comparable because there's no universal way to define equality for user-defined types. However, you can make a class comparable using the `unique` specifier: ```verse entity := class: ID:int Name:string Player1 := entity{ID := 1, Name := "Alice"} Player2 := entity{ID := 1, Name := "Alice"} Player3 := Player1 Player1 = Player2 # Fails - different instances Player1 = Player3 # Succeeds - same instance ``` With the `unique` specifier, instances are only equal to themselves (identity equality), not to other instances with the same field values (structural equality). This provides a clear, predictable semantics for class equality. ### Comparable as a Generic Constraint The `comparable` type is commonly used as a constraint in generic functions to ensure operations like equality testing are available: ```verse Find(Items:[]t, Target:t where t:subtype(comparable)):int = Results := for (Index->Item:Items, Item = Target): Index Results[0] # Works with any comparable type Position := Find[array{"apple", "banana", "cherry"}, "banana"] # Succeeds and returns 1 ``` ### Array-Tuple Comparison A notable feature of Verse's equality system is that arrays and tuples of comparable elements can be compared with each other: ```verse # Arrays can equal tuples array{1, 2, 3} = (1, 2, 3) # Succeeds (4, 5, 6) = array{4, 5, 6} # Succeeds - bidirectional # Inequality also works array{1, 2, 3} <> (1, 2, 4) # Succeeds - different values ``` This comparison works structurally - the sequences must have the same length and corresponding elements must be equal. This feature allows functions expecting arrays to accept tuples, increasing flexibility. ### Overload Distinctness with Comparable You cannot create overloads where one parameter is a specific comparable type and another is the general `comparable` type, as this creates ambiguity: ```verse # Not allowed - ambiguous overloads F(X:int):void = {} F(X:comparable):void = {} # ERROR: int is already comparable # Not allowed with unique classes either unique_class := class{} G(X:unique_class):void = {} G(X:comparable):void = {} # ERROR: unique_class is comparable ``` However, you can overload with non-comparable types: ```verse # This is allowed regular_class := class{} # Not comparable H(X:regular_class):void = {} H(X:comparable):void = {} # OK: no ambiguity ``` ### Dynamic Comparable Values When working with heterogeneous collections, you may need to box comparable values into the `comparable` type explicitly. These boxed values maintain their equality semantics: ```verse AsComparable(X:comparable):comparable = X # Boxed values compare correctly with both boxed and unboxed array{AsComparable(1)} = array{1} # Succeeds array{AsComparable(1)} = array{AsComparable(1)} # Succeeds array{AsComparable(1)} <> array{2} # Succeeds # With direct upcasting: comparable(15) = comparable(15) # Succeeds comparable("Hello") = "Hello" # Succeeds ``` This allows you to create collections that mix different comparable types by boxing them all to `comparable`. ### Map Keys and Comparable Map keys must be comparable types. Most comparable types can be used as map keys, including: - All numeric types: `int`, `float`, `rational` - Character types: `char`, `char32` - Text: `string` - Enumerations - `` classes - Optionals of comparable types: `?t` where `t` is comparable - Arrays of comparable types: `[]t` where `t` is comparable - Tuples of comparable types - Maps with comparable keys and values: `[k]v` - Structs with comparable fields Note that while `float` can be used as a map key, floating-point special values have specific equality semantics (see [Map documentation](02_primitives.md#floats) for details on `NaN` and zero handling). There is currently no way to make a regular class comparable by writing a custom comparison method. Only the `` specifier enables class comparability through identity equality. ## Type Hierarchies The type system forms a lattice rather than a simple tree. This means types can have multiple supertypes, though multiple inheritance is currently limited to interfaces. Understanding these relationships helps you design flexible, reusable code. ### Understanding void Unlike `any`, which erases type information, `void` serves as a "discard" type indicating that a value's specific type doesn't matter. Functions with `void` return type can return any value, which is then discarded by the type system: ```verse LogEvent(Message:string):void = WriteToFile(Message) 42 # Returns int, but typed as void F():void = 1 # Valid - returns int, typed as void F() # Result is void ``` Despite being typed as `void`, these functions still produce their computed values—the values are simply not accessible through the type system. This ensures side effects and computations occur even when the return value is discarded: ```verse MakePair(X:string, Y:string):void = (X, Y) # Function computes the pair even though return type is void MakePair("hello", "world") # Still creates ("hello", "world") ``` Functions with `void` parameters accept any argument type: ```verse Discard(X:void):int = 42 Discard(0) # int → void Discard(1.5) # float → void Discard("test") # string → void ``` Class fields can be typed as `void`, accepting any initialization value: ```verse config := class: Setting:void = array{1, 2} # Default with array ``` In function types, `void` participates in variance: ```verse IntIdentity(X:int):int = X # Contravariant return: supertype in return position F:int->void = IntIdentity # int->int → int->void ✓ # void is supertype of int, so this works AcceptVoid(X:void):int = 19 # Contravariant parameter: supertype in parameter position G:int->int = AcceptVoid # void->int → int->int ✓ # Can use function accepting void where function accepting int expected ``` However, `void` in parameter position does NOT allow conversion the other way: ```verse IntFunction(X:int):int = X # F:void->int = IntFunction # ERROR # Cannot convert int parameter to void parameter in function type ``` **void vs false**: The `false` type is the empty/bottom type (uninhabited type) with no values. It's the opposite of `void`: - **`void`**: Universal supertype - all types are subtypes of void, contains all values - **`false`**: Bottom type - subtype of all types, contains zero values Between the universal supertypes (`any`, `void`) and the bottom type (`false`), types form natural groupings. The numeric types (`int`, `float`, `rational`) share common arithmetic operations but don't form a single hierarchy - they're siblings rather than ancestors and descendants. The container types (arrays, maps, tuples, options) each have their own subtyping rules based on their element types. Understanding variance is crucial for working with generic containers. Arrays and options are covariant in their element type - if A is a subtype of B, then `[]A` is a subtype of `[]B` and `?A` is a subtype of `?B`. This allows natural code like: ```verse ProcessNumbers(Nums:[]rational):void = for (N : Nums): Print("{RationalPrinter(N)}") IntArray:[]int = array{1, 2, 3} ProcessNumbers(IntArray) # Works due to covariance ``` Functions exhibit more complex variance. They're contravariant in their parameter types and covariant in their return types. A function type `(T1)->R1` is a subtype of `(T2)->R2` if T2 is a subtype of T1 (contravariance) and R1 is a subtype of R2 (covariance). This ensures that function subtyping preserves type safety: ```verse function_type1 := type{_(:any):int} function_type2 := type{_(:int):any} # function_type1 is a subtype of function_type2 # It accepts more general input (any vs int) - contravariance # And returns more specific output (int vs any) - covariance # Demonstrate: a function matching type1 can be used where type2 is expected ConcreteFunc(Input:any):int = 42 UseFunction(F:function_type2, Value:int):void = Result:any = F(Value) UseFunction(ConcreteFunc, 5) # Works: function_type1 <: function_type2 ``` ## Type Aliases Type aliases allow you to create alternative names for types, making complex type signatures more readable and maintainable. They're particularly valuable for function types, parametric types, and frequently-used type combinations. A type alias is created using simple assignment syntax at module scope: ```verse # At module scope entity:=struct{} # Simple type aliases coordinate := tuple(float, float, float) entity_map := [string]entity player_id := int # Function type aliases update_handler := type{_(:float):void} validator := int -> logic transformer := type{_(:string):int} ``` Type aliases are compile-time only - they create no runtime overhead and are purely for programmer convenience and code clarity. **Type aliases are alternative names, not new types.** They don't create distinct types like `newtype` in some languages. Values of the alias and the original type are completely interchangeable: ```verse # Assume # player_id := int # game_id := int ProcessPlayer(ID:player_id):void = {} ProcessGame(ID:game_id):void = {} PID:player_id = 42 GID:game_id = 42 # These all work - aliases are just names ProcessPlayer(PID) # OK ProcessPlayer(GID) # OK - game_id is also int ProcessPlayer(42) # OK - int literal works too ProcessGame(PID) # OK - player_id is also int ``` Type aliases can have access specifiers that control their visibility across modules: ```verse # Public alias - accessible from other modules PublicAlias := int # Internal alias - only accessible within defining module InternalAlias := string # Protected/private also work ProtectedAlias := float # only in classes and interfaces ``` **Type aliases cannot be more public than the types they alias:** ```verse private_class := class{} # No specifier = internal scope # INVALID: Public alias to internal type # PublicToPrivate := private_class # VALID: Same or less visibility InternalToInternal := private_class InternalAlias := private_class # Defaults to internal ``` ### Requirement - **Type aliases can only be defined at module scope.** They cannot be defined inside classes, functions, or any nested scope. This restriction ensures type aliases have consistent visibility and prevents scope-dependent type interpretations. - Type aliases must be defined **before** they are used. Forward references are not allowed. - Type aliases are not first-class values and cannot be used as such. ## Metatypes Verse provides advanced type constructors that allow you to work with types as values, enabling powerful patterns for runtime polymorphism and generic instantiation. These metatypes—`subtype`, `concrete_subtype`, and `castable_subtype`—bridge the gap between compile-time type safety and runtime flexibility. ### subtype The `subtype(T)` type constructor represents runtime type values that are subtypes of `T`. Unlike `concrete_subtype` and `castable_subtype`, which are specialized for classes and interfaces, `subtype(T)` works with **any type** in Verse, including primitives, enums, collections, and function types. ```verse animal := class {} dog := class(animal) {} # Example of using subtype as a field type var AnimalType:subtype(animal) # Can hold animal, dog, or any subtype of animal # Assign class types F0():void = set AnimalType = animal F1():void = set AnimalType = dog # dog is subtype of animal # Accept as parameter F3(ClassArg:subtype(animal)):void = set AnimalType = ClassArg ``` The key capability of `subtype(T)` is holding type values at runtime while maintaining type safety through the subtype relationship. Unlike the other metatypes, `subtype(T)` accepts any type as its parameter: ```verse # Primitives IntType:subtype(int) = int LogicType:subtype(logic) = logic FloatType:subtype(float) = float # Enums EnumType:subtype(my_enum) = my_enum # Classes and interfaces ClassType:subtype(my_class) = my_class InterfaceType:subtype(my_interface) = my_interface # Note: Collection types and function types in subtype() currently have issues: # ArrayType:subtype([]int) = []int # Error: cannot be defined # OptionType:subtype(?string) = ?string # Error: cannot be defined # FuncType:subtype(type{_():void}) = type{_():void} # Error: cannot be defined ``` This universality makes `subtype(T)` the most flexible of the metatypes, suitable for any scenario where you need to store or pass type values. **Subtyping Relationship:** The `subtype` constructor preserves the subtyping relationship: `subtype(T) <: subtype(U)` if and only if `T <: U`. This means you can assign a more specific subtype to a less specific one: ```verse super_class := class{} sub_class := class(super_class) {} # Covariance: sub_class <: super_class SubtypeVar:subtype(sub_class) = sub_class SupertypeVar:subtype(super_class) = SubtypeVar # Valid # Reverse fails - super_class is not <: sub_class # SubtypeVar2:subtype(sub_class) = super_class ``` This also applies to interfaces: ```verse super_interface := interface{} sub_interface := interface(super_interface) {} class_impl := class(sub_interface) {} # Covariance through interface hierarchy SpecificType:subtype(sub_interface) = class_impl GeneralType:subtype(super_interface) = SpecificType # Valid ``` **Using with Interfaces:** When working with interfaces, `subtype(T)` can hold any class that implements the interface: ```verse printable := interface: PrintIt():void document := class(printable): PrintIt():void = {} # Can hold any type implementing printable DocumentType:subtype(printable) = document ``` **Relationship to `type`:** Both `subtype(T)` and `castable_subtype(T)` are subtypes of `type`, meaning they can be used where `type` is expected: ```verse c := class: f(C:subtype(c)):type = return(C) # Valid: subtype(c) <: type t := interface {} g(x:subtype(t)):type = x # Valid: subtype(t) <: type ``` **Restrictions:** While `subtype(T)` is flexible, it has important restrictions: 1. **Cannot use as value:** `subtype(T)` is a type constructor, not a value. You cannot use `subtype(T)` itself as a value. 2. **Exactly one argument:** `subtype` requires exactly one type argument. 3. **Cannot use with attributes:** `subtype` cannot be used with classes that inherit from `attribute`. ### concrete_subtype The `concrete_subtype(t)` type constructor creates a type that represents concrete (instantiable) subclasses of `t`. A concrete class is one that can be instantiated directly—it has the `` specifier and provides default values for all fields: ```verse # Abstract base class entity := class: Name:string GetDescription():string # Concrete implementations player := class(entity): Name:string = "Player" GetDescription():string = "A player character" enemy := class(entity): Name:string = "Enemy" GetDescription():string = "An enemy creature" # Class that stores a type and can instantiate it spawner := class: EntityType:concrete_subtype(entity) Spawn():entity = # Instantiate using the stored type EntityType{} # Use it # NewEntity := spawner{EntityType := player}.Spawn() ``` The key feature of `concrete_subtype` is that it ensures the stored type can be instantiated. Without this constraint, you couldn't safely call `EntityType{}` because abstract classes cannot be instantiated. #### Requirements A type can be used with `concrete_subtype` only if it's a class or interface type. Additionally, the actual type value assigned must be a concrete class—one marked with `` and having all fields with defaults: ```verse # Valid: concrete class with all defaults config := class: MaxPlayers:int = 8 TimeLimit:float = 300.0 ConfigType:concrete_subtype(config) = config # Valid # Invalid: abstract class cannot be concrete_subtype abstract_base := class: Value:int # This would be an error: # BaseType:concrete_subtype(abstract_base) = abstract_base ``` When you have a `concrete_subtype`, you can instantiate it with the empty archetype `{}`, but you cannot provide field initializers—the concrete class must provide all necessary defaults: ```verse entity_base := class: Health:int warrior := class(entity_base): Health:int = 100 EntityType:concrete_subtype(entity_base) = warrior # Valid: empty archetype uses defaults # Instance := EntityType{} # Invalid: cannot initialize fields through metatype # Instance := EntityType{Health := 150} ``` ### castable_subtype The `castable_subtype(t)` type constructor represents types that are subtypes of `t` and marked with the `` specifier. This enables runtime type queries and dynamic casting, which is essential for component systems and polymorphic hierarchies: ```verse # Castable base class component := class: Owner:entity # Castable subtypes physics_component := class(component): Velocity:vector3 render_component := class(component): Material:string # Function accepting castable subtype ProcessComponent(CompType:castable_subtype(component), Comp:component):void = # Can use CompType to perform type-safe casts if (Specific := CompType[Comp]): # Comp is now known to be of type CompType ``` ### final_super and Type Queries The `castable_subtype` works with the `` specifier and `GetCastableFinalSuperClass` function to enable sophisticated runtime type queries. This combination provides a powerful mechanism for component systems and polymorphic architectures. The `` specifier marks classes as stable anchor points in inheritance hierarchies. These "final super classes" act as canonical representatives for families of related types: ```verse component := class: Owner:entity # Stable anchor for the physics component family physics_component := class(component): Velocity:vector3 # Specific implementations inherit from the anchor rigid_body := class(physics_component): Mass:float soft_body := class(physics_component): SpringConstant:float ``` By marking `physics_component` as ``, you declare it as the canonical representative for all physics-related components. Even though `rigid_body` and `soft_body` are distinct types, they both belong to the "physics_component family" anchored at `physics_component`. #### GetCastableFinalSuperClass The `GetCastableFinalSuperClass` function queries the type hierarchy to find the `` class between a base type and a derived type. Two variants exist: ```verse # Takes an instance GetCastableFinalSuperClass(BaseType, instance):castable_subtype(BaseType) # Takes a type GetCastableFinalSuperClassFromType(BaseType, Type):castable_subtype(BaseType) ``` Both return a `castable_subtype` representing the least specific `` class that: 1. Directly inherits from the specified base type 2. Is in the inheritance chain of the instance/type The function fails if no appropriate `` class exists. Consider this hierarchy: ```verse component := class: ID:int # Direct final_super subclass of component physics_component := class(component): Velocity:vector3 # Descendants of physics_component rigid_body := class(physics_component): Mass:float character_body := class(rigid_body): Health:int ``` Query results: ```verse # All instances in the physics_component family return physics_component Body := character_body{ID:=1, Velocity:=vector3{}, Mass:=10.0, Health:=100} if (Family := GetCastableFinalSuperClass[component, Body]): # Family = physics_component (the final_super anchor) # Even though Body is character_body, the family anchor is physics_component ``` The function "walks up" the inheritance chain from `character_body` → `rigid_body` → `physics_component` and stops at `physics_component` because: 1. It has `` 2. It directly inherits from the queried base (`component`) **When Queries Succeed and Fail?** **Succeeds when:** - A `` class directly inherits from the base type - The instance/type inherits from that `` class ```verse base := class: Value:int anchor := class(base): Extra:string derived := class(anchor): More:string # Valid: anchor is final_super of base, derived inherits from anchor GetCastableFinalSuperClass[base, derived{}] # Returns anchor GetCastableFinalSuperClass[base, anchor{}] # Returns anchor ``` **Fails when:** - No `` class exists between base and instance - The queried type itself is the instance type (cannot query from same level) - Instance is not a subtype of the base #### Multiple Final Supers You can have multiple `` classes at different levels. The function returns the one directly inheriting from the queried base: ```verse base := class: ID:int first_anchor := class(base): Category:string second_anchor := class(first_anchor): Subcategory:string leaf := class(second_anchor): Specific:string # Query from base returns first_anchor GetCastableFinalSuperClass[base, leaf{}] # Returns first_anchor # Query from first_anchor returns second_anchor GetCastableFinalSuperClass[first_anchor, leaf{}] # Returns second_anchor ``` This layered approach allows hierarchical categorization where different levels represent different granularities of type families. #### GetCastableFinalSuperClassFromType The type-based variant works identically but takes a type instead of instance: ```verse # Same behavior, different syntax TypeFamily := GetCastableFinalSuperClassFromType[component, rigid_body] InstanceFamily := GetCastableFinalSuperClass[component, rigid_body{}] # Both return the same castable_subtype ``` This is useful when working with type values directly rather than instances. ### castable_concrete_subtype The `castable_concrete_subtype(t)` type constructor combines the requirements of both `castable_subtype` and `concrete_subtype`, representing types that are: - Subtypes of `t` - Marked with `` (enabling runtime type queries) - Marked with `` (enabling instantiation) This is useful when you need to ensure that type parameters are both castable and concrete: ```verse entity := class{} component := class: Owner:entity physics_component := class(component): Velocity:float = 0.0 # Function that requires both and CreateAndCast(CompType:castable_concrete_subtype(component)):component = # Can instantiate because CompType is Instance := CompType{} # Can cast because CompType is if (Specific := CompType[Instance]): Specific else: Instance ``` #> ### classifiable_subset Building on the concept of runtime type queries introduced by `castable_subtype`, Verse provides `classifiable_subset`—a sophisticated mechanism for maintaining sets of runtime types. Where `castable_subtype` represents a single type value, `classifiable_subset` represents a collection of types, tracking which classes are present in a system and supporting queries based on type hierarchies. This feature is particularly valuable for component-based architectures, where you need to track which component types an entity possesses, query for specific capabilities, or filter operations based on type compatibility. Rather than maintaining separate boolean flags or type tags, `classifiable_subset` provides a type-safe, hierarchy-aware registry of runtime types. Three related types work together to provide both immutable and mutable type sets: **`classifiable_subset(t)`** represents an immutable set of runtime types, where `t` must be a `` base type. Once created, the set cannot be modified, making it suitable for configuration, capability descriptions, or any scenario where the type set should remain stable. **`classifiable_subset_var(t)`** provides a mutable variant with `Read()` and `Write()` operations, enabling dynamic type sets that change during program execution. This is essential for runtime systems where component types are added or removed as entities evolve. **`classifiable_subset_key(t)`** represents keys used to identify specific instances when adding them to a mutable set. These keys enable removal of specific instances later, supporting lifecycle management of registered types. Unlike ordinary classes, `classifiable_subset` types cannot be directly instantiated. You must use the constructor functions `MakeClassifiableSubset()` and `MakeClassifiableSubsetVar()`: ```verse # Immutable set, initially empty EmptySet:classifiable_subset(component) = MakeClassifiableSubset() # Immutable set with initial instances InitialSet:classifiable_subset(component) = MakeClassifiableSubset(array{physics_component{}, render_component{}}) # Mutable set DynamicSet:classifiable_subset_var(component) = MakeClassifiableSubsetVar() ``` The base type `t` must be ``, ensuring runtime type queries are possible. This restriction is enforced at compile time: ```verse ComponentSet:classifiable_subset(component) = MakeClassifiableSubset() # Invalid: non-castable types cannot be used regular_class := class: Value:int # This would be an error: # BadSet:classifiable_subset(regular_class) = MakeClassifiableSubset() ``` You cannot subclass these types or create instances through ordinary construction syntax. This ensures that all sets use the proper internal representation for efficient type queries. #### Type Hierarchy Semantics The crucial insight of `classifiable_subset` is that it tracks runtime types, not individual instances. When you add an instance to the set, the system records that instance's actual runtime type. More importantly, type queries respect the inheritance hierarchy: ```verse # Add a rigid body instance Set:classifiable_subset(component) = MakeClassifiableSubset(array{rigid_body_component{}}) # Query results respect hierarchy Set.Contains[component] # true - rigid_body is a component Set.Contains[physics_component] # true - rigid_body is a physics_component Set.Contains[rigid_body_component] # true - directly present ``` This hierarchy awareness makes `classifiable_subset` fundamentally different from a simple set of type tags. The `Contains` operation asks "does this set contain any type that is-a T?" rather than "does this set contain exactly T?". When you add instances of different types, each distinct runtime type is tracked separately: ```verse # Add multiple different types TheSet:classifiable_subset_var(component) = MakeClassifiableSubsetVar() Key1 := TheSet.Add(physics_component{}) Key2 := TheSet.Add(render_component{}) Key3 := TheSet.Add(audio_component{}) TheSet.Contains[component] # succeeds - all three are components TheSet.Contains[physics_component] # succeeds - physics_component present TheSet.Contains[render_component] # succeeds - render_component present ``` The set remembers each distinct type that was added. When you remove an instance by its key, that specific type is removed only if it was the last instance of that type: ```verse # Add multiple instances of same type TheSet:classifiable_subset_var(component) = MakeClassifiableSubsetVar() Key1 := TheSet.Add(physics_component{}) Key2 := TheSet.Add(physics_component{}) TheSet.Contains[physics_component] # succeeds TheSet.Remove[Key1] TheSet.Contains[physics_component] # still succeeds - Key2 remains TheSet.Remove[Key2] # TheSet.Contains[physics_component] # fail - last instance removed ``` #### Core Operations The `classifiable_subset` types provide several operations for querying and manipulating type sets: **Contains** checks whether any type in the set matches or is a subtype of the queried type: ```verse TheSet:classifiable_subset(component) = MakeClassifiableSubset(array{physics_component{}}) if (TheSet.Contains[component]): # Physics component is present (and is a component) if (TheSet.Contains[render_component]): # No render component present ``` **ContainsAll** verifies that all types in an array are present in the set: ```verse TheSet:classifiable_subset(component) = MakeClassifiableSubset(array{physics_component{}}) if (TheSet.ContainsAll[array{physics_component, render_component}]): # Both physics and render components are present ``` **ContainsAny** checks whether at least one type from an array is present: ```verse if (TheSet.ContainsAny[array{physics_component, audio_component}]): # Either physics or audio component (or both) is present ``` **Add** (mutable sets only) adds an instance and returns a key for later removal: ```verse TheSet:classifiable_subset_var(component) = MakeClassifiableSubsetVar() Key := TheSet.Add(physics_component{}) # Can later remove using Key ``` **Remove** (mutable sets only) removes a previously added instance by its key: ```verse TheSet:classifiable_subset_var(component) = MakeClassifiableSubsetVar() Key := TheSet.Add(physics_component{}) if (TheSet.Remove[Key]): # Successfully removed else: # Key was not present (already removed or never added) ``` **FilterByType** creates a new set containing only types that are compatible (assignable to or from) the specified type: ```verse TheSet:classifiable_subset(component) = MakeClassifiableSubset(array{ physics_component{}, render_component{}, audio_component{}}) # Filter to physics-related types PhysicsSet := TheSet.FilterByType(physics_component) PhysicsSet.Contains[physics_component] # true PhysicsSet.Contains[render_component] # false - unrelated sibling PhysicsSet.Contains[component] # true - base type compatible ``` The filtering respects both upward and downward compatibility in the type hierarchy, keeping types that could be assigned to or from the filter type. **Union** combines two sets using the `+` operator: ```verse Set1:classifiable_subset(component) = MakeClassifiableSubset(array{physics_component{}}) Set2:classifiable_subset(component) = MakeClassifiableSubset(array{render_component{}}) Combined := Set1 + Set2 Combined.Contains[physics_component] # true Combined.Contains[render_component] # true ``` For mutable sets, the Read/Write operations enable copying and updating: ```verse Set1:classifiable_subset_var(component) = MakeClassifiableSubsetVar() Set1.Add(physics_component{}) Set2:classifiable_subset_var(component) = MakeClassifiableSubsetVar() Set2.Write(Set1.Read()) # Copy Set1's contents to Set2 ``` #### Design Considerations Several important constraints govern `classifiable_subset` usage: The base type must be `` to enable runtime type queries. This requirement ensures that type checks can be performed efficiently. You cannot subclass `classifiable_subset` types or create instances except through the designated constructor functions. This restriction maintains internal invariants required for correct type tracking. Keys from one set cannot be used with a different set—they're bound to the specific set instance where the element was added. The type parameter must be consistent across operations. You cannot add a `physics_component` to a `classifiable_subset(render_component)` even if both inherit from `component`: ```verse render_set:classifiable_subset(render_component) = MakeClassifiableSubset() physics_comp:physics_component = physics_component{} # This would be a type error - physics_component is not a render_component # render_set.Add(physics_comp) ``` Mutable sets require careful lifetime management. Keys become invalid when their corresponding instances are removed, and attempting to remove an already-removed key triggers a failure. Performance characteristics matter for large type sets. While `Contains` queries are efficient due to the internal representation, operations like `FilterByType` may need to examine each type in the set. When designing systems with `classifiable_subset`, consider whether immutable or mutable sets better fit your needs. Immutable sets provide stronger guarantees and work well for configuration, while mutable sets support dynamic systems where component types change frequently. The hierarchy-aware semantics mean that adding a derived type makes queries for base types succeed. This is usually desirable but requires awareness—if you only want exact type matches, `classifiable_subset` may not be the right tool.