Classes
A class instance is shared on assignment and can be changed through any name that refers to it; see
Values & Sharing. A class that implements
Pattern is a script pattern.
Defining a class
Section titled “Defining a class”class Point { let x; // field let y: Number; // typed field: every write is checked
fn new(x, y = 0) { // constructor this.x = x; this.y = y; }
fn add(other) -> Point { // method return Point(this.x + other.x, this.y + other.y); }
fn __str__() { // how print() and f-strings show it return f"({this.x}, {this.y})"; }}
let p = Point(3, 4); // call the class to constructlet q = p.add(Point(y: 2, x: 1)); // named arguments workassert q.x == 4;print(q); // (4, 6)| Member | Syntax | Notes |
|---|---|---|
| Field | let name; or let name: Type; | Declared once; takes no /// documentation |
| Constructor | fn new(params) { … } | Called by ClassName(args); sets fields through this |
| Method | fn name(params) { … } | Called as instance.name(args); /// documents it |
| Display | fn __str__() { … } | Used by print() and format strings |
| Conversion | fn from(x: Source) -> ClassName { … } | Applied where the class is asked for; never called |
Parameters of new and methods follow the function rules:
defaults, named arguments, rest parameters, and argument-count errors. A method’s default may use
this.
A field and a method may share a name: p.x reads the field and p.x() calls the method.
Fields
Section titled “Fields”Fields are read and assigned from anywhere, including through nested instances, and take compound assignment:
class Inner { let value; fn new(v) { this.value = v; }}
class Outer { let inner; fn new(i) { this.inner = i; }}
let o = Outer(Inner(1));o.inner.value = 5;o.inner.value += 1;assert o.inner.value == 6;print(o.inner.value);A method that assigns to this changes the instance in place, and every name sharing the
instance sees the change. A class that implements Clone has .clone(), which
makes an independent copy.
Display
Section titled “Display”With __str__, print() and f"{…}" show what it returns. Without it, or when it returns a
non-String, an instance shows as its class name and its fields in alphabetical order:
class Pair { let b; let a; fn new(a, b) { this.a = a; this.b = b; }}print(Pair(1, 2)); // Pair { a: 1, b: 2 }assert type(Pair(1, 2)) == "Pair";Conversions
Section titled “Conversions”fn from declares how a value of another type becomes a value of the class. Where an annotation
asks for the class and a value does not fit it, from converts the value once, at that place: an
argument, a return, a typed variable or a typed field.
class Hz { let value: Number; fn new(value) { this.value = value; } fn from(n: Number) -> Hz { return Hz(n); }}
fn period_ms(f: Hz) -> Number { return 1000 / f.value; }assert period_ms(500) == 2; // 500 converts to Hz(500)let a: Hz = 440;assert a.value == 440;- A class has one
from. It takes one annotated parameter, has no default or rest parameter, returns the class itself, and does not usethis. A union parameter (x: Number | String) takes several types. fromis not a method:a.from(1)is an error. Writelet a: Hz = 1;instead.- What
fromreturns must be the class as it is. A conversion never leads to another one. - An interface cannot declare
from. - A pattern callback converts too, with the same
from.
How conversions apply at a boundary is in
Values & Types. The prelude’s Note
converts a MIDI number: chord(60, "maj").
In modules
Section titled “In modules”A class in a module is exported like a fn and constructed as module.ClassName(args).
private class keeps it inside the module. See Modules.
Interfaces
Section titled “Interfaces”An interface names the methods that several classes have in common. A class that names it after
implements defines those methods, and its values fit the interface wherever an annotation names
it:
interface Shape { fn area() -> Number; // required: each class defines it
fn twice() -> Number { // default: a class may leave it out return 2 * this.area(); }}
class Square implements Shape { let side: Number; fn new(side) { this.side = side; } fn area() -> Number { return this.side * this.side; }}
fn total(a: Shape, b: Shape) -> Number { return a.area() + b.area();}
assert total(Square(2), Square(3)) == 13;assert Square(2).twice() == 8; // the default, with this as the square- An interface holds methods only: no fields, no
newand nofrom. An interface does not implement another interface. - A method written with
;is required. A method with a body is a default. - A class implements several interfaces with a list:
class Clip implements Shape, Named. - A class’s own method is used before a default. When two of its interfaces have a method of the same name, the class must define that method itself.
- In a default,
thisis the instance. The editor completesthis.with the interface’s methods. - An interface is a type and makes no values: a function can have the same name.
type(x)names the class of the value, not the interface. A builtin value that implements an interface natively is named by the interface:type([C4 E4])is"Pattern".- The stdlib’s
Patternis an interface: a class that implements it is a pattern, and has every combinator. See Script Patterns.
A method of an interface may return Self: the type of the value it is called on. A class
implementing it returns its own class, written by name or as Self. The prelude’s Clone is such
an interface. A class that implements it defines what its copy is:
class Point implements Clone { let x; let y; fn new(x, y) { this.x = x; this.y = y; } fn clone() -> Self { return Point(this.x, this.y); }}
let a = Point(1, 2);let b = a.clone();b.x = 10;assert a.x == 1;Arrays, dicts, tuples, sections and timelines are Clone natively. A copy clones each value inside
it that is Clone, through that value’s own clone(), and shares every other value.
The class is checked when its definition runs, and the editor shows the same errors. The class must define each required method itself. Each method must have the interface’s parameter names, in the same order, with a default and a rest parameter where the interface has one. Each parameter must take the values that the interface’s parameter takes. When the interface declares a return type, the class must declare a return type that fits it:
class Blob implements Shape { }// → Type error at 1:23: Blob implements Shape but has no area()A class keeps the interfaces as they were when its definition ran. If you execute an interface again with changes, the classes defined before keep the old methods. The next class definition is checked against the new interface.
In a module, an interface is exported as a class is, and private interface keeps it in the module.
Import it by name: use "./shapes" { Shape };.
Native classes
Section titled “Native classes”A native class is the type of values the engine or a
native extension makes: AudioTrack, Pattern,
Sampler. It declares the methods of those values, and nothing else:
- It has no fields and no
new. - Its name is a type, not a constructor. A function of the same name, such as
fn AudioTrack(name), makes the values.
The standard library declares its builtin types this way. Write one only for the values a native extension returns.
Not supported
Section titled “Not supported”- Inheritance — no
extendsorsuper. Classes share methods through an interface. - Static methods or fields — every member belongs to an instance.
- Private members — every field and method is public. For encapsulation, use module-level
private. - Getters and setters — fields are read and assigned directly.