Kawa provides various mechanisms for defining new classes.
The define-class
and define-simple-class
forms
will usually be the preferred mechanisms. They have basically
the same syntax, but have a couple of differences.
define-class
allows multiple inheritance as well as true nested
(first-class) class objects. However, the implementation
is more complex: code using it is slightly slower, and the mapping to
Java classes is a little less obvious. (Each Scheme class is implemented
as a pair of an interface and an implementation class.)
A class defined by define-simple-class
is slightly more
efficient, and it is easier to access it from Java code.
The syntax of define-class
are mostly compatible with that
in the Guile and Stk dialects of Scheme.
Syntax: define-class
name
(supers
...
) field-or-method-decl
...
Syntax: define-simple-class
name
(supers
...
) field-or-method-decl
...
field-or-method
::=field-decl
|method-decl
field-decl
::= (fname
[::ftype
] [option-keyword
option-value
]*)
method-decl
::= ((method-name
formal-arguments
) [::rtype
] [option-keyword
option-value
]*body
)
Defines a new class named
name
. Ifdefine-simple-class
is used, creates a normal Java class namedname
in the current package. (Ifname
has the form<xyx>
the Java implementation type is namedxyz
.) Fordefine-class
the implementation is unspecified. In most cases, the compiler creates a class pair, consisting of a Java interface and a Java implementation class.The class inherits from the classes and interfaces listed in
supers
. This is a list of names of classes that are in scope (perhaps imported usingrequire
), or names for existing classes or interfaces surrounded by<>
, such as<gnu.lists.Sequence>
. Ifdefine-simple-class
is used, at most one of these may be the name of a normal Java class or classes defined usingdefine-simple-class
; the rest must be interfaces or classes defined usingdefine-class
. Ifdefine-class
is used, all of the classes listed insupers
should be interfaces or classes defined usingdefine-class
.Each
field-decl
declares a instance "slot" (field) with the givenfname
. By default it is publicly visible, but you can specify a different visiblity with theaccess:
specifier. Ifftype
is specified it is the type of the slot. The followingoption-keyword
s are implemented:
type:
ftype
Specifies that
ftype
is the type of (the values of) the field. Equivalent to ‘::
’.ftype
allocation:
kind
If
kind
is'class
or'static
a single slot is shared between all instances of the class (and its sub-classes). Not yet implemented fordefine-class
, only fordefine-simple-class
. In Java terms this is astatic
field.If
kind
is'instance
then each instance has a separate value "slot", and they are not shared. In Java terms, this is a non-static
field. This is the default.You can use a keyword like
class:
or a string like"class"
if you prefer instead of a quoted symbol like'class
; the latter is recommended.access:
kind
Specifies the Java access permission on the field. Can be one of
'private
,'protected
,'public
(which is the default in Kawa), or'package
(which the default "unnamed" permission in Java code).init:
expr
An expression used to initialize the slot. The expression is evaluated in a scope that includes the field and method names of the current class.
init-form:
expr
An expression used to initialize the slot. The lexical environment of the
expr
is that of thedefine-class
; it does not include the field and method names of the current class. ordefine-simple-class
.init-value:
value
A value expression used to initialize the slot. For now this is synonymous with
init-form:
, but that may change (depending on what other implementation do), so to be safe only useinit-value:
with a literal.init-keyword:
name
:A keyword that that can be used to initialize instance in
make
calls. For now, this is ignored, andname
should be the same as the field'sfname
.The
fname
can be left out. That indicates a "dummy slot", which is useful for initialization not tied to a specific field. In this example,x
is the only actual field. It is first initialized to 10, but if(some-condition)
is true then its value is doubled.(define-simple-class <my-class> () (allocation: 'class init: (perform-actions-when-the-class-is-initizalized)) (x init: 10) (init: (if (some-condition) (set! x (* x 2)))))Each
method-decl
declares method, which is by default public and non-static, and whose name ismethod-name
. (Ifmethod-name
is not a valid Java method name, it is mapped to something reasonable. For examplefoo-bar?
is mapped toisFooBar
.) The types of the method arguments can be specified in theformal-arguments
. The return type can be specified byrtype
, or is otherwise the type of thebody
. Currently, theformal-arguments
cannot contain optional, rest, or keyword parameters. (The plan is to allow optional parameters, implemented using multiple overloaded methods.)A
method-decl
in adefine-simple-class
can have the followingoption-keyword
s:
access:
kind
Specifies the Java access permission on the method. Can be one of
'private
,'protected
,'public
, or'package
.allocation:
kind
If
kind
is'class
or'static
creates a static method.throws:
(exception-class-name
... )Specifies a list of checked exception that the method may throw. Equivalent to a
throws
specification in Java code. For example:(define-simple-class <T> (prefix) ((lookup name) throws: (<java.io.FileNotFoundException>) (make <java.io.FileReader> (string-append prefix name))))The special
method-name
‘*init*
’ can be used to name a non-default constructor (in adefine-simple-class
only). It can be used to initialize a freshly-allocated instance using passed-in parameters. You can call a superclass or a sibling constructor using theinvoke-special
special function. (This is general but a admittedly a bit verbose; a more compact form will be added in the future.) See the example below.The scope of the
body
of a method includes thefield-decl
s of the object. It does include the surrounding lexical scope. It sort-of also includes the declared methods, but this is not working yet.
In the following example we define a simple class <2d-vector>
and a class <3d-vector>
that extends it. (This is for illustration
only - defining 3-dimensional points as an extension
of 2-dimensional points does not really make sense.)
(define-simple-class <2d-vector> () (x type: <double> init-keyword: x:) (y type: <double> init-keyword: y:) (zero-2d :: <2d-vector> allocation: 'static init-value: (make <2d-vector> 0)) ;; An object initializer (constructor) method. ((*init* (x0 :: <double>) (y0 :: <double>)) (set! x x0) (set! y y0)) ((*init* (xy0 :: <double>)) ;; Call above 2-argument constructor. (invoke-special <2d-vector> (this) '*init* xy0 xy0)) ;; Need a default constructor as well, for the (make ..) below. ((*init*) #!void) ((add (other :: <2d-vector>)) :: <2d-vector> ;; Kawa compiles this using primitive Java types! (make <2d-vector> x: (+ x (slot-ref other 'x)) y: (+ y (slot-ref other 'y)))) ((scale (factor :: <double>)) :: <2d-vector> (make <2d-vector> x: (* factor x) y: (* factor y)))) (define-simple-class <3d-vector> (<2d-vector>) (z type: <double> init-value: 0.0 init-keyword: z:) ;; A constructor which calls the superclass constructor. ((*init* (x0 :: <double>) (y0 :: <double>) (z0 :: <double>)) (invoke-special <2d-vector> (this) '*init* x0 y0) (set! z z0)) ;; Need a default constructor for the (make ..) below. ((*init*) #!void) ((scale (factor :: <double>)) :: <2d-vector> ;; Note we cannot override the return type to <3d-vector> ;; because Java does not allow that. Should hide that. . (make <3d-vector> ;; Unfortunately, slot names of inherited classes are not visible. ;; Until this is fixed, use slot-ref. x: (* factor (slot-ref (this) 'x)) y: (* factor (*:.y (this))) ;; Alternative syntax. z: (* factor z))))
Note we define both explicit non-default constructor methods,
and we associate fields with keywords, so they can be named
in a make
call. The latter requires a default constructor,
and since having non-default constructors suppresses
the implicit default constructor we have to explicitly define it.
Using both styles of constructors is rather redundant, though.