trait.ss: Object-Oriented Traits

To load: (require (lib "trait.ss"))

A trait is a collection of methods that can be converted to a mixin and then applied to a class. Before a trait is converted to a mixin, the methods of a trait can be individually renamed, and multiple traits can be merged to form a new trait. The trait constructs provided by the trait.ss library work with the classes of the class.ss library (see section 6).

The trait form creates a new trait:

(trait trait-clause ···)

trait-clause is one of
  (public optionally-renamed-id ···)
  (pubment optionally-renamed-id ···)
  (public-final optionally-renamed-id ···)
  (override optionally-renamed-id ···)
  (overment optionally-renamed-id ···)
  (override-final optionally-renamed-id ···)
  (augment optionally-renamed-id ···)
  (augride optionally-renamed-id ···)
  (augment-final optionally-renamed-id ···)
  (inherit optionally-renamed-id ···)
  (inherit/super optionally-renamed-id ···)
  (inherit/inner optionally-renamed-id ···)
  method-definition
  (field field-declaration ···)
  (inherit-field optionally-renamed-id ···)

The body of a trait form is similar to the body of a class form, but restricted to non-private method definitions. In particular, the grammar of optionally-renamed-id, method-definition, and field-declaration are the same as for class (see section 6), and every method-definition must have a corresponding declaration (one of public, override, etc.). As in class, uses of method names in direct calls, super calls, and inner calls depend on bringing method names into scope via inherit, inherit/super, inherit/inner, and other method declarations in the same trait; an exception, compared to class is that overment binds a method name only in the corresponding method, and not in other methods of the same trait. Finally, macros such as public* and define/public work in trait as in class.

(trait->mixin trait) converts a trait to a mixin, which can be applied to a class to produce a new class. An expression of the form

(trait->mixin
 (trait
  trait-clause ···))

is equivalent to

(lambda (%)
 (class %
  trait-clause ···
  (super-new)))

Normally, however, a trait's methods are changed and combined with other traits before converting to a mixin.

(trait-sum trait ···1) produces a trait that combines all of the methods of the given traits. For example,

(define t1
 (trait
  (define/public (m1) 1)))
(define t2
 (trait
  (define/public (m2) 2)))
(define t3 (trait-sum t1 t2))

creates a trait t3 that is equivalent to

(trait
 (define/public (m1) 1)
 (define/public (m2) 2))

but t1 and t2 can still be used individually or combined with other traits.

When traits are combined with trait-sum, the combination drops inherit, inherit/super, inherit/inner, and inherit-field declarations when a definition is supplied for the same method or field name by another trait. The trait-sum operation fails (the exn:fail:contract exception is raised) if any of the traits to combine define a method or field with the same name, or if an inherit/super or inherit/inner declaration to be dropped is inconsistent with the supplied definition. In other words, declaring a method with inherit, inherit/super, or inherit/inner, does not count as defining the method; at the same time, for example, a trait that contains an inherit/super declaration for a method m cannot be combinaed with a trait that defines m as augment, since no class could satisfy the requirements of both augment and inherit/super when the trait is later converted to a mixin and applied to a class.

(trait-exclude trait-expr identifier) produces a new trait that is like the result of trait-expr, but with the definition of a method named by identifier removed; as the method definition is removed, either a inherit, inherit/super, or inherit/inner declaration is added:

If the trait produced by trait-expr has no method definition for identifier, the exn:fail:contract exception is raised.

(trait-exclude-field trait-expr identifier) produces a new trait that is like the result of trait-expr, but with the definition of a field named by identifier removed; as the field definition is removed, an inherit-field declaration is added.

(trait-alias trait-expr identifier new-identifier) produces a new trait that is like the result of trait-expr, but the definition and declaration of the method named by identifier is duplicated with the name new-identifier. The consistency requirements for the resulting trait are the same as for trait-sum, otherwise the exn:fail:contract exception is raised. This operation does not rename any other use of identifier, such as in method calls (even method calls to identifer in the cloned definition for new-identifier).

(trait-rename trait-expr identifier new-identifier) produces a new trait that is like the result of trait-expr, but all definitions and references to methods named identifier are replaced by definitions and references to methods named by new-identifier. The consistency requirements for the resulting trait is the same as for trait-sum, otherwise the exn:fail:contract exception is raised.

(trait-rename-field trait-expr identifier new-identifier) produces a new trait that is like the result of trait-expr, but all definitions and references to fields named identifier are replaced by definitions and references to fields named by new-identifier. The consistency requirements for the resulting trait is the same as for trait-sum, otherwise the exn:fail:contract exception is raised.

External identifiers in trait, trait-exclude, trait-exclude-field, trait-alias, trait-rename, and trait-rename-field forms are subject to binding via define-member-name and define-local-member-name (see section 6.3.3.3). Although private methods or fields are not allowed in a trait form, they can be simulated by using a public or field declaration and a name whose scope is limited to the trait form.