A type is a set of values, plus an associated set of operations
valid on those values.
Types are useful for catching errors ("type-checking"), documenting
the programmer's intent, and to help the compiler generate better code.
Types in some languages (such as C) appear in programs,
but do not exist at run-time. In such languages, all type-checking
is done at compile-time. Other languages (such as standard Scheme)
do not have types as such, but they have predicates, which
allow you to check if a value is a member of certain sets; also,
the primitive functions will check at run-time if the arguments
are members of the allowed sets. Other languages, including Java
and Common Lisp, provide a combination: Types may be used as specifiers
to guide the compiler, but also exist as actual run-time values.
In Java, for each class, there is a corresponding java.lang.Class
run-time object, as well as an associated type (the set of values
of that class, plus its sub-classes, plus null
).
Kawa, like Java, has first-class types, that is types exist as
objects you can pass around at run-time. For each Java type,
there is a corresponding Kawa type (but not necessarily vice
versa). It would be nice if we could represent run-time
type values using java.lang.Class
objects, but unfortunately
that does not work very well. One reason is that we need
to be able to refer to types and classes that do not exist yet,
because we are in the processing of compiling them. Another
reason is that we want to be able to distinuish between different
types that are implemented using the same Java class.
Various Kawa constructs require or allow a type to be specified. Those specifications consist of type expressions, which is evaluated to yield a type value. The current Kawa compiler is rather simple-minded, and in many places only allows simple types that the compiler can evaluate at compile-time. More specifically, it only allows simple type names that map to primitive Java types or java classes.
type
::=
expression
opt-type-specifier
::=
[::
type
]
These types are predefined with the following names.
Instead if plain
you can also use
the syntax typename
<
with angle brackets,
but that syntax is no longer recommended, because it doesn't
“fit” as well with some ways type names are used.
typename
>
To find which Java classes these types map into, look in
kawa/standard/Scheme.java
.
Note that the value of these variables are instances
of gnu.bytecode.Type
,
not (as you might at first expect) java.lang.Class
.
The type of Scheme symbols. (Implemented using the Java class
gnu.mapping.Symbol
.) (Compatibility note: Previous versions of Kawa implemented a simple Scheme symbol using an internedjava.lang.String
.)
The type of keyword values. See the section called “Keywords”.
The type of Scheme strings. (Implemented using
java.lang.String
for immutable strings, andgnu.lists.FString
for mutable strings. Both of these implement the interfacejava.lang.CharSequence
. In the future, we may change the representation for strings containing “surrogate characters”, for efficient indexing.) (Compatibility note: Previous versions of Kawa implemented always usedgnu.lists.FString
.)
The type of Scheme character values. This is a sub-type of
Object
, in contrast to typechar
, which is the primitive Javachar
type.
This type name is a special case. It specifies the class
java.lang.String
. However, coercing a value toString
is done by invoking thetoString
method on the value to be coerced. Thus it "works" for all objects. It also works for#!null
.When Scheme code invokes a Java methods any parameter whose type is
java.lang.String
is converted as if it was declared as aString
.
More will be added later.
A type specifier can also be one of the primitive Java types.
The numeric types long
, int
, short
,
byte
, float
, and double
are converted from the
corresponding Scheme number classes. Similarly, char
can be converted to and from Scheme characters. The type
boolean
matches any object, and the result is false
if and only if the actual argument is #f
.
(The value #f
is identical to Boolean.FALSE
,
and #t
is identical to Boolean.TRUE
.)
The return type void
indicates that no value is returned.
A type specifier can also be a fully-qualified Java class name
(for example java.lang.StringBuffer
). In that case,
the actual argument is cast at run time to the named class.
Also, java.lang.StringBuffer[]
represents
an array of references to java.lang.StringBuffer
objects.
Kawa has some basic support for parameterized (generic) types. The syntax:
Type[Arg1 Arg2 ... ArgN]
is more-or-less equivalent to Java's:
Type<Arg1, Arg2, ..., ArgN>
This is a work-in-progress. You can use this syntax with fully-qualified class names, and also type aliases:
(define v1 ::gnu.lists.FVector[gnu.math.IntNum] [4 5 6]) (define-alias fv gnu.lists.FVector) (define v2 ::fv[integer] [5 6 7]) (define-alias fvi fv[integer]) (define v3 ::fvi [6 7 8])
Scheme defines a number of standard type testing predicates.
For example (vector? x)
is #t
if and only if
x
is a vector.
Kawa generalizes this to arbitrary type names:
If T
is a type-name (that is in scope at compile-time),
then
is a one-argument function that returns
T
?#t
if the argument is an instance of the type
,
and T
#f
otherwise:
(gnu.lists.FVector? #(123)) ⇒ #t (let ((iarr (int[] 10))) (int[]? iarr)) ⇒ #t
To convert (coerce) the result of an expression value
to a
type T
use the syntax: (->
.
T
value
)
(->float 12) ⇒ 12.0f0
In general:
(T
?x
) ⇒ (instance?x
T
) (->T
x
) ⇒ (asT
x
)
Returns
#t
iffvalue
is an instance of typetype
. (Undefined iftype
is a primitive type, such asint
.)