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-class
is used, creates a normal Java class namedclass-name
in the current package. (Ifclass-name
has the form<xyz>
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.
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-interface
is#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-interface
is#f
, then a Java class is generated. Ifinterface:
is unspecified, the default is#f
fordefine-simple-class
. Fordefine-class
the 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 isabstract
if anymethod-body
is#!abstract
, or you specifyinterface: #t
.) Thekind
can also be a list, as for example:access: '(protected volatile)
-
class-name:
"
cname
"
Specifies the Java name of the created class. The
name
specified afterdefine-class
ordefine-simple-class
is 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 thecname
has no periods, then it is a name in the package of the main (module) class. If thecname
starts with a period, then you get a class nested within the module class. In this case the actual class name ismoduleClass
$
rname
, wherername
iscname
without 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
keyword
s are implemented:
-
type:
type
Specifies that
type
is the type of (the values of) the field. Equivalent to ‘::
’.type
-
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. -
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
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'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-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 scope of the body
of a method includes the field-decl
s
and field-decl
s 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-decl
is as fordefine-class
, except that we also allow an abbreviated syntax. Eachfield-decl
declares a public instance field. Ifobject-finit
is given, it is an expression whose value becomes the initial value of the field. Theobject-init
is evaluated at the same time as theobject
expression is evaluated, in a scope where all thefield-name
s are visible.A
method-decl
is 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-name
list. You can specify extra options and members usingoption-pair
andfield-or-method-decl
, which are as indefine-simple-class
. (Thedefine-enum
syntax is similar to adefine-simple-class
that 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-value
must be number or boolean coercible to the element type.Strings, where the
annotation-element-value
is normally a string literal.Classes, where the
annotation-element-value
is normally a classname.Enumeration types. The value usually has the form
.ClassName
:enumFieldname
Nested annotation types, where the
annotation-element-value
must be a compatibleannotation
value.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
export
andmodule-export
are equivalent. (The older Kawa name ismodule-export
;export
comes from R7RS.) Either form specifies a list of identifiers which can be made visible to other libraries or programs.
export-spec
::=
identifier
|(rename
identifier
_1identifier
_2)
In the former variant, an
identifier
names 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. Arename
spec 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-P
Kawa 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-static
If no
module-static
is specified, generate a static module (as if(module-static #t)
were specified). This is (now) the default.--module-nonstatic
--no-module-static
If no
module-static
is specified, generate a non-static module (as if(module-static #f)
were specified). This used to be the default.--module-static-run
If no
module-static
is specified, generate a static module (as if(module-static 'init-run)
were specified).
Control whether the generated fields and methods are static. If
#t
or'init-run
is specified, then the module will be a static module, all definitions will be static. If'init-run
is 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, thename
s that are explicitly listed will be compiled to static fields and methods. If#f
is specified, then all exported names will be compiled to non-static (instance) fields and methods.By default, if no
module-static
is specified:
If there is a
module-extends
ormodule-implements
declaration, or one of the--applet
or--servlet
command-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-run
was 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
key
option tovalue
for 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
value
must be a literal value: either a boolean (#t
or#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-options
syntax.(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.ModuleBody
or implementsjava.lang.Runnable
then the correspondingrun
method 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 thefeaturename
featurename
is 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
|(library
library-reference
)
|(only
import-set
identifier
^*)
|(except
import-set
identifier
^*)
|(prefix
import-set
identifier
)
|(rename
import-set
(
identifier1
identifier2
)
^*)
library-reference
::=
(
identifier
^+)
A
library-reference
is 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
, andrename
forms as described below.
An
only
form produces a subset of the bindings from anotherimport-set
, including only the listedidentifier
s. The includedidentifier
s must be in the originalimport-set
.An
except
form produces a subset of the bindings from anotherimport-set
, including all but the listedidentifier
s. All of the excludedidentifier
s must be in the originalimport-set
.A
prefix
form adds theidentifier
prefix to each name from anotherimport-set
.A
rename
form:(rename (identifier1
identifier2
) …)removes the bindings for
to form an intermediate
identifier1
…import-set
, then adds the bindings back for the correspondingto form the final
identifier2
…import-set
. Eachidentifier1
must be in the originalimport-set
, eachidentifier2
must not be in the intermediateimport-set
, and theidentifier2
s must be distinct.
Declare that
'
is available. A followingfeaturename
cond-expand
in this scope will matchfeaturename
.
Using require
and provide
with featurename
s 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 featurename
s 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-type
is 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-type
is equivalent to the following definitions:
The
type-name
is bound to a representation of the record type itself.The
constructor-name
is bound to a procedure that takes as many arguments as there arefield-tag
s in the(
subform and returns a newconstructor-name
...)type-name
record. Fields whose tags are listed withconstructor-name
have the corresponding argument as their initial value. The initial values of all other fields are unspecified.The
predicate-name
is a predicate that returns#t
when given a value returned byconstructor-name
and#f
for everything else.Each
accessor-name
is a procedure that takes a record of typetype-name
and 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-name
is a procedure that takes a record of typetype-name
and 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 pare
s.
(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-name
argument must be a string, but is only used for debugging purposes (such as the printed representation of a record of the new type). Thefield-names
argument 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-names
argument defaults to the list of field names in the call tomake-record-type
that created the type represented byrtd
; if thefield-names
argument 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-name
in that record. The symbolfield-name
must be a member of the list of field-names in the call tomake-record-type
that 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-name
in that record to contain the given value. The returned value of the modifier procedure is unspecified. The symbolfield-name
must be a member of the list of field-names in the call tomake-record-type
that 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-name
argument given in the call tomake-record-type
that 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-type
that 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 argument
s 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
class
can be ajava.lang.Class
, agnu.bytecode.ClassType
, or asymbol
orstring
that names a Java class. Thename
can besymbol
orstring
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, the first actual argument is used as thethis
argument. If there are no applicable methods (or no methods at all!), or there is no "best" method,WrongType
is 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-static
call if it can pick a single "best" matching method.
The
name
can 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, theobject
is used as thethis
argument; otherwiseobject
is prepended to theargs
list. If there are no applicable methods (or no methods at all!), or there is no "best" method,WrongType
is 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-static
call 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
class
can be ajava.lang.Class
, agnu.bytecode.ClassType
, or asymbol
orstring
that names a Java class. Thename
can besymbol
orstring
that names one or more methods in the Java class.This procedure is very similar to
invoke
andinvoke-static
and 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 languagesuper
keyword.Any methods in the specified
class
that match "name
" or "name
$V" collectively form a generic procedure. That generic procedure is then applied as ininvoke
using thereceiver-object
and 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
class
andname
must 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
class
that 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-name
arg
...)
This invokes the method named method-name
in the class corresponding
to prefix
, and the arg
s 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 set
KeyName
:
method.
The latter makes it convenient to add listeners:
add
KeyName
:
(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.Class
or a<gnu.bytecode.ClassType>
. Equivalent to:type
args
...
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
fieldname
from 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
""is
for a boolean property).Fieldname
"If
object
is a primitive Java array, thenfieldname
can only be'length
, and the result is the number of elements of the array.
Get the static field with the given
fieldname
from 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
fieldname
is the special nameclass
, then it returns thejava.lang.Class
object corresponding toclass
(which is usually agnu.bytecode.ClassType
object).
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-name
instance
)
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-name
instance
)
This syntax works with set!
- to set the field use this syntax:
(set! (*:.field-name
instance
)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-name
instance
)
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 theenv
environment, until end-of-file is reached. Return the value of the last expression, orInterpreter.voidObject
if there is no expression.
ObjectScheme.eval
(String string
, Environment env
)
Read expressions from
string
, and evaluate them in theenv
environment, until the end of the string is reached. Return the value of the last expression, orInterpreter.voidObject
if there is no expression.
ObjectScheme.eval
(Object sexpr
, Environment env
)
The
sexpr
is an S-expression (as may be returned byread
). Evaluate it in theenv
environment, 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.)