Classes & Polymorphism
While structs and enums form the core of data modeling in Wysna, the language also supports classes and interfaces when reference identity, encapsulation, and dynamic polymorphic dispatch are required.
Defining Interfaces
Section titled “Defining Interfaces”Interfaces declare method contracts without providing implementations:
interface Shape { fn area() -> float fn perimeter() -> float}Implementing Interfaces with Classes
Section titled “Implementing Interfaces with Classes”Classes define state and methods with explicit access control (public or private). Constructors are declared with fn init(...) and must return unit (or bare return). Receiver state is accessed via this:
interface Shape { fn area() -> float fn perimeter() -> float}
class Rectangle implements Shape { public width: float public height: float
fn init(width: float, height: float) { this.width = width this.height = height }
public fn area() -> float { return this.width * this.height }
public fn perimeter() -> float { return 2.0 * (this.width + this.height) }}
class Circle implements Shape { public radius: float
fn init(radius: float) { this.radius = radius }
public fn area() -> float { return 3.14159265359 * this.radius * this.radius }
public fn perimeter() -> float { return 2.0 * 3.14159265359 * this.radius }}Instantiation and Polymorphic Dispatch
Section titled “Instantiation and Polymorphic Dispatch”Classes are instantiated by calling their type constructor ClassName(...) (or optionally with the new keyword new ClassName(...)):
interface Shape { fn area() -> float fn perimeter() -> float}
class Rectangle implements Shape { public width: float public height: float
fn init(width: float, height: float) { this.width = width this.height = height }
public fn area() -> float { return this.width * this.height }
public fn perimeter() -> float { return 2.0 * (this.width + this.height) }}
class Circle implements Shape { public radius: float
fn init(radius: float) { this.radius = radius }
public fn area() -> float { return 3.14159265359 * this.radius * this.radius }
public fn perimeter() -> float { return 2.0 * 3.14159265359 * this.radius }}
fn print_shape_info(shape: Shape) -> unit { print("Area: ", shape.area()) print("Perimeter:", shape.perimeter())}
fn main() -> unit { let rect: Shape = Rectangle(10.0, 5.0) let circ: Shape = Circle(7.0)
print("Rectangle:") print_shape_info(rect)
print("Circle:") print_shape_info(circ)}Inheritance and super
Section titled “Inheritance and super”Classes support single inheritance via extends. Subclasses can invoke the parent constructor using super.init(...) and call overridden methods with super.method(...):
class Polygon { public name: str
fn init(name: str) { this.name = name }
public fn describe() -> str { return "Polygon: " + this.name }}
class Triangle extends Polygon { public base: float public height: float
fn init(name: str, base: float, height: float) { super.init(name) this.base = base this.height = height }
public fn describe() -> str { return super.describe() + " (Triangle)" }}Reference Identity vs Value Semantics
Section titled “Reference Identity vs Value Semantics”Unlike struct (which provides pure Copy-on-Write value semantics), instances of class have reference identity:
- Assigning
let b = ashares the underlying heap object. - Mutating a field through
b.width = 20.0is immediately visible througha.width. - To prevent cyclical reference memory leaks in graphs, use weak references via
Weak[T],weak(obj)andupgrade(handle).
Under the hood, interface dispatch in Wysna utilizes compact, type-erased vtables generated in C99, guaranteeing constant time ($O(1)$) dynamic dispatch.