Java supports “reflection”, that is the ability to determine and examine the class of an object, and use the class at run-time to extract fields and call methods using names specified at run-time. Kawa, like some other Scheme implementations, also supports reflection.
It seems plausible to represent a type using a
java.lang.Class
object, since that is what
the Java reflective facility does.
Unfortunately, there are at least
three reasons why Kawa needs a different representation:
We may need to refer to classes that do not exist yet, because we are in the process of compiling them.
We want to be able to specify different high-level types that are
represented using the same Java type. For example, we might want
to have integer sub-ranges and enumerations (represented using
int
), or different kinds of function types.
We want to associate different conversion (coercion) rules for different types that are represented using the same class.
Kawa represents types using instances of Type
:
public abstract class Type
{ ...;
String signature; // encoded type name
int size;
public final String getName() { ... }
public boolean isInstance(Object obj)
{ ... }
public void emitIsInstance(CodeAttr c)
{ ... }
}
The method isInstance
tests if an object
is a member of this type, while emitIsInstance
is called by the compiler to emit a run-time test.
Note that the earlier mentioned ClassType
extends Type
.
Kawa follows the convention (used in RScheme [RScheme]
and other Scheme dialects) that identifiers of the form
<
are used to name types. For example Scheme vectors are
members of the type typename
><vector>
.
This is only a convention and these names are regular identifiers,
expect for one little feature: If such an identifier is used,
and it is not bound, and typename
has the form
of a Java type, then a corresponding Type
is returned.
For example <java.lang.String[]>
evaluates to a Type
whose values are references to
Java arrays whose elements are references to Java strings.
As a simple example of using type values, here is the
definition of the standard Scheme predicate vector?
,
which returns true iff the argument is a Scheme vector:
(define (vector? x) (instance? x <vector>)
The primitive Kawa function instance?
implements
the Java instanceof
operation, using
Type
's isInstance
method.
(In compiled code, if the second operand is known at compile-time,
then the compiler uses Type
's
emitIsInstance
method to generate better code.)
The traditional benefits of adding types to a dynamic language
include better code generation, better error checking, and
machine-checkable interface documentation. These benefits require either
type inference or (optional) type declarations. Kawa does allow
the types of parameters to be declared, and does some very simple
local type inference. In some cases this lets “unboxed”
values (such as raw Java int
) be passed
from one function to another without having to allocate an object.
While Kawa has a framework for working with types,
it does need a more systematic approach.
Kawa includes the record extension which
allows a new record type to be specified and created at run-time.
It is implemented by creating a new ClassType
with the specified fields, and loading the class
using ClassLoader.defineClass
.
The record facility consists of a number of functions executed at run-time.
Many people prefer an approach based on declarations
that can be more easily analysed at compile-time. (This is why
the record facility was rejected for R5RS.) A more declarative and
general class definition facility is planned but not yet implemented.