Home › Type System
Custom types, traits, parent types, data fields, and member methods in LaiRu 0.1.
Custom types can be defined either with type_define or using the richer
define … => type { … } body form.
// simple declaration
type_define('Person')
type_define('Employee', -parent='Person')
type_define('Named', -traits=array('Printable'))
// full type body
define Person => type {
data public name::string = 'Ada'
data protected role::string = 'dev'
public summary()::string => .name + ' (' + .role + ')'
}
Member methods are defined with the TypeName->method syntax.
Inside a member body, #self refers to the receiver, and
.field is shorthand for #self->field.
define Person->greet => 'Hello, I am ' + .name define Person->score(value) => #value + 1 define Person->score(left, right) => #left + #right local(p) = Person(-name='Ada', -role='dev') #p->greet #p->score(10) #p->score(3, 4)
The onCreate method is called when an object is created via its type name.
Data fields declared in the type body can be passed as keyword arguments:
define Person->onCreate(name) => {
.name = #name
}
local(p) = Person('Ada')
#p->name
define Person->+(suffix) => .name + #suffix local(p) = Person(-name='Ada') #p + '!' // 'Ada!'
Traits define reusable interface contracts. They can declare requirements (methods the concrete type must provide) and provided methods (default implementations).
define TraitReadable => trait {
require get(index::integer)::string
provide first()::string => .get(1)
}
define TraitNamed => trait {
import TraitReadable
provide label()::string => .first
}
A type imports a trait using the trait { import … } section
inside its body, or via trait_assign:
define Widget => type {
trait {
import TraitReadable, TraitNamed
}
data public name::string
public get(index::integer)::string => .name
}
local(w) = Widget(-name='button')
#w->isA('TraitNamed') // true
#w->label // 'button'
define Animal => type {
data public name::string
public speak => 'I am ' + .name
}
define Dog => type {
parent Animal
public speak => inherited->speak + ' and I woof'
}
local(d) = Dog(-name='Rex')
#d->speak // 'I am Rex and I woof'
#d->isA('Animal') // true
The inherited->method form (also written ..method inside
a member body) calls the nearest ancestor’s implementation.
Fields declared with data generate getter and setter methods automatically.
Access modifiers control who can read or write a field:
define Account => type {
data public username::string
data protected balance::decimal = 0.0
data private secret::string
}
| Modifier | Read | Write |
|---|---|---|
public | Anyone | Anyone |
protected | Same type / subtype members | Same type / subtype members |
private | Same type members only | Same type members only |
Direct member assignment such as obj->field = value dispatches
to a visible field= setter before falling back to the generated slot write.
Slot writes enforce declared type constraints.
Custom objects are reference-backed: assigning #b = #a makes both variables
point to the same object. To get an independent copy use copy or
clone:
local(a) = Person(-name='Ada') local(b) = #a->copy // independent structural copy #b->name = 'Charles' #a->name // still 'Ada'
object_serialize / object_deserialize provide a
value-graph envelope for round-tripping custom objects, preserving shared identity and
cycles inside the graph.
local(p) = Person(-name='Ada')
#p->type // 'Person'
#p->isA('Person') // true
#p->isA('Animal') // false (unless Person inherits Animal)
#p->listMethods // list of visible method signatures
#p->hasMethod('greet') // true / false