Kappa: Capabilities and Modes
In Encore, a capability is simply a reference to an object. The type of a capability defines its interface, i.e. the operations that are available on that object. Capabilities are introduced through traits and classes:
active class Greeter
def greet() : unit
println("Hello, world!")
end
end
With this class present, the expression new Greeter
results in a
capability of type Greeter
, whose interface contains a single
method greet
.
In addition, a capability must declare a mode, which describes
how one interacts with the capability. In the example above, the
mode of Greeter
is active
, which lets us know that the
underlying object is an active object and that communication with
this object happens asynchronously. There are five modes in Encore:
active
- the underlying object has its own logical thread of control and will always be interacted with asynchronously.local
- the underlying object is a normal passive object, but it may not be shared between active objects. Use this mode for data that is intended for use by a single active object.read
- this capability will not cause any mutations to the underlying object. An object that is only referenced byread
capabilities is effectively immutable and may thus be shared across active objects without risk of data-races.linear
- this capability is the only capability to the underlying object (modulo subordinate capabilities, see below). Alinear
capability may not be copied, but must be moved using theconsume
keyword . Use this mode for mutable data that needs to be passed between active objects.subord
- a subordinate capability may never be passed from the context in which it was created. Use this mode for strongly encapsulated data that is used to define the representation of another object.
Another way to look at modes is that they explain why interaction
with the underlying object is safe from data-races. They reify
(and enforce) programming patterns like actors with mailboxes
(active
), encapsulation (local
and subord
), immutability
(read
) and uniqueness (linear
).
Traits
Encore traits are building blocks for creating classes. Traits
require fields and methods, and provide methods. When creating a
class C
from the traits T1
and T2
, C
must declare the
fields that T1
and T2
require. If T1
requires a method m
,
m
must be provided by T2
or C
.
Required fields are prefixed by the keyword require
. A field may
be mutable (var
) or immutable (val
). In the latter case, the
field may only be written in the constructor:
trait Greeter
require val name : String
def greet() : unit
println("Hey {}! You're awesome!", this.name)
end
end
A trait must be provided with a mode when it is included by a class. The same trait can be used with different modes for different classes:
class ActiveGreeter : active Greeter
val name : String
def init(name : String) : unit
this.name = name
end
end
class LocalGreeter : local Greeter
val name : String
def init(name : String) : unit
this.name = name
end
end
active class Main
def main(args : [String]) : unit
val g1 = new ActiveGreeter("Alice")
val g2 = new LocalGreeter("Bob")
g1!greet()
g2.greet()
end
end
Optionally, a trait can provide a mode at the declaration site. This trait can only be included together with other traits of the same mode . Specifying the mode on inclusion is optional in this case:
local trait Rename
require var name : String
def setName(name : String) : unit
this.name = name
end
end
class LocalGreeter : local Greeter + Rename
var name : String
def init(name : String) : unit
this.name = name
end
end
active class Main
def main(args : [String]) : unit
val g1 = new ActiveGreeter("Alice")
val g2 = new LocalGreeter("Bob")
g1!greet()
g2.setName("Bobby")
g2.greet()
end
end
A required method may be provided by another trait or by the including class:
trait Greeter
require val name : String
require def getEncouragement() : String
def greet() : unit
println("Hey {}! {}", this.name, this.getEncouragement())
end
end
trait Awesome
def getEncouragement() : String
"You're awesome!"
end
end
class ActiveGreeter : active Greeter + active Awesome
val name : String
def init(name : String) : unit
this.name = name
end
end
class LocalGreeter : local Greeter
var name : String
def init(name : String) : unit
this.name = name
end
def getEncouragement() : String
"You rock!"
end
end
active class Main
def main(args : [String]) : unit
val g1 = new ActiveGreeter("Alice")
val g2 = new LocalGreeter("Bob")
g1!greet()
g2.greet()
end
end
Modeless traits, minor modes and read traits
A trait T
that does not provide a mode on declaration will be
typechecked with this
having the type subord T
. This means
that this
may not be leaked outside of the object, and it is
this restriction that makes it possible to give any mode to T
when it is included by a class. The subord
mode is a minor
mode, that may be overridden on trait inclusion.
The other minor mode is the read
mode, which is special in
several ways. The read
mode must be given on trait declaration,
and such a trait may only require val
fields containing values
that may not be mutated directly (i.e. primitives, other read
capabilities or active
capabilities). Being a minor mode, the
read
trait is typechecked with a subordinate this
, and thus
the mode may be overridden on trait inclusion.
Classes
A class is a blueprint for creating objects. It may declare fields
and methods and include any number of traits. Optionally, a class
may also provide a mode. A class that provides a mode m
may only
include traits of mode m
. Specifying the mode of traits included
by moded classes is optional:
trait Greeter
require val name : String
require def getEncouragement() : String
def greet() : unit
println("Hey {}! {}", this.name, this.getEncouragement())
end
end
trait Awesome
def getEncouragement() : String
"You're awesome!"
end
end
active class ActiveGreeter : Greeter + Awesome
val name : String
def init(name : String) : unit
this.name = name
end
def getName() : String
this.name
end
end
A class that does not provide a mode may only provide a constructor method and methods required by its included traits (but see below on overriding and extending traits on inclusion). In this case, an unmoded class can be seen as an alias of its included traits:
class LocalGreeter : local Greeter
val name : String
def init(name : String) : unit
this.name = name
end
-- Required by Greeter
def getEncouragement() : String
"You rock!"
end
end