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-keywordoption-value]*)
method-decl::= ((method-nameformal-arguments) [::rtype] [option-keywordoption-value]*body)
Defines a new class named
name. Ifdefine-simple-classis used, creates a normal Java class namednamein the current package. (Ifnamehas the form<xyx>the Java implementation type is namedxyz.) Fordefine-classthe 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-classis 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-classis used, all of the classes listed insupersshould be interfaces or classes defined usingdefine-class.Each
field-decldeclares a instance "slot" (field) with the givenfname. By default it is publicly visible, but you can specify a different visiblity with theaccess:specifier. Ifftypeis specified it is the type of the slot. The followingoption-keywords are implemented:
type:ftypeSpecifies that
ftypeis the type of (the values of) the field. Equivalent to ‘::’.ftypeallocation:kindIf
kindis'classor'statica 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 astaticfield.If
kindis'instancethen each instance has a separate value "slot", and they are not shared. In Java terms, this is a non-staticfield. 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:kindSpecifies 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:exprAn 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:exprAn expression used to initialize the slot. The lexical environment of the
expris that of thedefine-class; it does not include the field and method names of the current class. ordefine-simple-class.init-value:valueA 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
makecalls. For now, this is ignored, andnameshould be the same as the field'sfname.The
fnamecan be left out. That indicates a "dummy slot", which is useful for initialization not tied to a specific field. In this example,xis 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-decldeclares method, which is by default public and non-static, and whose name ismethod-name. (Ifmethod-nameis 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-argumentscannot contain optional, rest, or keyword parameters. (The plan is to allow optional parameters, implemented using multiple overloaded methods.)A
method-declin adefine-simple-classcan have the followingoption-keywords:
access:kindSpecifies the Java access permission on the method. Can be one of
'private,'protected,'public, or'package.allocation:kindIf
kindis'classor'staticcreates a static method.throws:(exception-class-name... )Specifies a list of checked exception that the method may throw. Equivalent to a
throwsspecification 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-classonly). 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-specialspecial 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
bodyof a method includes thefield-decls 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.