6.8 Classes and Interfaces
definition | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
class id_name(field_spec, ...) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
definition | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
in the expr space, a constructor function or form, which by default is a function that takes as many arguments as the supplied non-private field_specs in parentheses, and it returns an instance of the class;
in the annot space, an annotation, which is satisfied by any instance of the class, and by default an annotation constructor id_name.of or id_name.now_of, which default takes as many annotation arguments as supplied non-private field_specs in parentheses; the name is id_name.of if all such field_specs are for immutable fields;
in the bind space, a binding-pattern constructor, which by default takes as many patterns as the supplied non-private field_specs in parentheses and matches an instance of the class where the fields match the corresponding patterns;
in the namespace space, a namespace to access exported bindings as well as a function id_name.method, a function id_name.property, a syntactic form id_name.dot, and a field accessor id_name.field for each non-private method, property, dot syntax, and field in the class (including inherited methods, properties, dot syntax, and fields), respectively; and
in the class space, a representation of the class for reference as a superclass.
Fields, methods, properties, and dot syntax declared in a class can be accessed from an object (as opposed to just a class) using ., but fields, methods, and properties declared as private can only be accessed by . within methods and properties of the class or through an identifier bound by an internal form. In static mode (see use_static), a non-property method must be called like a function; in dynamic mode, a method accessed from an object closes over the object. Private fields, methods, and properties can be accessed with . only statically. Syntactic forms bound via dot can be accessed with . only statically, and functional update via with also relies on static access.
A field_spec has an identifier, keyword, or both. A keyword implies that the default constructor expects the corresponding argument as a keyword argument instead of a by-position argument. The default annotation and binding pattern similarly expect a keyword-tagged subform instead of a by-position form for the corresponding fields. The name of the field for access with . is the identifier, if present, otherwise the name is the symbolic form of the keyword. When a field_spec has the private modifier, however, then it is not included as an argument for the default constructor, binding form, or annotation form.
When a default-value expression or block is provided for a field after = or :, then the default constructor evaluates the default_expr or default_bodys to obtain a value for the argument when it is not supplied. If a by-position field has a default-value expression or block, then all later by-position fields must have a default. If the class extends a superclass that has a non- private by-position argument with a default, then all by-position arguments of the subclass must have a default. A default_expr or default_body can refer to earlier field names in the same class to produce a default value. If a priviate field_spec lacks a = and default-value expression, then a custom constructor must be declared with constructor.
If a block follows a class form’s field_spec sequence, the block contains a mixture of definitions, expressions, exports, and class clauses. A class clause adjusts the class and bindings created by the class form; it be one of the predefined clause forms, or it can be a macro that ultimately expands to a predefined form. Definitions and expressions in a class block are evaluated at when the class form is evaluated, and not when an instance is created. Definitions are scoped to the block for potential use by class clauses, but a class form is analogous to namespace in that local definitions can be exported. exported names must be distinct from all non-private field, method, property, and dot-syntax names (which are automatically exported from the class in its role as a namespace). Since the definitions and expressions of a class body must be processed to find class clauses in the body, the class is not available for use until after the definitions and expressions, as if the definitions and expressions appeared before the class form.
When a class_clause is a field form, then an additional field is added to the class, but the additional field is not represented by an arguments to the default constructor, annotation form, or binding-pattern form. Instead, the expr or body block the field gives the added field its initial value; that expression or block is evaluated each time an instance of the class is created, but it cannot refer to this, fields of the class, methods of the class, or properties of the class. All fields added through a field clause are mutable, and they can be updated in a custom constructor (form example) using assignment operators such as :=. The field can appear any number of times as a class_clause, with or without a private prefix.
When a class_clause is a method form, override form, abstract form, method-shaped final or private form, or property form, then the clause declares a method or property for the class. These clauses can appear any number of times as a class_clause to add or override any number of methods or properties. See method for more information on methods and properties.
When a class_clause is an extends form, the new class is created as a subclass of the extended class. The extended class must not be final. At most one class_clause can have extends.
When a class_clause is an implements form, the new class is created as an implementation of the named interfaces. Like a superclass, an interface can supply method and property implementations (that can be overridden) and have abstract methods and properties, but an interface does not have fields; see interface for more information. Prefixing implements with private makes the interface privately implemented; see interface for information on privately implementing an interface. A class_clause can have any number of implements clauses (with or without private). Any rule that applies to the superinterface of an interface also applies to the implemented interfaces of class, as well as any superinterface of those interfaces.
Unless some class_clause is nonfinal, then the new class is final, which means that it cannot have subclasses. When a class_clause is nonfinal, then the new class is not final. At most one class_clause can have nonfinal.
When a class_clause is an internal form, then the clause’s id is bound in similar ways as the main class id_name: as a constructor, annotation form, binding pattern form, and namespace. A use of the internal id as a constructor creates an instance of the same class, but the constructor expects arguments for all fields declared with field_specs, including private fields. For more information on internal names, see constructor, since the details of internal names are closely related to constructor, annotation, and binding pattern customization. Any number of internal declarations can appear among the class_clauses, which means that multiple internal aliases may be defined.
The class_clause forms constructor or expression, binding, and annotation replace default meanings of the defined id_name for an expression context, binding context, and annotation context, respectively. The reconstructor form with optional reconstructor_fields replaces the way that with functional update is implemented. The dot form (which must be imported through rhombus/meta) replaces the way that . accesses are resolved for expressions that have the class’s annotation. The static_info form (which must be imported through rhombus/meta) adds static information for the class’s instances. See constructor, expression, binding, annotation, reconstructor, dot, and static_info for more information on those forms.
When a method procedure is accessed from a class (as a namespace) via ., the procedure expects an extra by-position argument that must be an instance of the class, and the extra argument is supplied before all other arguments. A field accessor from a class (as a namespace) via . similarly takes an instance of the class, and it accepts a second argument to act as a mutator if the field is mutable. A property accessor from a class (as a namespace) via . takes an instance of the class, and it accepts an additional value to assign to the property (if the property supports assignment). Even when a method is accessed via its class instead of an object, if the method and class are not final, the called method is determined by the object and may be from a subclass that overrides the method; the same is true for properties.
Each field, method, property, and dot-syntax name must be distinct from all other field, method, property, and dot-syntax names, whether from a parenthesized field_spec, from a field clause, or from a method, property, or dot-syntax clause. If an extends or implements clause is present, then each name must also be distinct from any name in the superclass or interface, except that a override clause must name a method or property that is already declared in the superclass. Private superclass fields, methods, and properties are not visible to the subclass, so their names are not required to be distinct from subclass field, method, and property names. When a method or property is overridden via override, the original and overriding versions must be both methods or both properties.
See Rules for Static Information for information about static information associated with classes.
> class Posn(x, y)
> Posn(1, 2)
Posn(1, 2)
> Posn.x
#<function:Posn.x>
> Posn.x(Posn(1, 2))
1
> Posn(1, 2).x
1
> class Posn3(z):
extends Posn
class: superclass is final and cannot be extended
> class Posn2D(x, y):
> class Posn3D(z):
extends Posn2D
> Posn3D(1, 2, 3)
Posn3D(1, 2, 3)
> class Rectangle(w, h):
constructor (~width: w, ~height: h):
super(w, h)
> class Square():
extends Rectangle
constructor (~side: s):
super(~width: s, ~height: s)()
> Square(~side: 10)
Square(10, 10)
definition | ||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||
definition | ||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||
|
The body of an interface form has interface clauses that are similar to class clauses, but declared separately and sometimes with different syntax. For example, extends as an interface clause supports multiple superinterface names instead of just one, and extends can appear multiple times in an interface body.
Interfaces cannot be instantiated. They are implemented by classes via the implements form. When a class implements an interface (not privately), it has all methods and properties of the interface, and its instances satisfy the interface as an annotation.
Typically, an interface declares methods and properties with abstract to be implemented by classes that implement the interface. However, an interface can define method and property implementations; those implementations are inherited by classes that implement the interface or any subinterface that extends the interface. An interface can also have private helper methods and properties, but they are useful only when an interface also has implemented public methods or properties that refer to them.
When a class implements an interface privately using private implements, its instances do not satisfy the interface as an annotation. If the privately implemented interface has an internal name declared with internal, however, instances satisfy the internal name as an annotation. Methods and properties of a privately implemented instance can be accessed only with static . via the internal-name annotation. As long as a method or property belongs to only privately implemented interfaces, it can be overridden with private override, otherwise it is overidden normally. If a class declares the implementation of a interface both normally and privately, then the interface is implemented normally. Abstract private methods and properties must be implemented immediately in the class that privately implements the associated interface.
When a class or interface extends or implements multiple interfaces that provide a method or property with the same name, the method or property implementation must be the same for all interfaces. That is, the method or property must be abstract, the implementation must reside in a shared superinterface of the interfaces, or the method or property must be overridden in the implementing class. Overriding applies to same-named methods or properties of all interfaces.
The class.together form expands to a combination of namespace, annot.delayed_declare, and annot.delayed_complete declarations.
class Even():
class Odd():
class clause |
|
class clause |
|
interface clause |
|
interface clause |
class clause |
implements id_name ... |
|
class clause |
implements: id_name ...; ... |
class clause |
class clause |
|
class clause |
|
class clause |
|
class clause |
|
class clause |
|
class clause |
|
interface clause |
|
interface clause |
|
interface clause |
|
interface clause |
|
interface clause |
|
interface clause |
|
interface clause |
class clause |
|
class clause |
field id maybe_annot: body; ... |
class clause | |||||||||||
| |||||||||||
class clause | |||||||||||
| |||||||||||
class clause | |||||||||||
| |||||||||||
class clause | |||||||||||
| |||||||||||
class clause | |||||||||||
| |||||||||||
interface clause | |||||||||||
| |||||||||||
interface clause | |||||||||||
| |||||||||||
interface clause | |||||||||||
| |||||||||||
interface clause | |||||||||||
| |||||||||||
interface clause | |||||||||||
| |||||||||||
interface clause | |||||||||||
| |||||||||||
interface clause | |||||||||||
| |||||||||||
interface clause | |||||||||||
| |||||||||||
interface clause | |||||||||||
| |||||||||||
interface clause | |||||||||||
| |||||||||||
| |||||||||||
| |||||||||||
| |||||||||||
| |||||||||||
| |||||||||||
| |||||||||||
|
A method_impl is either an id followed by an optional result annotation and a block containing an entry point, or it has the same form as a fun definition with a form name like method in place of fun. A maybe_res_annot applies to the immediate method implementation as well as overriding implementations in subclasses; a result annotation within an entry point, in contrast, does not apply to subclasses. A maybe_res_annot can specify a converter annotation only if the method is final or the enclosing class is final; the conversion applies before inherited result annotations for the method are checked.
A property clause declares or overrides a property, which is like a method in that using the property evaluates a block. However, the property is used either as an expression, which is analogous to calling a method with no arguments, or as the left-hand side of an assignment operator like := form, which is analogous to calling a method with the right-hand side of := as the method argument. A property always supports a reference form, but it supports assignment only when the property form includes a := case. In a property’s := case, the part after := is a binding analogous to a function-argument binding, and the subsequent body will normally refer to that binding. Using := with a property always produces #void, but more generally, any value returned by the body of a property definition’s := case is ignored by assignment operators. A maybe_res_annot in a property clause applies to overriding implementations in subclasses, but it imposes no constraints on the right-hand part of := or other assignment operators when assigning to a property.
In the body of a method or property, the special expression form this refers to the object whose method was called. Fields (in the case of a class) and methods can be accessed using this and ., but they can also be used directly. Using a field, method, or property name directly is the same as using this and . in static mode (which implies that a direct reference to a method name must be a call of the method). An argument that has the same name as a field, method, or property shadows the field, method, or property.
In an interface, a method, override, or property declation can be just an identifier, or it can omit a body block. In that case, method, override, or property is treated as if abstract is added before. If arguments are declared for an abstract method, they determine the method’s expectations for static argument-count checking (see use_static), but they do not impose constraints on overriding implementations. When a property_decl uses the single-case | form, it declares the property as not supporting assignment; that declaration is not enforced on implementations of the property, but it affects static resolution of a property assignment.
class clause |
private implements id_name ... |
|
class clause |
private implements: id_name ...; ... |
|
class clause |
|
class clause |
|
class clause |
|
class clause |
|
class clause |
|
class clause |
|
class clause |
|
interface clause |
|
interface clause |
|
interface clause |
|
interface clause |
|
interface clause |
|
interface clause |
Private fields, methods, and properties can be accessed only within the body of the enclosing class or through an identifier declared with internal. When referenced via the . operator, only static references are allowed through the enclosing class’s annotation (not a subclass annotation) or through an internal identfier’s annotation.
class clause |
|
class clause |
|
class clause |
|
class clause |
|
class clause |
|
interface clause |
|
interface clause |
|
interface clause |
|
interface clause |
|
interface clause |
When a class has an abstract method or property, either declared directly or inherited, the underlying constructor for the class throws an exception. The method or property must be overridden with a override class in a subclass, and then the subclass can be instantiated (as long as it has no other abstract methods). A final class cannot have an abstract method or property.
A method or property can be both abstract and override. In that case, if the overridden method or property is not abstract, then the method or property becomes abstract and most be overridden in a subclass before instantiation. Even if the overidden method or property is already abstract, an abstract override can be useful to impose an additional result annotation.
expression |
class clause |
|
class clause |
|
interface clause |
|
interface clause |
When used as a namespace, id can access the immediate private fields, methods, and properties of the class or interface containing the internal declaration. Along similar lines, id as an annotation associates static information with an expression or binding so that . can be used to access private fields, methods and properties, but only with . as statically resolved.
class clause | ||||||
| ||||||
class clause | ||||||
| ||||||
class clause | ||||||
| ||||||
| ||||||
class clause | ||||||
| ||||||
class clause | ||||||
expression 'id pattern ...': 'template ...' | ||||||
| ||||||
class clause | ||||||
| ||||||
| ||||||
class clause | ||||||
| ||||||
class clause | ||||||
| ||||||
class clause | ||||||
| ||||||
| ||||||
class clause | ||||||
| ||||||
class clause | ||||||
annotation 'id pattern ...': 'template ...' | ||||||
| ||||||
class clause | ||||||
| ||||||
| ||||||
|
When a class has a constructor form with an empty maybe_name, then a use of new class’s id_name as a constructor function invokes a function the entry point (typically a fun form) in the block after constructor. That function must return an instance of the new class, typically by calling super:
If the new class does not have a superclass, then super accesses the default constructor, which returns an instance of the class. Note that this instance might be an instance of a subclass if the new class is not final.
If the new class has a superclass, then super accesses a curried function. The function accepts the same arguments as the superclass constructor. Instead of returning an instance of the class, it returns a function that accepts arguments as declared by field_specs in the new subclass, including private clause fields; the result of that function is an instance of the new class. Again, the result instance might be an instance of a subclass if the new class is not final.
If a constructor form has an id for maybe_name that is not the same as the enclosing class’s id_name, then the constructor is bound to id instead of id_name. Typically, naming a constructor is paired with an expression declaration that refers to that constructor.
If a class has an internal clause, then the bound name acts as a constructor like super, except that it always instantiates the class that contains the internal clause (so, the internal name is not a substitute for using super in a custom constructor). If a superclass has a custom constructor, the default constructor of a subclass assumes that the superclass constructor accepts the same argument as the default superclass constructor.
When a class has a expression form, then a use of the new class’s id_name as an expression invokes the entry point (typically a macro form) in the block after expression. The entry_point is a meta-time expression. This macro replaces the default meaning of the id_name as a reference to the constructor. When expression, then the default id_name.of annotation constructor accepts only predicate annotations.
When a class has a binding form, then a use of the new class’s id_name as a binding-pattern constructor invokes the entry point (typically a macro form) in the block after binding. The entry_point is a meta-time expression. There is no super for custom binding patterns; instead, use internal to bind an internal name that acts similar to the class’s default binding form, but with two differences: it does not expect bindings for superclass fields, but it does expect bindings for private fields declared with a field_spec. When a class has a superclass, then a custom binding form is typically implemented using an internal binding form, the superclass’s binding form, and the && binding operator. When a superclass has a custom binding form, then a class must have a custom binding form, too (unlike the case with constructors, where a default constructor still can be generated to call a custom superclass constructor).
When a class has an annotation form, then a use of new class’s id_name in a annotation invokes the entry point (typically a macro form) in the block after annotation. The entry_point is a meta-time expression. Similar to custom binding forms, a custom annotation form normally needs an internal annotation name bound with internal; the of form of that annotation expects annotations for only immediate fields of the class, but including private ones declared with field_specs. Use the && annotation operator to combine the internal annotation with a superclass annotation. When a superclass has a custom annotation form, then a class must have a custom annotation form, too. Typically, an of annotation is also defined and exported from the class’s namespace to go along with the customization of the class name as an annotation.
interface clause |
expression: expresssion_decl |
|
interface clause |
annotation: annotation_point |
There is no constructor for interfaces, since interfaces cannot be instantiated directly, but an expression clause can make an interface identifier behave like a constructor, perhaps instantiating some default class. There is no binding for interfaces, because interface does not otherwise define an interfeace name for binding, and so bind.macro can be used alongside interface with the same interface name.
class clause |
class clause |
A prefab class’s fields and methods are accessible from an instance expression followed by . only when the reference is statically resolved. That is, dynamic . lookup will fail to find a field or a method.
If a prefab class has a superclass, the superclass must also be prefab.
A prefab class is implicitly nonfinal, and it cannot be opaque or authentic.
When a field is declared with a field_spec that has a :: annotation, a field value is checked against the annotation only when an instance is created through a constructor from the class declaration, and not when the field is accessed. The field access is effectively treated as having a :~ annotation.
class clause |
The listed ids must all correspond to fields of the object that would be represented by a default constructor, independent of whether the field is optional or would be supplied with a keyword. The fields are checked dynamically, unless with is static (see use_static), in which case the set of fields must syntacticaly match the ones expected for the class indicated by static information.
An object is updated by calling its class’s reconstructor. A default reconstructor for a non-abstract class without a custom constructor is implemented by calling the class’s constructor. A class declaration can contain an reconstructor clause to replace the default implementation or supply an implementation when a default is not available.
class Posn(x, y)
class Posn(x, y):
class Posn3D(z):
extends Posn
// same as the default reconstructor:
reconstructor (x, y, z = this.z):
Posn3D(x, y, z)
class PosnX(x, y):
internal _PosnX
reconstructor (x, y):
class PosnD(x, y):
delta: 0
reconstructor (x, y, delta):
class clause | |||
| |||
class clause | |||
| |||
class clause | |||
|
By default, a class’s reconstructor should expect as many arguments as the class has fields, and it should expect them in the declared order. If the class has a reconstructor_fields declaration, the reconstructor should instead expect those fields in addition to the ones that the superclass (if any) expects for its reconstuctor.
A reconstructor should not expect keyword arguments; all fields are supplied by-position. If the class has a superclass, then any fields added by the class should be made optional, because those arguments will not be supplied when an instance of the class is updated based on static information corresponding to the superclass. The optional arguments typically have a default value that is drawn from the corresponding field of this.
See with for examples.
class clause | |||
|
The body sequence for a field is evaluated for a use of with that does not supply the field. The body sequence can refer to this or other bindings that would be available in a 0-argument method of the class.
When a class has a reconstructor_fields declaration, then the class and any subclass that extends it must have a reconstructor declaration, since there is not necessarily any connection between the declared reconstructor fields and the constructor’s arguments.
See with for an example.
class clause |
primitive_property expr: body; ... |
|
interface clause |
primitive_property expr: body; ... |
If the class is not final, then any subclass of the class by default implements the property with the same value, but it can specify the same property to produce a different value that replaces the one produced by body. Implementing an interface with implements, meanwhile, corresponds to writing primitive_property for each primitive property inthe interface and its superinterfaces, which means that those properties cannot be immediately overridden with primitive_property.
The expr and body are evaluated in order relative to surrounding expr and defn forms.
class NamedPosn(name, x, y):
primitive_property base.#{prop:object-name}:
> base.#{object-name}(NamedPosn("Rumpelstiltskin", 1, 2))
"Rumpelstiltskin"