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 by read 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). A linear capability may not be copied, but must be moved using the consume 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

results matching ""

    No results matching ""