Skip to main content
Version: 🚧 Alpha 🚧

Virtual Classes

What Is a Virtual Class?​

A virtual class cannot be instantiated directly. It acts as a contract — every concrete subclass must implement all declared virtual actions.

class sensor virtual: true {

string id <- "sensor";
bool active <- true;

// Virtual (abstract) action: every concrete subclass MUST implement
float read() virtual: true;

// Virtual action: every concrete subclass MUST implement
string unit() virtual: true;

// Concrete action: implemented once here, calls the virtual actions
string formatted_reading() {
if !active { return id + ": [OFF]"; }
return id + ": " + (read() with_precision 2) + " " + unit();
}
}

The sensor class:

  • Cannot be instantiated: sensor s <- sensor() yields nil (s is nil).
  • Acts as a contract: every subclass must implement read() and unit().
  • Provides a concrete formatted_reading() that calls the virtual actions polymorphically.

Concrete Subclasses​

A concrete class (no virtual: true) must implement every inherited virtual action:

class thermometer parent: sensor {

float temperature <- 20.0;
float min_temp <- #infinity;
float max_temp <- -#infinity;

// Implements sensor.read()
float read() {
return temperature;
}

// Implements sensor.unit()
string unit() {
return "°C";
}

action update(float t) { // new action specific to thermometer
temperature <- t;
if t < min_temp { min_temp <- t; }
if t > max_temp { max_temp <- t; }
}
}

class barometer parent: sensor {

float pressure <- 1013.25;

// Implements sensor.read()
float read() {
return pressure;
}

// Implements sensor.unit()
string unit() {
return "hPa";
}

// Specific action not part of the sensor contract
float estimated_altitude() {
return 44330.0 * (1.0 - (pressure / 1013.25) ^ 0.1903);
}
}

If a child class does not implement ALL virtual actions from its parent, it becomes virtual itself (and cannot be instantiated).

Partially Abstract Intermediate Class​

A class that implements SOME but not all virtual actions from its parent becomes virtual itself:

class calibrated_sensor virtual: true parent: sensor {

float calibration_offset <- 0.0;

// Implements unit() from sensor — concrete
string unit() {
return "raw";
}

// read() is still virtual here — calibrated_sensor is itself abstract
// You cannot instantiate this class
}

class light_sensor parent: calibrated_sensor {

float raw_lux <- 0.0;

// Finally implements read() from sensor → light_sensor is CONCRETE
float read() {
return raw_lux + calibration_offset;
}

// Override the "raw" unit from calibrated_sensor
string unit() {
return "lux";
}
}

Hierarchy:

sensor (abstract)
├── thermometer (concrete)
├── barometer (concrete)
└── calibrated_sensor (abstract — still needs read())
└── light_sensor (concrete — implements read + overrides unit)

Polymorphism with Abstract Types​

A variable typed as the virtual class can hold any concrete subclass:

list<sensor> sensors <- [
thermometer(id: "temp01", temperature: 23.5),
barometer(id: "baro01", pressure: 1005.0),
light_sensor(id: "lux01", raw_lux: 850, calibration_offset: 15)
];

loop s over: sensors {
write s.formatted_reading(); // dispatches to the concrete class's read() and unit()
}

Aggregates Over a Polymorphic List​

float max_r <- max(sensors collect (each.active ? each.read() : -#infinity));
write "max raw reading = " + (max_r with_precision 2);

Accessing Concrete-Specific Actions​

You can only access concrete-specific methods (not in the abstract class) after narrowing down to the right type:

// barometer-specific: estimated_altitude() is NOT defined on sensor
loop s over: sensors {
if s is barometer {
barometer b <- s; // cast to barometer
write b.estimated_altitude() with_precision 0 + " m";
}
}

Type Checking with is​

if t1 is sensor {
// OK — thermometer is a sensor
}

if l1 is calibrated_sensor {
// OK — light_sensor extends calibrated_sensor which extends sensor
}

if t1 is barometer {
// false — thermometer and barometer are siblings
}

Example: Full Model​

class sensor virtual: true {
string id <- "sensor";
bool active <- true;

float read() virtual: true;
string unit() virtual: true;

string formatted_reading() {
if !active { return id + ": [OFF]"; }
return id + ": " + (read() with_precision 2) + " " + unit();
}
}

class thermometer parent: sensor {
float temperature <- 20.0;
float min_temp <- #infinity;
float max_temp <- -#infinity;

float read() { return temperature; }
string unit() { return "C"; }

action update(float t) {
temperature <- t;
if t < min_temp { min_temp <- t; }
if t > max_temp { max_temp <- t; }
}
}

class barometer parent: sensor {
float pressure <- 1013.25;

float read() { return pressure; }
string unit() { return "hPa"; }
}

global {
init {
list<sensor> sensors <- [
thermometer(id: "temp01", temperature: 23.5),
barometer(id: "baro01", pressure: 1005.0)
];

// Polymorphic dispatch
loop s over: sensors {
write s.formatted_reading();
}

// Toggle off
sensors[1].active <- false;
loop s over: sensors {
write s.formatted_reading(); // barometer now prints [OFF]
}
}
}

experiment Virtual_Classes title: "Virtual Classes" type: gui {}

Key Takeaways​

FeatureSyntax
Abstract classclass ClassName virtual: true {
Virtual actionreturn_type action_name() virtual: true;
Implement inherited virtual actionRedefine with the same signature (no virtual: true)
Partially abstract intermediateImplements some virtual actions → becomes virtual itself
Polymorphic listlist<AbstractClass> items <- [concrete1, concrete2];
Type checkobj is ConcreteClass