Scheme is a dynamically and strongly typed language. That is, every value knows its type at run-time, and the type determines what kind of operations can be applied on the value.
The Scheme standard is pretty simple on types; basically, an object being a type means the type predicate returns true on the object, and that’s all. Gauche adopts a bit more elaborated system—types are first-class objects and you can query various information.
In Gauche, types are conventionally
named with brackets
It’s nothing syntactically special with these brackets; they’re valid
characters to consist of variable names.
|• Prescriptive and descriptive types|
|• Generic type predicates|
|• Predefined classes|
|• Type expressions and type constructors|
|• Native types|
Types are used in two ways. A type can be seen as a template of
the actual values (instances)—that is, a type prescribes
the structure of, and possible operations on, its instances.
In Gauche, such prescriptive types are represented by classes.
Every value in Gauche belongs to a class, which can be queried with the
class-of procedure. You can also define your own class with
(see Object system, and see Record types).
A type can also be seen as a constraint of a given expression—that is, a type describes what characteristics a certain expression must have. Gauche has entities to represent such descriptive types separate from classes. Descriptive types are created with type constructors (see Type expressions and type constructors). We also have a set of predefined descriptive types to communicate to C libraries (see Native types.
A value is an instance of a class. For example,
1 is an instance of
is an instance of
<string>. This presctiptive type relationship
is checked with
(is-a? 1 <integer>) and
(is-a? "xyz" <string>) both returns
On the other hand, you may have a procedure that takes either a number
or a string as an argument. “A number or a string” is a type constraint,
and can be expressed as a descriptive type,
(</> <number> <string>).
Descriptive type relationship is checked with
(of-type? 1 (</> <number> <string>)) and
(of-type? "xyz" (</> <number> <string>)) both returns
(assume-type arg (</> <number> <string>))
would raise an error if arg doesn’t satisfy the given type constraint.
A “type predicate” is a predicate that tells if an object is of a specific
number? tells you if the argument is a number.
Since types are first-class in Gauche, we have predicates that can tell
an object is of a given type, as well as predicates to ask
the relationship between types.
This is a prescriptive types predicate. Returns true iff obj is an instance of class or an instance of descendants of class.
(is-a? 3 <integer>) ⇒ #t (is-a? 3 <real>) ⇒ #t (is-a? 5+3i <real>) ⇒ #f (is-a? :foo <symbol>) ⇒ #f
Note: If obj’s class has been redefined,
triggers instance update.
See Class redefinition for the details.
This is a descriptie type predicate.
Returns true iff obj satisfies the constraints described by type,
which can be either a class (in that case, this is the same as
or a descriptive type (see Type expressions and type constructors below).
(of-type? 1 (</> <number> <string>)) ⇒ #t (of-type? "a" (</> <number> <string>)) ⇒ #t (of-type? 'a (</> <number> <string>)) ⇒ #f
#t if a type sub is a subtype of a type super
(includes the case that sub is super).
In general, if sub is a subtype of super, an object that satisfies the constraints of sub also satisfies the constraints of super, so you can use the object where objects of type super are expected. In other words, sub is more restrictive than super. If both sub and super are classes, sub being a subtype of super means sub is a subclass of super.
(subtype? <integer> <real>) ⇒ #t (subtype? <char> (</> <char> <string>)) ⇒ #t (subtype? (</> <integer> <string>) (</> <number> <string>)) ⇒ #t
Note that we’re not rigorous on this “substitution principle”,
for we don’t aim at guaranteeing type safety through static analysis.
For example, “list of integers” can be used in place of a generic list
most of the time, and
(subtype? (<List> <integer>) <list>) is
#t. However, if list is mutated, you can’t replace
generic list with a list of integers—because mutators may try to
set non-integer in the list. That kind of cases needs to be handled
separately, not on relying solely on
Both arguments must be classes. Returns
#t iff sub is
a subclass of super. A class is regarded as a subclass of
Predefined classes are bound to a global variable; Gauche’s We’ll introduce classes for each built-in type as we go through this chapter. Here are a few basic classes to start with:
This class represents the supertype of all the types in Gauche.
That is, for any class
(subtype? X <top>) is
and for any object
(is-a? x <top>) is
This class represents the subtype of all the types in Gauche.
For any class
(subtype? <bottom> X) is
and for any object
(is-a? x <bottom>) is
There’s no instance of
<bottom> is subtype of other types,
the class precedence list (CPL) of
<bottom> only contains
<top>. It’s because it isn’t
always possible to calculate a linear list of all the types.
Even if it is possible, it would be expensive to check and update the
<bottom> every time a new class is defined or
an existing class is redefined. Procedures
One of use case of
See Procedure class and applicability.
This class represents a supertype of all user-defined classes.
Returns a class metaobject of obj.
(class-of 3) ⇒ #<class <integer>> (class-of "foo") ⇒ #<class <string>> (class-of <integer>) ⇒ #<class <class>>
Note: In Gauche, you can redefine existing user-defined classes.
If the new definition has different configuration of the instance,
class-of on existing instance triggers instance updates;
see Class redefinition for the details. Using
current-class-of suppresses instance updates
(see Accessing instance).
Types are first-class objects manipulatable at runtime in Gauche, but they must be known at compile-time in order to do optimizations or static analysis. In certain places, Gauche requires the the value of type-yielding expressions to be statically computable. We call such expressions type expressions.
A type expression is either a global variable reference that are constantly bound to a type, or a call of type constructor:
<type-expression> : <global-variable-constantly-bound-to-a-type> | <type-constructor-call> <type-constructor-call> : (<type-constructor> <type-constructor-argument> ...) <type-constructor-argument> : <type-expression> | <integer-constant> | -> | *
All built-in classes, such as
<integer>, are statically bound
(in precise terms, they are “inlinable” binding). If you try to alter
it, a warning is issued, and the further bahvior will be undefined.
In future, we’ll make it an error to alter the binding of
global variables bount to types. Classes defined with
define-record-type are also bound as inlinable.
Type constructors are special classes whose instances are descriptive types. Type constructors can be invoked as if they are a procedure, but <type-constructor-call> above is recognized by the compiler and the derived type is computed at compile-time. So, at runtime you only see the resulting derived type instance.
<?> is a type constructor that creates
“maybe”-like type, e.g. “an integer or
(Do not confuse this with Maybe type
defined in srfi-189, see Maybe and Either optional container types).
A type expression
(<?> <integer>) yields such type
(<?> <integer>) ⇒ #<? <integer>>
It looks like a procedure call, but it s computed at compile time:
gosh> (disasm (^ (<?> <integer>))) CLOSURE #<closure (#f)> === main_code (name=#f, cc=0x7f5cd76ab540, codevec=...): signatureInfo: ((#f)) 0 CONST-RET #<? <integer>>
Type must be a type expression.
This creates a maybe-like type, that is, the object is either
of type or
that the value is invalid (e.g. the return value of
This type has ambiguity when
#f can be a meaningful value, but
traditionally been used a lot in Scheme, for it is lightweight.
We also have a “proper” Maybe type in srfi-189
(see Maybe and Either optional container types) but that involves
extra allocation to wrap the value.
Type … must be type expressions.
This creates a sum type of type ….
(</> <string> <symbol>) is a type that is
either a string or a symbol.
Type … must be type expressions, except that the
last argument that may be an identifier
This creates a product type of type ….
Actually, this type is looser than the product types in typical
statically-typed languages. Our tuple is a subtype of list, with
types of each positional element are restricted with type ….
(<Tuple> <integer> <string>) is a list of two
elements, the first one being an integer and the second being a string.
(of-type? '(3 "abc") (<Tuple> <integer> <string>)) ⇒ #t
If you need a more strict and disjoint product type, you can just create a class or a record.
If the last argument is
*, the resulting type allows
extra elements after the typed elements.
(of-type? '(3 "abc" 1 2) (<Tuple> <integer> <string> *)) ⇒ #t
Type … must be type expressions, except that the
one right before
-> and the last one may be an identifier
This creates a procedure type, with the constraints in
arguments and return types.
The type … before
-> are the argument types,
and the ones after
-> are the result types.
* in the last of argument type list and/or result type list
indicates extra elements are allowed.
In vanilla Scheme, all procedures belong to just one type, that responds
procedure?. It is simple and flexible, but sometimes the
resolution is too coarse to do reasoning on the program.
In Gauche, we can attach more detailed type information in procedures.
In the current version, some built-in procedures already have such
type information, that can be retrieved with
(procedure-type cons) ⇒ #<^ <top> <top> -> <pair>>
Not all procedures have such information, though. Do not expect the rigorousness of statically typed languages.
At this moment, Scheme-defined procedures treats all argument types
<top>. We’ll provide a way to attach type info in future.
Type must be a type expression.
These create a list or a vector type whose elements are of type,
(<List> <string>) is a list of strings.
The optional arguments must be a literal real numbers that limit
the minimum and maximum length of the list or the vector.
When omitted, min-length
is zero and max-length is
Native types are a set of predefined descriptive types that bridge Scheme types and C types. They can be used to access binary blobs passed from a foreign functions, for example. They’ll also be used for FFI.
An example of native types is
<int16>, which corresponds to
int16_t. You can use
<int16> to check if a Scheme
object can be treated as that type:
(of-type? 357 <int16>) ⇒ #t (of-type? 50000 <int16>) ⇒ #f
Or obtain size and alignemnt info:
gosh> (describe <int16>) #<native-type <int16>> is an instance of class <native-type> slots: name : <int16> super : #<class <integer>> c-type-name: "int16_t" size : 2 alignment : 2
Predefined native types. The correspondence of Scheme and C types are shown below:
Native type Scheme C Notes ----------------------------------------------------------------- <fixnum> <integer> ScmSmallInt Integers within fixnum range <int> <integer> int Integers representable in C <int8> <integer> int8_t <int16> <integer> int16_t <int32> <integer> int32_t <int64> <integer> int64_t <short> <integer> short <long> <integer> long <uint> <integer> uint Integers representable in C <uint8> <integer> uint8_t <uint16> <integer> uint16_t <uint32> <integer> uint32_t <uint6r> <integer> uint64_t <ushort> <integer> ushort <ulong> <integer> ulong <float> <real> float Unboxed value casted to float <double> <real> double <size_t> <integer> size_t System-dependent types <ssize_t> <integer> ssize_t <ptrdiff_t> <integer> ptrdiff_t <off_t> <integer> off_t <void> - void (Used only as a return type. Scheme function returns #<undef>)