Classes
LuneScript supports class-based object-oriented programming.
Definition and Instantiation
Classes are defined with the class keyword and instantiated with new.
class MyClass {
pri let val: int;
// Constructor
pub fn __init( val: int ) {
self.val = val;
}
// Method
pub fn func(): int {
return self.val;
}
}
let obj = new MyClass( 10 );
print( obj.func() ); // 10
- Constructor: Defined with the name
__init. All member variables must be initialized here. - self: Use
selfto refer to itself (the instance). - Access Control:
pub(public),pro(protected - up to subclasses),pri(private), andlocal(within the module). The default ispri.
Accessors
Accessors can be automatically generated in the format {getter, setter} during member
definition.
class Data {
pri let mut val: int {pub, pub}; // getter=pub, setter=pub
pub fn __init( val: int ) { self.val = val; }
}
let mut d = new Data( 1 );
$ Operator
To access an automatically generated getter, prepend $ to the member name. This is
syntactic sugar that is internally converted into a call to the
get_MemberName() method.
For setter, a method named set_MemberName is generated.
Why not "properties"?
LuneScript intentionally avoids "properties" (a style found in some languages where method calls look
like direct access). This ensures that developers can clearly distinguish between a simple data
assignment/reference and a method call with potential side effects directly in the source code.
class Data {
pri let mut val: int {pub, pub};
pub fn __init( val: int ) { self.val = val; }
}
let mut d = new Data( 1 );
print( d.$val ); // Getter call ($ sign)
d.set_val( 20 ); // Setter call (set_MemberName)
print( d.$val );
Omitting Access Control
If a setter is not needed, omit the second specification or specify non.
class AccessorTest {
pri let val1: int {pub}; // Getter only (no setter)
pri let val2: int {pub, non}; // Same as above
}
Immutable Return Value
To make the return value of a getter immutable, append &.
class ListWrapper {
pri let mut list: List<int> {pub&, pub};
}
// $list returns &List<int> (read-only), so it cannot be modified
Inheritance and Overriding
Single inheritance is possible with extend. The override keyword is mandatory for
overriding methods.
class Base {
pub fn func() { print( "Base" ); }
}
class Sub extend Base {
pub override fn func() {
super(); // Parent class call
print( "Sub" );
}
}
In a subclass constructor, you must call super() at the beginning.
Abstract Classes
A class that has methods without implementation (abstract fn) must be defined as an
abstract class.
abstract class Animal {
pub abstract fn cry();
}
Interfaces
Interfaces can be used instead of multiple inheritance. Interfaces do not have implementations.
interface Runner {
pub fn run();
}
class Dog extend (Runner) {
pub fn run() { print( "Run!" ); }
}
When implementing multiple interfaces, write it as extend (IF1, IF2).
Advertise (Delegation)
A feature that exposes (delegates) the methods of a composed (contained) object as if they were its own.
class Inner {
pub fn foo() { print("Inner.foo"); }
}
class Wrapper {
pri let inner: Inner;
advertise inner; // Expose public methods of inner as methods of Wrapper
}
Mapping
By declaring extend (Mapping), reciprocal conversion with Map (JSON-like objects) becomes
possible.
class User extend (Mapping) {
let name: str {pub};
let age: int {pub};
}
// Create from JSON (Map)
let user = unwrap User._fromMap( { "name": "John", "age": 20 } );
// Convert to Map
let map = user._toMap();
Prototype Declarations
Used when writing the implementation of a method later (or in a separate file). Useful when mutual references are needed.
class Test {
pub fn func(); // Declaration only
}
// Implementation
pub fn Test.func() {
print( "impl" );
}