master
branch, virtual methods ¶There! I merged the ‘typed-language’ branch into the master
branch of
Vicare. I have also rotated the boot images, so it is now easier to catch
errors in the source code of the boot image.
Some work has gone into methods. With simple inheritance, overloaded functions, methods, overloaded methods, virtual methods, sealing methods, interfaces, mixins, the toolbox for object–oriented programming is almost complete. Still missing are recursive types (indispensable) and maybe public/private members for record–types.
Everything I discuss here is relative to code in the head of the master
branch.
An implementation of virtual methods for record–types is in the code; sealing methods are also there. Let’s just do a recapitulation of methods by a good copy and paste from the documentation, let’s go…
Methods are possibly overloaded typed functions whose syntactic binding resides in a namespace reserved to an object–type and that are accessible only through an instance of such object–type.
The syntactic bindings method
, virtual-method
and
seal-method
are used as clause keywords in uses of
define-record-type
to define methods and virtual methods for a record–type;
they can be used any number of times in the same define-record-type
macro
use.
The clause method
is also accepted by define-label-type
,
define-interface-type
and define-mixin-type
.
The clauses virtual-method
and seal-method
are also accepted by
define-mixin-type
.
We can think of method
as acting like define/typed
with regard to
the syntax of arguments to function; the first argument to a method is the record
itself, but it is implicit: we can access it using the fluid syntax this
.
For example, using the procedural coding style:
(define-record-type <duo> (strip-angular-parentheses) (fields one two) (method (sum-them) (+ (duo-one this) (duo-two this))) (method (mul-them) (* (duo-one this) (duo-two this)))) (define O (make-duo 1 2)) (method-call sum-them O) ⇒ 3
and using the object–oriented syntax style:
(define-record-type <duo> (fields one two) (method (sum-them) (+ (.one this) (.two this))) (method (mul-them) (* (.one this) (.two this)))) (define O (new <duo> 1 2)) (.sum-them O) ⇒ 3
The syntax method-call
searches for a record–type’s methods by using
eq?
to search for the method’s name, as symbol, in the record–type’s internal
table of methods (it does not use the syntactic identifiers with
free-identifier=?
).
The clause method
allows the definition of overloaded
functions, with multiple function specialisations associated to the same
method name. Example:
(define-record-type <alpha> (fields a b) (method ({doit <list>} {A <fixnum>}) (list (.a this) (.b this) 'fixnum A)) (method ({doit <list>} {A <symbol>}) (list (.a this) (.b this) 'symbol A)) (method ({doit <list>} {A <number>} {B <number>}) (list (.a this) (.b this) 'numbers A B))) (define O (new <alpha> 1 2)) (.doit O 123) ⇒ (1 2 fixnum 123) (.doit O 'ciao) ⇒ (1 2 symbol ciao) (.doit O 3 4) ⇒ (1 2 numbers 3 4)
we can think of the methods defined above as expanding to the following definitions:
(define/overload ({doit <list>} {subject <alpha>} {A <fixnum>}) (fluid-let-syntax ((this (identifier-syntax subject))) (list (.a this) (.b this) 'fixnum A))) (define/overload ({doit <list>} {subject <alpha>} {A <symbol>}) (fluid-let-syntax ((this (identifier-syntax subject))) (list (.a this) (.b this) 'symbol A))) (define/overload ({doit <list>} {subject <alpha>} {A <number>} {B <number>}) (fluid-let-syntax ((this (identifier-syntax subject))) (list (.a this) (.b this) 'numbers A B)))
where subject is a non–accessible identifier.
When methods are defined for record-types: method names cannot be equal
to field names; field accessors and mutators are accessible as methods
automatically, with a method name equal to the field name. The syntax
of method-call
is:
(method-call ?name ?subject-expr ?arg ...)
when there are no ?arg operand the syntax is compatible with a field accessor call; when there is a single ?arg operand the syntax is compatible with a field mutator call.
So we can access and mutate fields as follows:
(define-record-type <alpha> (fields (mutable a))) (define O (new <alpha> 1)) (.a O) ⇒ 1 (.a O 2) (.a O) ⇒ 2
When a hierarchy of record–types is defined: the sub–types can override the super–type’s concrete methods by defining methods with the same name. Example:
(define-record-type <super> (method (doit) 1)) (define-record-type <sub> (parent <super>) (method (doit) 2)) (define (super-fun {O <super>}) (.doit O)) (define (sub-fun {O <sub>}) (.doit O)) (define O (new <sub>)) (super-fun O) ⇒ 1 (sub-fun O) ⇒ 2
The method of the super–type is still accessible if we “see” the object instance through the super–type’s type specification.
The sub–type’s method is in no way limited by the super–type’s method: the two methods are allowed to have completely different type signature.
The clause virtual-method
allows the definition of virtual
methods associated to a record–type. Virtual methods work almost like
concrete methods, but the rules of overriding are different: with
virtual methods we request run–time dynamic dispatching, to have the
methods of the sub–types take precedence over the methods of the
super–types.
NOTE Virtual methods must have an implementation, there are no abstract methods.
Example:
(define-record-type <super> (virtual-method (doit) 1)) (define-record-type <sub> (parent <super>) (method (doit) 2)) (define (super-fun {O <super>}) (.doit O)) (define (sub-fun {O <sub>}) (.doit O)) (define O (new <sub>)) (super-fun O) ⇒ 2 (sub-fun O) ⇒ 2
we see that even though the function super-fun
accesses the
instance of <sub>
with the type specification <super>
, the
actually called method is the one defined by <sub>
.
The overriding method must have type signature being a sub–type of the overridden type signature. For example, the following definitions are fine:
(define-record-type <super> (fields value) (virtual-method ({doit <number>} {S <nestring>}) (.value this))) (define-record-type <sub> (parent <super>) (virtual-method ({doit <fixnum>} {S <string>}) (.value this)))
because:
(type-annotation-super-and-sub? (lambda (<nestring>) => (<number>)) (lambda (<string>) => (<fixnum>))) ⇒ #t
while the following definitions will cause a syntax violation exception at expand–time:
(define-record-type <super> (fields value) (virtual-method ({doit <number>} {S <nestring>}) (.value this))) (define-record-type <sub> (parent <super>) (virtual-method ({doit <fixnum>} {S <vector>}) (.value this)))
because:
(type-annotation-super-and-sub? (lambda (<nestring>) => (<number>)) (lambda (<string>) => (<vector>))) ⇒ #f
Once a method has been defined as virtual in a super–type, it does not
matter if the sub–type makes use of method
,
virtual-method
or seal-method
: all these clauses will
override the super–type’s method if they use the same name. Example:
(define-record-type <super> (virtual-method (darkness) 1) (virtual-method (light) 2)) (define-record-type <sub> (parent <super>) (method (darkness) 11) (virtual-method (light) 22)) (define O (new <sub>)) (define {P <super>} O) (.darkness P) ⇒ 11 (.light P) ⇒ 22
Notice that it is not possible to define a sub–type having a field with the same name of a super–type’s virtual method; the following definitions will cause an expand–time exception:
(define-record-type <super> (method (doit) 1)) (define-record-type <sub> (parent <super>) (fields doit)) error→ &syntax
The clause seal-method
seals a method name so that the
sub–types can no more use it; it does not matter if the super–types
actually use it or not.
It happens that we want to define virtual methods in a super–type, override them in a sub–type and then forbid further overriding in the sub–types of the sub-type. This is possible with sealing methods.
The clause seal-method
allows the definition of methods
associated to a record–type that might override the super–type’s
methods, but that forbid the sub–types from overriding them. For
everything else: sealing methods work like concrete methods.
In the following example everything works as usual, with <sub>
overriding the implementation of doit
in <super>
:
(define-record-type <super> (virtual-method (doit) 1)) (define-record-type <sub> (parent <super>) (seal-method (doit) 2)) (define (super-fun {O <super>}) (.doit O)) (define (sub-fun {O <sub>}) (.doit O)) (define O (new <sub>)) (super-fun O) ⇒ 2 (sub-fun O) ⇒ 2
but adding the following definition will cause an expand–time
exception, because sub–types of <sub>
are forbidden from having a
method named doit
:
(define-record-type <sub-sub> (parent <sub>) (method (doit) 3)) error→ &syntax
the following definition will also cause an expand–time exception,
because sub–types of <sub>
are forbidden from having a field
named doit
:
(define-record-type <sub-sub> (parent <sub>) (fields doit)) error→ &syntax