-
Notifications
You must be signed in to change notification settings - Fork 0
Classes & interfaces in Compose
Classes are defined with the class keyword.
Classes can contain fields, methods and associated functions. A function is a method if
it takes self as its first parameter.
Functions and fields are private by default and can be made public with the pub keyword.
pub class Point {
pub x,
pub y,
pub fn origin() {
new Point {
x: 0, y: 0
}
}
pub fn magnitude(self) {
math.sqrt(self.x.pow(2) + self.y.pow(2))
}
}
Instances of classes are created with the new keyword. All fields must be specified.
let p = new Point { x: 3, y: 4 };
Since all fields must be specified, this means classes with private fields can only be created within
their definition.
To allow a class with private fields to be initialised outside the definition, it must expose an associated (factory) function that returns an instance.
Fields can be accessed with field access syntax (.). Private fields can only be accessed within the type definition.
pub class PositiveInt {
inner,
/// Create a positive int from the passed in value.
///
/// Panics if the value is a negative int, or of a different type.
pub fn create(value) {
match (value) {
Int v if v > 0 => new PositiveInt { inner: v },
Int v => panic("Cannot create a PositiveInt from an int <= 0: " + v.to_string()),
other_ty => panic("Cannot create a PositiveInt from type: ", other_ty.ty().to_string()),
}
}
pub fn get(self) {
self.inner // accessing inner is allowed here
}
}
let p = PositiveInt::create(2);
p.inner; // Error: Not allowed to access private field `inner` outside its definition
Associated functions are called with path access (::) on the type.
let origin = Point::origin();
Methods can be called with method calling syntax as well as associated function syntax.
let p = new Point { x: 3, y: 4 };
let mag1 = p.magnitude();
let mag2 = Point::magnitude(p);
Interfaces
Interfaces can be defined to support polymorphic patterns.
pub interface Drawable {
fn draw(self);
}
pub class Point: Drawable {
pub x, pub y,
fn Drawable::draw(self) {
// ...
}
}
Interface methods can be called like regular methods:
let p = Point::origin();
p.draw();
Name conflict resolution
Take the following example:
pub class Point: Drawable, ColorDrawable {
pub fn draw(self) {
// plain method
}
fn Drawable::draw(self) {
// Drawable interface method
}
fn ColorDrawable::draw(self) {
// ColorDrawable interface method
}
}
Calling point.draw() is ambiguous in this instance and will result in an error. This is resolved by using fully qualified names:
point.Point::draw();
point.Drawable::draw();
point.ColorDrawable::draw();
// or as associated functions
Point::draw(point);
Drawable::draw(point);
ColorDrawable::draw(point);
Note: the fully qualified names do need to be in scope.
fn method(self)defines a plain class methodfn Interface::method(self)defines an interface method- Plain methods do not automatically satisfy interface requirements
- Interface methods do not shadow plain methods
- Conflicts can be resolved by using fully qualified names.
Extension implementations
Sometimes it makes sense to implement methods and associated functions outside the initial definition.
To accomplish this Compose allows extending types.
To extend a type, make sure it is imported and then create an extend block:
pub extend Point as PointTransformationExt {
fn scale(self, factor) {
// ...
}
}
This extension then becomes available on instances of Point. To use these extension methods from other
modules the extension first needs to be imported:
import "point_extensions.cmps": PointTransformationExt;
let point = new Point { x: 1, y: 2 };
point.scale(2);
assert::eq(point.x, 2);
// or as an associated function
PointTransformationExt::scale(point, 2);
Extending types with interface implementations
Sometimes it makes sense to implement an interface for a type outside its definition.
Albeit implementing an interface for a type you don't own, or simply for organisation.
Extension blocks can implement interfaces for other types:
pub extend Point: Drawable as DrawablePointExt {
fn draw(self) {
// ...
}
}
let point = new Point { ... };
point.draw(); // if there are no name conflicts
// or
point.DrawablePointExt::draw(); // fully unambiguous
Interface witnesses
Because Compose supports implementing interfaces multiple times for types, disambiguation for which
implementation to use when a type erased value is passed to a function is needed.
pub fn use_drawable(drawable) {
drawable.draw();
}
pub class Point: Drawable {
fn Drawable::draw(self) { "standard" }
}
pub extend Point: Drawable as FancyDrawablePointExt {
fn draw(self) { "fancy" }
}
pub extend Point: Drawable as BasicDrawablePointExt {
fn draw(self) { "basic" }
}
let p = new Point;
use_drawable(p); // Which implementation should use_drawable pick?
In this case an interface witness can be used to specify which implementation to use.
If the type itself implements an interface, and no witness is specified the base implementation will
be used.
use_drawable(p as FancyDrawablePointExt); // "fancy"
use_drawable(p as BasicDrawablePointExt); // "basic"
use_drawable(p): // "standard"
List view
0 of 0 selected 0 issues of 0 selected
There are no open issues in this milestone
Add issues to milestones to help organize your work for a particular release or project. Find and add issues with no milestones in this repo.