ClassDefinition_step4 - gama-platform/gama GitHub Wiki

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

Feature Syntax
Abstract class class ClassName virtual: true {
Virtual action return_type action_name() virtual: true;
Implement inherited virtual action Redefine with the same signature (no virtual: true)
Partially abstract intermediate Implements some virtual actions → becomes virtual itself
Polymorphic list list<AbstractClass> items <- [concrete1, concrete2];
Type check obj is ConcreteClass
⚠️ **GitHub.com Fallback** ⚠️