Class, types, and declarations

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:

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 <typename> are used to name types. For example Scheme vectors are members of the type <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.