Kawa provides various ways to define, create, and access Java objects. Here are the currently supported features.
The Kawa module system is based on the features of the Java class system.
Returns the "this object" - the current instance of the current class. The current implementation is incomplete, not robust, and not well defined. However, it will have to do for now. Note: "
this" is a macro, not a variable, so you have to write it using parentheses: ‘(this)’. A planned extension will allow an optional class specifier (needed for nested clases).
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.
define-class class-name (supers ... ()annotation|)option-pair* field-or-method-decl ...
define-simple-class class-name (supers ... ()annotation|)option-pair* field-or-method-decl ...
Defines a new class named
class-name. Ifdefine-simple-classis used, creates a normal Java class namedclass-namein the current package. (Ifclass-namehas the form<xyz>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.
class-name ::= identifier
option-pair ::= option-keyword option-value
field-or-method-decl ::= field-decl | method-decl
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
using require), or names for existing classes or interfaces
optionally surrounded by <>, such as <gnu.lists.Sequence>.
If define-simple-class is used, at most one of these may be
the name of a normal Java class or classes defined using
define-simple-class; the rest must be interfaces or classes
defined using define-class.
If define-class is used, all of the classes listed
in supers should be interfaces or classes defined using
define-class.
-
interface:make-interface Specifies whether Kawa generates a Java class, interface, or both. If
make-interfaceis#t, then a Java interface is generated. In that case all the supertypes must be interfaces, and all the declared methods must be abstract. Ifmake-interfaceis#f, then a Java class is generated. Ifinterface:is unspecified, the default is#ffordefine-simple-class. Fordefine-classthe default is to generate an interface, and in addition (if needed) a helper class that implements the interface. (In that case any non-abstract methods are compiled to static methods. The methods that implement the interface are just wrapper methods that call the real static methods. This allows Kawa to implement true multiple inheritance.)-
access:kind -
Specifies the Java access permission on the class. Can be one of
'public(which is the default in Kawa),'package(which the default "unnamed" permission in Java code),'protected,'private,'volatile, or'transient. Can also be used to specifyfinal,abstract, orenum, as in Java. (You don't need to explicitly specify the class isabstractif anymethod-bodyis#!abstract, or you specifyinterface: #t.) Thekindcan also be a list, as for example:access: '(protected volatile)
-
class-name:"cname" Specifies the Java name of the created class. The
namespecified afterdefine-classordefine-simple-classis the Scheme name, i.e. the name of a Scheme variable that is bound to the class. The Java name is by default derived from the Scheme name, but you can override the default with aclass-name:specifier. If thecnamehas no periods, then it is a name in the package of the main (module) class. If thecnamestarts with a period, then you get a class nested within the module class. In this case the actual class name ismoduleClass$rname, wherernameiscnamewithout the initial period. To force a class in the top-level (unnamed) package (something not recommended) write a period at the end of thecname.
field-decl ::= (field-name (annotation | opt-type-specifier | field-option)*)
field-name ::= identifier
field-option ::= keyword expression
As a matter of style the following order is suggested, though this not enforced:
Each field-decl declares a instance "slot" (field)
with the given field-name.
By default it is publicly visible, but you can specify
a different visiblity with the access: specifier.
The following field-option keywords are implemented:
-
type:type Specifies that
typeis the type of (the values of) the field. Equivalent to ‘::’.type-
allocation:kind -
If
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. -
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). Can also be used to specifyvolatile,transient,enum, orfinal, as in Java.-
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
expris 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
makecalls. For now, this is ignored, andnameshould be the same as the field'sfield-name.
The field-name can be left out. That indicates a "dummy slot",
which is useful for initialization not tied to a specific field.
In Java terms this is an instance or static initializer, i.e., a
block of code executed when a new instance is created or the class is loaded.
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)))))
method-decl ::= ((method-name formal-arguments)
(annotation | opt-return-type | option-pair)* [deprecated-return-specifier] method-body)
method-name ::= identifier
method-body ::= body | #!abstract | #!native
deprecated-return-specifier ::= identifier
Each method-decl declares a method,
which is by default public and non-static, and whose name is method-name.
(If method-name is not a valid
Java method name, it is mapped to something reasonable.
For example foo-bar? is mapped to isFooBar.)
The types of the method arguments can be specified in the
formal-arguments. The return type can be specified by
a opt-return-type, deprecated-return-specifier,
or is otherwise the type of the body.
Currently, the formal-arguments cannot contain optional, rest,
or keyword parameters. (The plan is to allow optional parameters,
implemented using multiple overloaded methods.)
A method-decl in a define-simple-class
can have the following option-keywords:
-
access:kind Specifies the Java access permission on the method. Can be one of
'private,'protected,'public, or'package.-
allocation:kind If
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 scope of the body of a method includes the field-decls
and field-decls of the body, including those inherited from
superclasses and implemented interfaces.
If the method-body is the special form #!abstract,
then the method is abstract. This means the method must
be overridden in a subclass, and you're not allowed to
create an instance of the enclosing class.
(define-simple-class Searchable () interface: #t ((search value) :: boolean #!abstract))
If the method-body is the special form #!native,
then the method is native, implemented using JNI.
The special method-name ‘*init*’ can be used to name
a non-default constructor (only if make-interface discussed above
is #f).
It can be used to initialize a freshly-allocated instance
using passed-in parameters.
You can call a superclass or a sibling constructor using
the invoke-special special function.
(This is general but admittedly a bit verbose; a more compact
form may be added in the future.)
See the example below.
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 ::double init-keyword: x:)
;; Alternative type-specification syntax.
(y type: double init-keyword: y:)
(zero-2d :: 2d-vector allocation: 'static
init-value: (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.
((*init*) #!void)
((add (other ::2d-vector)) ::2d-vector
;; Kawa compiles this using primitive Java types!
(2d-vector
x: (+ x other:x)
y: (+ y other:y)))
((scale (factor ::double)) ::2d-vector
(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.
((*init*) #!void)
((scale (factor ::double)) ::2d-vector
;; Note we cannot override the return type to 3d-vector
;; because Kawa doesn't yet support covariant return types.
(3d-vector
x: (* factor x)
y: (* factor (this):y) ;; 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 when allocating an object. Using keywords 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.
Kawa doesn't directly support marking a method as synchronized,
but you can get the same effect using a synchronized expression:
(define-simple-class <Bar> () ;; non-static method ((foo) :: void (synchronized (this) (synchronized-block))) ;; static method ((baz) allocation: 'static :: void (synchronized <Bar> (synchronized-block))))
object (supers ...) field-or-method-decl ...
Returns a new instance of an anonymous (inner) class. The syntax is similar to
define-class.
object-field-or-method-decl::=object-field-decl|method-decl
object-field-decl::=(field-name(annotation|opt-type-specifier|field-option)* [object-init])
object-init::=expression
Returns a new instance of a unique (anonymous) class. The class inherits from the list of
supers, where at most one of the elements should be the base class being extended from, and the rest are interfaces.This is roughly equivalent to:
(begin (define-simple-classhname(supers...)field-or-method-decl...) (makehname))A
field-declis as fordefine-class, except that we also allow an abbreviated syntax. Eachfield-decldeclares a public instance field. Ifobject-finitis given, it is an expression whose value becomes the initial value of the field. Theobject-initis evaluated at the same time as theobjectexpression is evaluated, in a scope where all thefield-names are visible.A
method-declis as fordefine-class.
An anonymous class is commonly used in the Java platform where a
function language would use a lambda expression.
Examples are call-back handlers, events handlers, and run methods.
In these cases Kawa lets you use a lambda expression as a short-hand
for an anonymous class. For example:
(button:addActionListener (lambda (e) (do-something)))
is equivalent to:
(button:addActionListener
(object (java.awt.event.ActionListener)
((actionPerformed (e ::java.awt.event.ActionEvent))::void
(do-something))))
This is possible when the required type is an interface or abstract class with a Single (exactly one) Abstract Methods. Such a class is sometypes called a SAM-type, and the conversion from a lambda expression to an anonymous class is sometimes called SAM-conversion.
Note that Kawa can also infer the parameter and return types of a method that overrides a method in a super-class.
An enumeration type is a set of named atomic enumeration values
that are distinct from other values. You define the type
using define-enum, and you reference enumeration values
using colon notation:
(define-enum colors (red blue green)) (define favorite-color colors:green)
Displaying an enum just prints the enum name,
but readable output using write (or the ~s format
specifier) prepends the type name:
(format "~a" favorite-color) ⇒ "green" (format "~s" favorite-color) ⇒ "colors:green"
The static values method returns a Java array of the enumeration
values, in declaration order, while ordinal yields the index
of an enumeration value:
(colors:values) ⇒ [red blue green] ((colors:values) 1) ⇒ blue (favorite-color:ordinal) ⇒ 2
If you invoke the enumeration type as a function,
it will map the name (as a string) to the corresponding value.
(This uses the valueOf method.)
(colors "red") ⇒ red (colors "RED") ⇒ throws IllegalArgumentException (eq? favorite-color (colors:valueOf "green")) ⇒ #t
Kawa enumerations are based on Java enumerations.
Thus the above is similar to a Java5 enum declaration,
and the type colors above extends java.lang.Enum.
define-enum enum-type-name option-pair... (enum-value-name ...)field-or-method-decl...
This declares a new enumeration type
enum-type-name, whose enumerations values are theenum-value-namelist. You can specify extra options and members usingoption-pairandfield-or-method-decl, which are as indefine-simple-class. (Thedefine-enumsyntax is similar to adefine-simple-classthat extendsjava.lang.Enum.)
(Note that R6RS has a separate Enumerations library (rnrs enum).
Unfortunately, this is not compatible with standard Java enums.
R6RS enums are simple symbols, which means you cannot distinguish
two enum values from different enumeration types if they have the
same value, nor from a vanilla symbol. That makes them less useful.)
The Java platform lets you associate with each declaration zero or more
annotations.
They provide an extensible mechanism to associate properties
with declarations.
Kawa support for annotations is not complete (the most important
functionality missing is being able to declare annotation types),
but is fairly functional.
Here is a simple example illustrating use of
JAXB annotations:
an XmlRootElement annotation on a class,
and an XmlElement annotation on a field:
(define-alias XmlRootElement javax.xml.bind.annotation.XmlRootElement) (define-alias XmlElement javax.xml.bind.annotation.XmlElement) (define-simple-class Bib ( ) (@XmlRootElement name: "bib") (books (@XmlElement name: "book" type: Book) ::java.util.ArrayList)) (define-simple-class Book () ...)
This tutorial explains the JAXB example in depth.
Here is the syntax:
annotation ::= (@annotation-typename annotations-element-values)
annotations-element-values ::= annotation-element-value
| annotation-element-pair ...
annotation-element-pair ::= keyword annotation-element-value
annotation-element-value ::= expression
annotation-typename ::= expression
An annotations-element-values consisting of just
a single annotation-element-value is equivalent to an
annotation-element-pair with a value: keyword.
Each keyword must correspond to the name of
an element (a zero-argument method) in the annotation type.
The corresponding annotation-element-value must be compatible with the
element type (return type of the method) of the annotation type.
Allowed element types are of the following kinds:
Primitive types, where the
annotation-element-valuemust be number or boolean coercible to the element type.Strings, where the
annotation-element-valueis normally a string literal.Classes, where the
annotation-element-valueis normally a classname.Enumeration types. The value usually has the form
.ClassName:enumFieldnameNested annotation types, where the
annotation-element-valuemust be a compatibleannotationvalue.An array of one of the allowable types. An array constructor expression works, but using the square bracket syntax is recommended.
Annotations are usually used in declarations, where they are required to be “constant-folded” to compile-time constant annotation values. This is so they can be written to class files. However, in other contexts an annotation can be used as an expression with general sub-expressions evaluated at run-time:
(define bk-name "book") (define be (@XmlElement name: bk-name type: Book)) (be:name) ⇒ "book"
(This may have limited usefulness: There are some bugs, including lack of support for default values for annotation elements. These bugs can be fixed if someone reports a need for runtime construction of annotation values.)
A module is a set of definitions that the module exports, as well as some actions (expressions evaluated for their side effect). The top-level forms in a Scheme source file compile a module; the source file is the module source. When Kawa compiles the module source, the result is the module class. Each exported definition is translated to a public field in the module class.
You can declare a class using define-simple-class
with the same name as the module class, for example the
following in a file named foo.scm:
(define-simple-class foo ...)
In this case the defined class will serve dual-purpose as the module class.
The definitions that a module exports are accessible to other modules.
These are the "public" definitions, to use Java terminology.
By default, all the identifiers declared at the top-level of a module
are exported, except those defined using define-private.
(If compiling with the --main flag,
then by default no identifiers are exported.)
However, a major purpose of using modules is to control the set of
names exported. One reason is to reduce the chance of accidental
name conflicts between separately developed modules. An even more
important reason is to enforce an interface: Client modules should
only use the names that are part of a documented interface, and should
not use internal implementation procedures (since those may change).
If there is a module-export (or export)
declaration in the module, then only those names listed are exported.
There can be more than one module-export, and they can be
anywhere in the Scheme file. The recommended style has
a single module-export near the beginning of the file.
module-export export-spec^*
export export-spec^*
The forms
exportandmodule-exportare equivalent. (The older Kawa name ismodule-export;exportcomes from R7RS.) Either form specifies a list of identifiers which can be made visible to other libraries or programs.
export-spec::=identifier
|(renameidentifier_1identifier_2)
In the former variant, an
identifiernames a single binding defined within or imported into the library, where the external name for the export is the same as the name of the binding within the library. Arenamespec exports the binding defined within or imported into the library and named byidentifier_1, usingidentifier_2 as the external name.Note that it is an error if there is no definition for
identifier(oridentifier_1) in the current module, or if it is defined usingdefine-private.
In this module, fact is public and worker is private:
(module-export fact) (define (worker x) ...) (define (fact x) ...)
Alternatively, you can write:
(define-private (worker x) ...) (define (fact x) ...)
If you want to just use a Scheme module as a module (i.e. load
or require it), you don't care how it gets translated
into a module class. However, Kawa gives you some control over how this
is done, and you can use a Scheme module to define a class which
you can use with other Java classes. This style of class definition
is an alternative to define-class,
which lets you define classes and instances fairly conveniently.
The default name of the module class is the main part of the
filename of the Scheme source file (with directories and extensions
sripped off). That can be overridden by the -T Kawa
command-line flag. The package-prefix specified by the -P
flag is prepended to give the fully-qualified class name.
Sets the name of the generated class, overriding the default. If there is no ‘
.’ in thename, the package-prefix (specified by the-PKawa command-line flag) is prepended.
By default, the base class of the generated module class is unspecified;
you cannot count on it being more specific than Object.
However, you can override it with module-extends.
Specifies that the class generated from the immediately surrounding module should extend (be a sub-class of) the class
<.class>
module-implements interface ...
Specifies that the class generated from the immediately surrounding module should implement the interfaces listed.
Note that the compiler does not currently check that all the abstract methods requires by the base class or implemented interfaces are actually provided, and have the correct signatures. This will hopefully be fixed, but for now, if you are forgot a method, you will probably get a verifier error
For each top-level exported definition the compiler creates a
corresponding public field with a similar (mangled) name.
By default, there is some indirection: The value of the Scheme variable
is not that of the field itself. Instead, the field is a
gnu.mapping.Symbol object, and the value Scheme variable is
defined to be the value stored in the Symbol.
Howewer, if you specify an explicit type, then the field will
have the specified type, instead of being a Symbol.
The indirection using Symbol is also avoided if you use
define-constant.
If the Scheme definition defines a procedure (which is not re-assigned
in the module), then the compiler assumes the variable as bound as a
constant procedure. The compiler generates one or more methods
corresponding to the body of the Scheme procedure. It also generates
a public field with the same name; the value of the field is an
instance of a subclass of <gnu.mapping.Procedure> which when
applied will execute the correct method (depending on the actual arguments).
The field is used when the procedure used as a value (such as being passed
as an argument to map), but when the compiler is able to do so,
it will generate code to call the correct method directly.
You can control the signature of the generated method by declaring
the parameter types and the return type of the method. See the
applet (see the section called “Compiling to an applet”) example for how this can be done.
If the procedures has optional parameters, then the compiler will
generate multiple methods, one for each argument list length.
(In rare cases the default expression may be such that this is
not possible, in which case an "variable argument list" method
is generated instead. This only happens when there is a nested
scope inside the default expression, which is very contrived.)
If there are #!keyword or #!rest arguments, the compiler
generate a "variable argument list" method. This is a method whose
last parameter is either an array or a <list>, and whose
name has $V appended to indicate the last parameter is a list.
Top-leval macros (defined using either define-syntax
or defmacro) create a field whose type is currently a sub-class of
kawa.lang.Syntax; this allows importing modules to detect
that the field is a macro and apply the macro at compile time.
Unfortunately, the Java class verifier does not allow fields to have
arbitrary names. Therefore, the name of a field that represents a
Scheme variable is "mangled" (see the section called “Mapping Scheme names to Java names”) into an acceptable Java name.
The implementation can recover the original name of a field X
as ((gnu.mapping.Named) X).getName() because all the standard
compiler-generate field types implemented the Named interface.
There are two kinds of module class: A static module is a class (or gets compiled to a class) all of whose public fields a static, and that does not have a public constructor. A JVM can only have a single global instance of a static module. An instance module has a public default constructor, and usually has at least one non-static public field. There can be multiple instances of an instance module; each instance is called a module instance. However, only a single instance of a module can be registered in an environment, so in most cases there is only a single instance of instance modules. Registering an instance in an environment means creating a binding mapping a magic name (derived from the class name) to the instance.
In fact, any Java class class that has the properties of either an instance module or a static module, is a module, and can be loaded or imported as such; the class need not have written using Scheme.
You can control whether a module is compiled to a static or
a non-static class using either a command-line flag to the compiler,
or using the module-static special form.
--module-staticIf no
module-staticis specified, generate a static module (as if(module-static #t)were specified). This is (now) the default.--module-nonstatic--no-module-staticIf no
module-staticis specified, generate a non-static module (as if(module-static #f)were specified). This used to be the default.--module-static-runIf no
module-staticis specified, generate a static module (as if(module-static 'init-run)were specified).
Control whether the generated fields and methods are static. If
#tor'init-runis specified, then the module will be a static module, all definitions will be static. If'init-runis specified, in addition the module body is evaluated in the class's static initializer. (Otherwise, it is run the first time it isrequire'd.) Otherwise, the module is an instance module. However, thenames that are explicitly listed will be compiled to static fields and methods. If#fis specified, then all exported names will be compiled to non-static (instance) fields and methods.By default, if no
module-staticis specified:
If there is a
module-extendsormodule-implementsdeclaration, or one of the--appletor--servletcommand-line flags was specified, then(module-static #f)is implied.If one of the command-line flags
--no-module-static,--module-nonstatic,--module-static, or--module-static-runwas specified, then the default is#f,#f,#t, or'init-run, respectively.Otherwise the default is
(module-static #t). (It used to be(module-static #f)in older Kawa versions.)Note
(module-static #t)usually produces more efficient code, and is recommended if a module contains only procedure or macro definitions. (This may become the default.) However, a static module means that all environments in a JVM share the same bindings, which you may not want if you use multiple top-level environments.
The top-level actions of a module will get compiled to a run
method. If there is an explicit method-extends, then the
module class will also automatically implement java.lang.Runnable.
(Otherwise, the class does not implement Runnable, since in that
case the run method return an Object rather than void.
This will likely change.)
Certain compilation options can be be specified either on the command-line when compiling, or in the module itself.
module-compile-options [key: value] ...
This sets the value of the
keyoption tovaluefor the current module (source file). It takes effect as soon it is seen during the first macro-expansion pass, and is active thereafter (unless overridden bywith-compile-options).The
key:is one of the supported option names (The ending colon makes it a Kawa keyword). Valid option keys are:
main:- Generate an application, with a main method.
full-tailcalls:- Use a calling convention that supports proper tail recursion.
warn-undefined-variable:- Warn if no compiler-visible binding for a variable.
warn-unknown-member:- Warn if referencing an unknown method or field.
warn-invoke-unknown-method:- Warn if invoke calls an unknown method (subsumed by warn-unknown-member).
warn-unused:- Warn if a variable is usused or code never executed.
warn-unreachable:- Warn if this code can never be executed.
warn-void-used:- Warn if an expression depends on the value of a void sub-expression (one that never returns a value).
warn-as-error:- Treat a compilation warning as if it were an error.The
valuemust be a literal value: either a boolean (#tor#f), a number, or a string, depending on thekey. (All the options so far are boolean options.)(module-compile-options warn-undefined-variable: #t) ;; This causes a warning message that y is unknown. (define (func x) (list x y))
with-compile-options [key: value] ... body
Similar to
module-compile-options, but the option is only active withinbody.The module option key
main:has no effect when applied to a particular body via thewith-compile-optionssyntax.(define (func x) (with-compile-options warn-invoke-unknown-method: #f (invoke x 'size)))
You can import a module into the current namespace with require.
require classname ["sourcepath]"
Search for a matching module (class), and add the names exported by that module to the current set of visible names. Normally, the module is specified using
classname. The module can be static module (all public fields must be static), or an instance module (it has a public default constructor).If the module is a instance module and if no module instance for that class has been registered in the current environment, then a new instance is created and registered (using a "magic" identifier). If the module class either inherits from
gnu.expr.ModuleBodyor implementsjava.lang.Runnablethen the correspondingrunmethod is executed. (This is done after the instance is registered so that cycles can be handled.) These actions (creating, registering, and running the module instance) are done both at compile time and at run time, if necessary.All the public fields of the module class are then incorporated in the current set of local visible names in the current module. (This is for both instance and static modules.) This is done at compile time - no new bindings are created at run-time (except for the magic binding used to register the module instance), and the imported bindings are private to the current module. References to the imported bindings will be compiled as field references, using the module instance (except for static fields).
If a
is specified then that is used to locate the source file for the module, and if necessary, compile it."sourcepath"If a
'is specified then thefeaturenamefeaturenameis looked up (at compile time) in the "feature table" which yields the implementingclassname.
import import-set^*
Similar functionality as
require, but specified by R6RS.
import-set::=library-reference
|(librarylibrary-reference)
|(onlyimport-setidentifier^*)
|(exceptimport-setidentifier^*)
|(prefiximport-setidentifier)
|(renameimport-set(identifier1identifier2)^*)
library-reference::=(identifier^+)
A
library-referenceis mapped to a class name by concatenating all the identifiers, separated by dots. For example:(import (gnu kawa slib srfi37))is equivalent to:
(require gnu.kawa.slib.srfi37)By default, all of an imported library's exported bindings are made visible within an importing library using the names given to the bindings by the imported library. The precise set of bindings to be imported and the names of those bindings can be adjusted with the
only,except,prefix, andrenameforms as described below.
An
onlyform produces a subset of the bindings from anotherimport-set, including only the listedidentifiers. The includedidentifiers must be in the originalimport-set.An
exceptform produces a subset of the bindings from anotherimport-set, including all but the listedidentifiers. All of the excludedidentifiers must be in the originalimport-set.A
prefixform adds theidentifierprefix to each name from anotherimport-set.A
renameform:(rename (identifier1identifier2) …)removes the bindings for
to form an intermediateidentifier1…import-set, then adds the bindings back for the correspondingto form the finalidentifier2…import-set. Eachidentifier1must be in the originalimport-set, eachidentifier2must not be in the intermediateimport-set, and theidentifier2s must be distinct.
Declare that
'is available. A followingfeaturenamecond-expandin this scope will matchfeaturename.
Using require and provide with featurenames is
similar to the same-named macros in SLib, Emacs, and Common Lisp.
However, in Kawa these are not functions, but instead they
are syntax forms that are processed at compile time. That is
why only quoted featurenames are supported.
This is consistent with Kawa emphasis on compilation and
static binding.
For some examples, you may want to look in the gnu/kawa/slib
directory.
The define-record-type form can be used for creating new data
types, called record types. A predicate, constructor, and field
accessors and modifiers are defined for each record type.
The define-record-type feature is specified
by SRFI-9,
which is implemented by many modern Scheme implementations.
define-record-type type-name (constructor-name field-tag ...) predicate-name (field-tag accessor-name [modifier-name]) ...
The form
define-record-typeis generative: each use creates a new record type that is distinct from all existing types, including other record types and Scheme's predefined types. Record-type definitions may only occur at top-level (there are two possible semantics for `internal' record-type definitions, generative and nongenerative, and no consensus as to which is better).An instance of
define-record-typeis equivalent to the following definitions:
The
type-nameis bound to a representation of the record type itself.The
constructor-nameis bound to a procedure that takes as many arguments as there arefield-tags in the(subform and returns a newconstructor-name...)type-namerecord. Fields whose tags are listed withconstructor-namehave the corresponding argument as their initial value. The initial values of all other fields are unspecified.The
predicate-nameis a predicate that returns#twhen given a value returned byconstructor-nameand#ffor everything else.Each
accessor-nameis a procedure that takes a record of typetype-nameand returns the current value of the corresponding field. It is an error to pass an accessor a value which is not a record of the appropriate type.Each
modifier-nameis a procedure that takes a record of typetype-nameand a value which becomes the new value of the corresponding field. The result (in Kawa) is the empty value#!void. It is an error to pass a modifier a first argument which is not a record of the appropriate type.Set!ing the value of any of these identifiers has no effect on the behavior of any of their original values.
Here is an example of how you can define a record type named pare
with two fields x and y:
(define-record-type pare (kons x y) pare? (x kar set-kar!) (y kdr))
The above defines kons to be a constructor,
kar and kdr to be accessors,
set-kar! to be a modifier,
and pare? to be a predicate for pares.
(pare? (kons 1 2)) ⇒ #t (pare? (cons 1 2)) ⇒ #f (kar (kons 1 2)) ⇒ 1 (kdr (kons 1 2)) ⇒ 2 (let ((k (kons 1 2))) (set-kar! k 3) (kar k)) ⇒ 3
Kawa compiles the record type into a nested class.
If the define-record-type appears at module level,
the result is a class that is a member of the module class.
For example if the above pare class is define in a
module parelib, then the result is a class
named pare with the internal JVM name parelib$pare.
The define-record-type can appear inside a procedure,
in which case the result is an inner class.
The nested class has a name derived from
the type-name. If the type-name is valid Java class name,
that becomes the name of the Java class. If the type-name has
the form < (for example name><pare>), then name
is used, if possible, for the Java class name. Otherwise, the name
of the Java class is derived by "mangling" the type-name.
In any case, the package is the same as that of the surrounding module.
Kawa generates efficient code for the resulting functions, without needing to use run-time reflection.
Calling the make-record-type procedure creates a new record data
type at run-time, without any compile-time support.
It is primarily provided for compatibility; in most cases it is better
to use the define-record-type form (see the section called “Record types”).
make-record-type type-name field-names
Returns a record-type descriptor, a value representing a new data type disjoint from all others. The
type-nameargument must be a string, but is only used for debugging purposes (such as the printed representation of a record of the new type). Thefield-namesargument is a list of symbols naming the fields of a record of the new type. It is an error if the list contains any duplicates.
record-constructor rtd [field-names]
Returns a procedure for constructing new members of the type represented by
rtd. The returned procedure accepts exactly as many arguments as there are symbols in the given list,field-names; these are used, in order, as the initial values of those fields in a new record, which is returned by the constructor procedure. The values of any fields not named in that list are unspecified. Thefield-namesargument defaults to the list of field names in the call tomake-record-typethat created the type represented byrtd; if thefield-namesargument is provided, it is an error if it contains any duplicates or any symbols not in the default list.
Returns a procedure for testing membership in the type represented by
rtd. The returned procedure accepts exactly one argument and returns a true value if the argument is a member of the indicated record type; it returns a false value otherwise.
record-accessor rtd field-name
Returns a procedure for reading the value of a particular field of a member of the type represented by
rtd. The returned procedure accepts exactly one argument which must be a record of the appropriate type; it returns the current value of the field named by the symbolfield-namein that record. The symbolfield-namemust be a member of the list of field-names in the call tomake-record-typethat created the type represented byrtd.
record-modifier rtd field-name
Returns a procedure for writing the value of a particular field of a member of the type represented by
rtd. The returned procedure accepts exactly two arguments: first, a record of the appropriate type, and second, an arbitrary Scheme value; it modifies the field named by the symbolfield-namein that record to contain the given value. The returned value of the modifier procedure is unspecified. The symbolfield-namemust be a member of the list of field-names in the call tomake-record-typethat created the type represented byrtd.
Returns a record-type descriptor representing the type of the given record. That is, for example, if the returned descriptor were passed to
record-predicate, the resulting predicate would return a true value when passed the given record.
Returns the type-name associated with the type represented by rtd. The returned value is
eqv?to thetype-nameargument given in the call tomake-record-typethat created the type represented byrtd.
Returns a list of the symbols naming the fields in members of the type represented by
rtd. The returned value isequal?to the field-names argument given in the call tomake-record-typethat created the type represented byrtd.
Records are extensions of the class Record.
These procedures use the Java 1.1 reflection facility.
You can call a Java method as if it were a Scheme procedure using various mechanisms.
The easiest way to invoke a static method is to use colon notation, specifically:
(class-expression:method-name argument ...)
The class-expression can be a class in the current lexical
scope, such as a class defined using define-simple-class:
(define-simple-class MyClass () ((add2 x y) allocation: 'static (+ x y))) (MyClass:add2 3 4) ⇒ 7
Often class-expression is a fully-qualified class name:
(java.lang.Math:sqrt 9.0) ⇒ 3.0
This is only allowed when the name is of a class that exists and is accessible both at compile-time and run-time, and the name is not otherwise lexically bound.
You can also use a defined alias:
(define-alias jlMath java.lang.Math) (jlMath:sqrt 16.0) ⇒ 4.0
You can even evaluate class-expression at run-time
(in which case Kawa may have to use slower reflection):
(let ((math java.lang.Math)) math:sqrt 9.0) ⇒ 3.0
Here java.lang.Math evaluates to a java.lang.Class
instance for the named class (like Java's java.lang.Class.class,
again assuming the class exists and is accessible both at compile-time and
run-time, and the name is not otherwise lexically bound.
The syntax is:
(instance:method-name argument ...)
This invokes the method named method-name
with the evaluated instance as the target object
and the evaluated arguments as the method arguments.
For example:
((list 9 8 7):toString) ⇒ "(9 8 7)" ([5 6 7]:get 2) ⇒ 7
This older syntax is also available:
(*:method-name instance argument ...)
For example:
(*:toString (list 9 8 7))
You can also name the class explicitly:
(class-expression:method-name instance argument ...)
For example:
(java.util.List:get [5 6 7] 2) ⇒ 7
Using an explicit class is like coercing the instance:
(*:method-name (as class-expression instance )argument ...)
Note that for some special values,
including java.lang.Class instances, you can't
use the compact form of colon notation
where the instance is before the comma:
(java.lang.Integer:getDeclaredField "MAX_VALUE") ⇒ error
This is because in this case we look for a static member
of java.lang.Integer
(at least as currently defined and implemented),
while we want an instance member of java.lang.Class.
In those cases you can use one of
these alternative forms, which all return the same
java.lang.reflect.Field result:
(*:getDeclaredField java.lang.Integer "MAX_VALUE") (java.lang.Class:getDeclaredField java.lang.Integer "MAX_VALUE") (invoke java.lang.Integer 'getDeclaredField "MAX_VALUE")
The method to invoke is selected using the specified
method name and argments. If specified name is not a Java name,
it is "mangled" (see the section called “Mapping Scheme names to Java names”) into a valid Java name.
All accessible methods whose names match are considered.
Methods that match after appending $V or $X or $V$X
are also considered. A $V suffix matches a variable
number of arguments: any excess arguments are collect into an
gnu.lists.LList or a Java array (depending on the final parameter type).
A $X specifies that the method expects an extra implicit
CallContext parameter. In that case the method's result is written
to the CallContext, so the method result type must be void.
(Kawa may compile a procedure with a #!rest or keyword args
whose name is to a method named fn.
It adds an implicit parameter for the extra arguments.
By default this extra extra parameter is a Scheme list.
You can specify a Java array type instead, in which case the method is
named fn$V without the fn$V,
and instead it is marked as a Java-5 varargs method.
The array element type must be compatible with all the extra arguments.)
If you prefer, you can instead use the following functions. (There is also an older deprecated lower-level interface (see ???.)
invoke-static class name args ...
The
classcan be ajava.lang.Class, agnu.bytecode.ClassType, or asymbolorstringthat names a Java class. Thenamecan besymbolorstringthat names one or more methods in the Java class.Any accessible methods (static or instance) in the specified
class(or its super-classes) that match "name" or "name$V" collectively form a generic procedure. When the procedure is applied to the argument list, the most specific applicable method is chosen depending on the argument list; that method is then called with the given arguments. Iff the method is an instance method, the first actual argument is used as thethisargument. If there are no applicable methods (or no methods at all!), or there is no "best" method,WrongTypeis thrown.An example:
(invoke-static java.lang.Thread 'sleep 100)The behavior of interpreted code and compiled code is not identical, though you should get the same result either way unless you have designed the classes rather strangely. The details will be nailed down later, but the basic idea is that the compiler will "inline" the
invoke-staticcall if it can pick a single "best" matching method.
The
namecan be<symbol>or<string>that names one or more methods in the Java class.Any accessible methods (static or instance) in the specified
class(or its super-classes) that match "name" or "name$V" collectively form a generic procedure. When the procedure is applied to the argument list, the most specific applicable method is chosen depending on the argument list; that method is then called with the given arguments. Iff the method is an instance method, theobjectis used as thethisargument; otherwiseobjectis prepended to theargslist. If there are no applicable methods (or no methods at all!), or there is no "best" method,WrongTypeis thrown.The behavior of interpreted code and compiled code is not indentical, though you should get the same result either way unless you have designed the classes rather strangely. The details will be nailed down later, but the basic idea is that the compiler will "inline" the
invoke-staticcall if it can pick a single "best" matching method.If the compiler cannot determine the method to call (assuming the method name is constant), the compiler has to generate code at run-time to find the correct method. This is much slower, so the compiler will print a warning. To avoid a waning, you can use a type declaration, or insert a cast:
(invoke (as java.util.Date my-date) 'setDate cur-date)or
(let ((my-date ::java.util.Date (calculate-date)) (cur-date ::int (get-cur-date))) (invoke my-date 'setDate cur-date))
invoke-special class receiver-object name arg ...
The
classcan be ajava.lang.Class, agnu.bytecode.ClassType, or asymbolorstringthat names a Java class. Thenamecan besymbolorstringthat names one or more methods in the Java class.This procedure is very similar to
invokeandinvoke-staticand invokes the specified method, ignoring any methods in subclasses that might overide it. One interesting use is to invoke a method in your super-class like the Java languagesuperkeyword.Any methods in the specified
classthat match "name" or "name$V" collectively form a generic procedure. That generic procedure is then applied as ininvokeusing thereceiver-objectand the arguments (if any).The compiler must be able to inline this procedure (because you cannot force a specific method to be called using reflection). Therefore the
classandnamemust resolve at compile-time to a specific method.(define-simple-class <MyClass> (<java.util.Date>) ((get-year) :: <int> (+ (invoke-special <java.util.Date> (this) 'get-year)) 1900) ((set-year (year :: <int>)) :: <void> (invoke-special <java.util.Date> (this) 'set-year (- year 1900))))
Return a generic function containing those methods of
classthat match the namename, in the sense ofinvoke-static. Same as:(lambda args (apply invoke-static (cons class (cons name args))))
Some examples using these functions are ‘vectors.scm’
and ‘characters.scm’ the directory ‘kawa/lib’ in
the Kawa sources.
This way of invoking a method is deprecated.
You can use define-namespace to define an alias for a Java class:
(define-namespace Int32 "class:java.lang.Integer")
In this example the name Int32 is a namespace alias
for the namespace whose full name is "class:java.lang.Integer".
The full name should be the 6 characters "class:" followed
by the fully-qualified name of a Java class.
Instead of a vamespace-uri you can use a variable that names
a class, usually of the form <.
The following is equivalent to the above:
classname>
(define-namespace Int32 <java.lang.Integer>)
However, there is one important difference: The <
is first searched in the lexical scope.
It may resolve to a class defined in the current compilation unit
(perhaps defined using classname>define-simple-class),
or imported from another module,
or an alias (such as from define-alias).
Only if < is not found in the current
scope is it tried as the class name classname>classname.
You can name a method using a qualified name containing a colon.
The part of the name before the colon is a namespace alias (in
this case Int32), and the part of the name after the colon is the
method name. For example:
(Int32:toHexString 255) ⇒ "ff"
This invokes the static method toHexString in the
Java class java.lang.Integer, passing it the argument 255,
and returning the String "ff".
The general syntax is
(prefix:method-namearg...)
This invokes the method named method-name in the class corresponding
to prefix, and the args are the method arguments.
You can use the method name new to construct new objects:
(Int32:new '|255|)
This is equivalent to the Java expression new Integer("255").
You can also write:
(Int32:new "255")
You can also call instance methods using a namespace prefix:
(Int32:doubleValue (Int32:new "00255"))
This returns the double value 255.0.
As a shorthand, you can use the name of a Java class instead of a namespace alias:
(java.lang.Integer:toHexString 255) (java.lang.Object:toString some-value)
If Kawa sees a qualified name with a prefix that is not defined and
that matches the name of a known class, then Kawa will automatically
treat the prefix
as a nickname for namespace uri like class:java.lang.Integer.
Both conditions should be true at both compile-time and run-time.
However, using an explicit define-namespace is recommended.
As a final shorthand you can use an identifier in handle brackets,
such as an existing type alias like <list>.
The following are all equivalent:
(<list>:list3 'a 'b 'c)
This is equivalent to:
(define-namespaceprefix<list> (prefix:list3 'a 'b 'c)
for some otherwise-unused prefix.
The recommended way to create an instance of a type T
is to “call” T as if it were a function, with the
arguments used to initialize the object.
If T is a class and T has a matching constructor,
then the arguments will used for constructor arguments:
(java.util.StringTokenizer "this/is/a/test" "/")
(You can think of the type T as being
coerced to an instance-constructor function.)
If T is a container or collection type,
then typically the arguments will be used to specify
the child or component values.
Many standard Scheme procedures fit this convention.
For example in Kawa list and vector evaluate to
types, rather than procedures as in standard Scheme,
but because types can be used as constructor functions it just works:
(list 'a (+ 3 4) 'c) ⇒ (a 7 c) (vector 'a 'b 'c) ⇒ #(a b c)
Any class T that has a default constructor
and an add method can be initialized this way.
Examples are java.util collection classes,
and jawa.awt and javax.swing containers.
(java.util.ArrayList 11 22 33) ⇒ [11, 22, 333]
The above expression is equivalent to:
(let ((tmp (java.util.ArrayList))) (tmp:add 11) (tmp:add 22) (tmp:add 33) tmp)
Allocating Java arrays (see the section called “Using Java Arrays”) uses a similar pattern:
(int[] 2 3 5 7 11)
Sometimes you want to set some named property to an initial value. You can do that using a keyword argument. For example:
(javax.swing.JButton text: "Do it!" tool-tip-text: "do it")
This is equivalent to using setter methods:
(let ((tmp (javax.swing.JButton))) (tmp:setText "Do it!") (tmp:setToolTipText "do it") tmp)
A keyword argument key-name: can
can translated to either a
or a setKeyName: method.
The latter makes it convenient to add listeners:
addKeyName:
(javax.swing.JButton
text: "Do it!"
action-listener:
(object (java.awt.event.ActionListener)
((actionPerformed e) (do-the-action))))
This is equivalent to:
(let ((tmp (javax.swing.JButton)))
(tmp:setText "Do it!")
(tmp:addActionListener
(object (java.awt.event.ActionListener)
((actionPerformed e) (do-the-action))))
tmp)
Making use of so-called “SAM-conversion” (see the section called “Anonymous classes”) makes it even more convenient:
(javax.swing.JButton text: "Do it!" action-listener: (lambda (e) (do-the-action)))
The general case allows for a mix of constructor arguments, property keywords, and child values:
class-type constructor-value... property-initializer... child-value...
constructor-value ::= expression
property-initializer ::= keyword expression
child-value ::= expression
First an object is constructed with the constructor-value arguments
(if any) passed to the object constructor;
then named properties (if any) are used to initialize named properties;
and then remaining arguments are used to add child values.
There is an ambiguity if there is no property-initializer -
we can't distinguish between a constructor-value
and a child-value.
In that case, if there is a matching constructor method, then all of the
arguments are constructor arguments;
otherwise, there must a default constructor, and all
of the arguments are child-value arguments.
There is a trick you can you if you need both
constructor-value and child-value arguments:
separate them with an “empty keyword” ||:.
This matches a method named add, which means that
the next argument effectively a child-value - as do
all the remaining arguments. Example:
(let ((vec #(1 2 3))) (java.util.ArrayList vec ||: 4 5 6)) ⇒ [1, 2, 3, 4, 5, 6]
The compiler rewrites these allocations expression to generated efficient bytecode, assuming that the “function” being applied is a type known by the compiler. Most of the above expressions also work if the type is applied at run-time, in which case Kawa has to use slower reflection:
(define iarr int[]) (apply iarr (list 3 4 5)) ⇒ [3 4 5]
However add methods and SAM-conversion
are currently only recognized in the case of a class known at compile-time,
not at run-time.
Xxx
Here is a working Swing demo illustrating many of these techniques:
(define-alias JButton javax.swing.JButton)
(define-simple-class HBox (javax.swing.Box)
((*init*) (invoke-special javax.swing.Box (this) '*init* 0)))
(define-alias JFrame javax.swing.JFrame)
(define-alias Box javax.swing.Box)
(define value 0)
(define txt
(javax.swing.JLabel
text: "0"))
(define (set-value i)
(set! value i)
(set! txt:text (number->string i)))
(define fr
(JFrame
title: "Hello!"
(Box 1#|VERTICAL|# ||:
(javax.swing.Box:createGlue)
txt
(javax.swing.Box:createGlue)
(HBox
(JButton ;; uses 1-argument constructor
"Decrement" ;; constructor argument
tool-tip-text: "decrement"
action-listener: (lambda (e) (set-value (- value 1))))
(javax.swing.Box:createGlue)
(JButton ;; uses 0-argument constructor
text: "Increment"
tool-tip-text: "increment"
action-listener: (lambda (e) (set-value (+ value 1))))))))
(fr:setSize 200 100)
(set! fr:visible #t)
If you prefer, you can use the older make special function:
Constructs a new object instance of the specified
type, which must be either ajava.lang.Classor a<gnu.bytecode.ClassType>. Equivalent to:typeargs...
Another (semi-deprecated) function is to use the colon notation
with the new pseudo-function.
The following three are all equivalent:
(java.awt.Point:new x: 4 y: 3) (make java.awt.Point: x: 4 y: 3) (java.awt.Point x: 4 y: 3)
The recommmended way to access fields uses the colon notation. For static fields and properties the following is recommended:
class-expression:field-name
For example:
java.lang.Integer:MAX_VALUE
A property with a get method is equivalent to a field.
The following are all equivalent:
java.util.Currency:available-currencies java.util.Currency:availableCurrencies (java.util.Currency:getAvailableCurrencies)
Just like for a method call, the class-expression
can be a class in the current lexical scope,
a fully-qualified class name, or more generally an
expression that evaluates to a class.
The syntax is:
instance:field-name
The field-name can of course be the name of an actual
object field, but it can also be the name of a property with
a zero-argument get method.
For example, if cal is a java.util-Calendar instance,
then the following are all equivalent:
cal:time-zone cal:timeZone (cal:getTimeZone) (cal:get-time-zone)
You can use colon notation to assign to a field:
(set! cal:time-zone TimeZone:default)
which is equivalent to:
(cal:setTimeZone (TimeZone:getDefault))
A Java array only has the length field, plus the class property:
(int[] 4 5 6):length ⇒ 3 (int[] 4 5 6):class:name ⇒ "int[]"
The following methods are useful in cases where colon notation is ambiguous, for example where there are both fields and methods with the same name. You might also prefer as a matter of style, to emphasise that a field is being accessed.
Get the instance field with the given
fieldnamefrom the givenObject. Returns the value of the field, which must be accessible. This procedure has asetter, and so can be used as the first operand toset!.The field name is "mangled" (see the section called “Mapping Scheme names to Java names”) into a valid Java name. If there is no accessible field whose name is
", we look for a no-argument method whose name isfieldname""get(orFieldname""isfor a boolean property).Fieldname"If
objectis a primitive Java array, thenfieldnamecan only be'length, and the result is the number of elements of the array.
Get the static field with the given
fieldnamefrom the givenclass. Returns the value of the field, which must be accessible. This procedure has asetter, and so can be used as the first operand toset!.If the
fieldnameis the special nameclass, then it returns thejava.lang.Classobject corresponding toclass(which is usually agnu.bytecode.ClassTypeobject).
Examples:
(static-field java.lang.System 'err) ;; Copy the car field of b into a. (set! (field a 'car) (field b 'car))
There is older syntax where following the colon there is field name a following the colon and a period.
To access an static field named field-name use this syntax
(prefix:.field-nameinstance)
The prefix can be as discussed in See the section called “Calling Java methods from Scheme”.
Here are 5 equivalent ways:
(java.lang.Integer:.MAX_VALUE) (<java.lang.Integer>:.MAX_VALUE) (define-namespace Int32 <java.lang.Integer>) (Int32:.MAX_VALUE) (define-namespace Integer "class:java.lang.Integer") (Integer:.MAX_VALUE) (define-alias j.l.Integer java.lang.Integer) (j.l.Integer:.MAX_VALUE)
You can set a static field using this syntax:
(set! (prefix:.field-name)new-value)
The special field name class can be used to extract the
java.lang.Class object for a class-type. For example:
(java.util.Vector:.class) ⇒ class java.util.Vector
To access a instance field named field-name use the following syntax.
Note the period before the field-name.
(*:.field-nameinstance)
This syntax works with set! - to set the field use this syntax:
(set! (*:.field-nameinstance)new-value)
Here is an example:
(define p (list 3 4 5)) (*:.cdr p) ⇒ (4 5) (set! (*:.cdr p) (list 6 7)) p ⇒ (3 6 7)
You can specify an explicit class:
(prefix:.field-nameinstance)
If prefix is bound to <, then the above
is equivalent to:
class>
(*:.field-name(as <class>instance))
Programs use "names" to refer to various values and procedures.
The definition of what is a "name" is different in different
programming languages. A name in Scheme (and other Lisp-like
languages) can in principle contain any character (if using a
suitable quoting convention), but typically names consist of
"words" (one or more letters) separated by hyphens, such
as ‘make-temporary-file’. Digits
and some special symbols are also used. Standard Scheme
is case-insensitive; this means that the names ‘loop’,
‘Loop’, and ‘LOOP’ are all the same name. Kawa
is by default case-sensitive, but we recommend that you
avoid using upper-case letters as a general rule.
The Java language and the Java virtual machine uses names for
classes, variables, fields and methods. These names can
contain upper- and lower-case letters, digits, and the special
symbols ‘_’ and ‘$’.
Given a name in a Scheme program,
Kawa needs to map that name into a valid Java name. A typical
Scheme name such as ‘make-temporary-file’ is not a valid
Java name. The convention for Java names is to use
"mixed-case" words, such as ‘makeTemporaryFile’.
So Kawa will translate a Scheme-style name into a Java-style
name. The basic rule is simple: Hyphens are dropped, and
a letter that follows a hyphen is translated to its
upper-case (actually "title-case") equivalent. Otherwise,
letters are translated as is.
Some special characters are handled specially. A final ‘?’
is replaced by an initial ‘is’, with the following
letter converted to titlecase. Thus ‘number?’ is
converted to ‘isNumber’ (which fits with Java conventions),
and ‘file-exists?’ is converted to ‘isFileExists’
(which doesn't really).
The pair ‘->’ is translated to ‘$To$’.
For example ‘list->string’ is translated to ‘list$To$string’.
Some symbols are mapped to a mnemonic sequence, starting with a dollar-sign,
followed by a two-character abbreviation. For example, the less-than
symbol ‘<’ is mangled as ‘$Ls’.
See the source code to the mangleName method in the
gnu.expr.Compilation class for the full list.
Characters that do not have a mnemonic abbreviation are
mangled as ‘$’ followed by a four-hex-digit unicode value.
For example ‘Tamil vowel sign ai’ is mangled as ‘$0bc8’.
Note that this mapping may map different Scheme names to the
same Java name. For example ‘string?’, ‘String?’,
‘is-string’, ‘is-String’,
and ‘isString’ are all mapped to the same Java identifier
‘isString’. Code that uses such "Java-clashing" names
is not supported. There is very partial support for
renaming names in the case of a clash, and there may be better
support in the future. However, some of the nice features of
Kawa depend on being able to map Scheme name to Java names
naturally, so we urge you to not write code that
"mixes" naming conventions by using (say) the names ‘open-file’
and ‘openFile’ to name two different objects.
The above mangling is used to generate Java method names.
Each top-level definition is also mapped to a Java field.
The name of this field is also mangled, but using a mostly
reversible mapping: The Scheme function ‘file-exists?’
is mapped to the method name ‘file$Mnexists$Qu’.
Because ‘$’ is used to encode special characters, you
should avoid using it in names in your source file.
All Scheme values are implemented by sub-classes of ‘java.lang.Object’.
Scheme symbols are implemented using java.lang.String.
(Don't be confused by the fact the Scheme sybols are represented
using Java Strings, while Scheme strings are represented by
gnu.lists.FString. It is just that the semantics of Java strings
match Scheme symbols, but do not match mutable Scheme strings.)
Interned symbols are presented as interned Strings.
(Note that with JDK 1.1 string literals are automatically interned.)
Scheme integers are implemented by gnu.math.IntNum.
Use the make static function to create a new IntNum from an int or a long.
Use the intValue or longValue methods to get the int or long value of
an IntNum.
A Scheme "flonum" is implemented by gnu.math.DFloNum.
A Scheme pair is implemented by gnu.lists.Pair.
A Scheme vector is implemented by gnu.lists.FVectror.
Scheme characters are implemented using gnu.text.Char.
Scheme strings are implemented using gnu.lists.FString.
Scheme procedures are all sub-classes of gnu.mapping.Procedure.
The "action" of a ‘Procedure’ is invoked by using one of
the ‘apply*’ methods: ‘apply0’, ‘apply1’,
‘apply2’, ‘apply3’, ‘apply4’, or ‘applyN’.
Various sub-class of ‘Procedure’ provide defaults
for the various ‘apply*’ methods. For example,
a ‘Procedure2’ is used by 2-argument procedures.
The ‘Procedure2’ class provides implementations of all
the ‘apply*’ methods except ‘apply2’,
which must be provided by any class that extends Procedure2.
To allocate a Java array you can use the array type specifier
as a constructor function. For example, to allocate an array with room for 10 elements
each of each is a primitive int:
(int[] length: 10)
You can specify the initial elements instead of the length:
(object[] 31 32 33 34)
This creates a 4-length array, initialized to the given values.
Note this is a variation of the generation object-allocation
(see the section called “Allocating objects”) pattern. You can explicitly
use the make function, if you prefer:
(make object[] 31 32 33 34)
If you specify a length, you can also specify initial values for selected elements. If you specify an index, in the form of a literal integer-valued keyword, then following elements are placed starting at that position.
(int[] length: 100 10 12 80: 15 16 50: 13 14)
This creates an array with 100 elements. Most of them are initialized to the default value of zero, but elements with indexes 0, 1, 50, 51, 80, 81 are initialized to the values 10, 12, 13, 14, 15, 16, respectively.
You can access the elements of a Java array by treating it as a one-argument function, where the argument is the index:
(define primes (integer[] 2 3 5 7 11 13)) (primes 0) ⇒ 2 (primes 5) ⇒ 13
You can set an element by treating the array as a function
with a setter:
(set! (primes 0) -2) (set! (primes 3) -7) primes ⇒ [-2 3 5 -7 11 13]
To get the number of elements of an array, you can treat
it as having a length field:
primes:length ⇒ 6
Here is a longer example. This is the actual definition of the
standard gcd function. Note the args variable
receives all the arguments on the form of an integer array.
(This uses the Java5 varargs feature.)
(define (gcd #!rest (args ::integer[])) ::integer
(let ((n ::int args:length))
(if (= n 0)
0
(let ((result ::integer (args 0)))
(do ((i ::int 1 (+ i 1)))
((>= i n) result)
(set! result (gnu.math.IntNum:gcd result (args i))))))))
The above example generates good code, thanks to judicious use of casts and type specifications. In general, if Kawa knows that a “function” is an array then it will generate efficient bytecode instructions for array operations.
The deprecated ??? are also supported.
When kawa -C compiles (see the section called “Compiling to a set of .class files”) a Scheme module
it creates a class that implements the java.lang.Runnable interface.
(Usually it is a class that extends the gnu.expr.ModuleBody.)
It is actually fairly easy to write similar "modules" by hand in Java,
which is useful when you want to extend Kawa with new "primitive functions"
written in Java. For each function you need to create an object that
extends gnu.mapping.Procedure, and then bind it in the global
environment. We will look at these two operations.
There are multiple ways you can create a Procedure object. Below
is a simple example, using the Procedure1 class, which is class
extending Procedure that can be useful for one-argument
procedure. You can use other classes to write procedures. For example
a ProcedureN takes a variable number of arguments, and you must
define applyN(Object[] args) method instead of apply1.
(You may notice that some builtin classes extend CpsProcedure.
Doing so allows has certain advantages, including support for
full tail-recursion, but it has some costs, and is a bit trickier.)
import gnu.mapping.*;
import gnu.math.*;
public class MyFunc extends Procedure1
{
// An "argument" that is part of each procedure instance.
private Object arg0;
public MyFunc(String name, Object arg0)
{
super(name);
this.arg0 = arg0;
}
public Object apply1 (Object arg1)
{
// Here you can so whatever you want. In this example,
// we return a pair of the argument and arg0.
return gnu.lists.Pair.make(arg0, arg1);
}
}
You can create a MyFunc instance and call it from Java:
Procedure myfunc1 = new MyFunc("my-func-1", Boolean.FALSE);
Object aresult = myfunc1.apply1(some_object);
The name my-func-1 is used when myfunc1 is printed
or when myfunc1.toString() is called. However,
the Scheme variable my-func-1 is still not bound.
To define the function to Scheme, we can create
a "module", which is a class intended to be loaded
into the top-level environment. The provides the definitions to be
loaded, as well as any actions to be performed on loading
public class MyModule
{
// Define a function instance.
public static final MyFunc myfunc1
= new MyFunc("my-func-1", IntNum.make(1));
}
If you use Scheme you can use require:
#|kawa:1|# (require <MyModule>) #|kawa:2|# (my-func-1 0) (1 0)
Note that require magically defines my-func-1 without
you telling it to. For each public final
field, the name and value of the field are entered in the
top-level environment when the class is loaded. (If there are
non-static fields, or the class implements Runnable, then
an instance of the object is created, if one isn't available.)
If the field value is a Procedure (or implements Named),
then the name bound to the procedure is used instead of the field name.
That is why the variable that gets bound in the Scheme environment is
my-func-1, not myfunc1.
Instead of (require <MyModule>), you can do (load "MyModule")
or (load "MyModule.class").
If you're not using Scheme, you can use Kawa's -f option:
$ kawa -f MyModule --xquery -- #|kawa:1|# my-func-1(3+4) <list>1 7</list>
If you need to do some more complex calculations when a module is loaded,
you can put them in a run method, and have the module
implement Runnable:
public class MyModule implements Runnable
{
public void run ()
{
Interpreter interp = Interpreter.getInterpreter();
Object arg = Boolean.TRUE;
interp.defineFunction (new MyFunc ("my-func-t", arg));
System.err.println("MyModule loaded");
}
}
Loading MyModule causes "MyModule loaded" to be printed,
and my-func-t to be defined. Using Interpreter's
defineFunction method is recommended because it does the righ
things even for languages like Common Lisp that use separate
"namespaces" for variables and functions.
A final trick is that you can have a Procedure be its own module:
import gnu.mapping.*;
import gnu.math.*;
public class MyFunc2 extends Procedure2
{
public MyFunc(String name)
{
super(name);
}
public Object apply2 (Object arg1, arg2)
{
return gnu.lists.Pair.make(arg1, arg2);
}
public static final MyFunc myfunc1 = new MyFunc("my-func-2);
}
The following methods are recommended if you need to evaluate a
Scheme expression from a Java method.
(Some details (such as the ‘throws’ lists) may change.)
voidScheme.registerEnvironment ()
Initializes the Scheme environment. Maybe needed if you try to load a module compiled from a Scheme source file.
ObjectScheme.eval (InPort port, Environment env)
Read expressions from
port, and evaluate them in theenvenvironment, until end-of-file is reached. Return the value of the last expression, orInterpreter.voidObjectif there is no expression.
ObjectScheme.eval (String string, Environment env)
Read expressions from
string, and evaluate them in theenvenvironment, until the end of the string is reached. Return the value of the last expression, orInterpreter.voidObjectif there is no expression.
ObjectScheme.eval (Object sexpr, Environment env)
The
sexpris an S-expression (as may be returned byread). Evaluate it in theenvenvironment, and return the result.
For the Environment in most cases you could use
‘Environment.current()’. Before you start, you
need to initialize the global environment,
which you can with
Environment.setCurrent(new Scheme().getEnvironment());
Alternatively, rather than setting the global environment, you can use this style:
Scheme scm = new Scheme();
Object x = scm.eval("(+ 3 2)");
System.out.println(x);
Kawa also supports the standard
javax.script API.
The main advantage of this API is if you want your users to be able to chose
between multiple scripting languages. That way you can support Kawa
without Kawa-specific programming.
For example the standard JDK tool jrunscript provides a
read-eval-print-loop for any language that implements the javax.script
API. It knows nothing about Kawa but can still use it:
$ jrunscript -cp kawa.jar -l scheme scheme> (cadr '(3 4 5)) 4
(Of course the jrunscript REPL isn't as nice as the one that
Kawa provides. For example the latter can handle multi-line inputs.)
