Concept:Type

Implementation support for type checking

Types are useful to programming. Many Scheme implementations provide types in one form or another, but this is not always consistent across them.

On this page, a hypothetical procedure indexed-string will be used to illustrate the syntax of different type systems:

    (define (indexed-string str i)
      (string-append str (number->string i)))

Given that most implementations focus on procedure argument / return checking, this example is almost always representative of what they provide in default package. See the section below for more expansive set of typing operations.

SRFI-253 and SRFI-273

It is recommended to use these type-checking SRFIs to write properly checked Scheme code. Utilities like define-checked and define-record-type-checked make it easy to port code from plain Scheme to SRFI-253, while SRFI-273 adds some quality-of-life features on top.

    (define-checked (indexed-string (str string?) (i integer?)) => (string?)
      (string-append str (number->string i)))

SRFI-253 provided forms, in more detail:

  • (check-arg predicate? value) checks the given value for abiding to the predicate
  • (values-checked (predicate? ...) value ...) is same as values, but with checking added on top
  • (check-case value clause ...) is like case, but every clause starts with a predicate that the provided value must satisfy for this clause to be entered
  • (lambda-checked args body ...) extends lambda by allowing (name predicate?) shape of args to enable type checking
  • (case-lambda-checked clause ...) extends case-lambda in the same way
  • (define-checked name predicate form) and (define-checked (name . args) [=> (return? ...)] body ... are same as two forms of define, but with checking of bound values and procedure args / returns, respectively. Argument shape is the same as in lambda-checked
  • (define-record-type-checked name (constructor arg ...) pred? field ...) is the same as define-record-type, but every field must have a predicate following the name of the field

SRFI-273 provided forms:

  • (define-check name predicate?) defines a new check useable in SRFI-253 and SRFI-273 forms
  • (declare-checked name predicate?) and (declare-checked (name . args) [=> (return?)]) are matching the shapes of define-checked, but are used for pre/post-declaration of types for another procedure / variable without define-ing it
  • (define-values-checked (name ...) (predicate? ...) form) as a double to variable-binding define-checked

This is a pretty involved system that surpasses what most implementations provide. Use it if you can.

Chicken

Chicken has a really involved type system. It is described in the Types wiki page:

https://wiki.call-cc.org/man/5/Types

Provided niceties:

  • Full-blown procedure types
  • Union and complement types via or and not
  • Type variables via forall
  • Parametric sequence types like vector and list
  • Multiple valued procedure types
  • noreturn and undefined types for return values
    (import (chicken type))
    (: indexed-string (string integer -> string))
    (define (indexed-string str i)
      (string-append str (number->string i)))

Provided operations (only have effect during compilation via csc:)

: identifier type

Declares a type of a given identifier. There's a variety of procedure types with a general type shape of (arg ... -> ret ...).

the type value

Guarantees that the value abides by a given type and returns it.

assume ((variable type) ...) body ...

Guarantees that every variable abides by the respective type for the duration of body.

Kawa

Kawa allows type annotations for identifiers in binding forms. Every e.g. procedure parameter can be followed by ::type annotation to make it type-checked. Types can range from mere ::procedure to parameterized and Java types like ::gnu.lists.FVector[gnu.math.IntNum]

There's also an as special form that tries to coerce a given value to a type and fails if the type is not convertible. And auto-generated conversion and checking procedures per each implementation-provided type:

https://www.gnu.org/software/kawa/Type-tests-and-conversions.html

    (define (indexed-string str::String i::integer)::String
      (string-append str (number->string i)))

Guile

There are no native types in Guile, but there are class-dispatching generics backed by GOOPS. Defined by define-generic and define-method with GOOPS classes like <string> provided with arguments.

    (import (oop goops))
    (define-generic indexed-string)
    (define-method (indexed-string (str <string>) (i <integer>))
      (string-append str (number->string i)))

ChibiScheme

Chibi provides generic procedures as part of (chibi generic) library. These are defined with (define-generic name) and extended with define-method with arguments optionally having a (name predicate?) shape for predicate-based generic dispatch.

While it's not types per se, it's the closest one gets to type checking in Chibi, and that's what e.g. SRFI-253 uses in its Chibi implementation.

    (import (chibi generic))
    (define-generic indexed-string)
    (define-method (indexed-string (str string?) (i integer?))
      (string-append str (number->string i)))

Chibi also allows limited type introspection. (chibi ast) library provides procedures like type-of, type-name, and opcode-param-type among other useful utilities.

Gambit

Gambit provides type-checked procedures via a special define-procedure form:

    (define-procedure (indexed-string (str string) (i number))
      (string-append str (number->string i)))

The full listing of types is available in Gambit Info manual in a "Type specifiers" subsection:

https://github.com/gambit/gambit/blob/master/doc/gambit.txi

Bigloo

Bigloo allows adding type annotations to procedure arguments or variable definitions, using the ::type suffixes to symbols:

    (define (indexed-string::bstring str::bstring i::integer)
      (string-append str (number->string i)))

See typing manual for the places allowing type annotations:

https://github.com/manuel-serrano/bigloo/blob/master/manuals/typing.texi

And see foreign interface manual for possible type names:

https://github.com/manuel-serrano/bigloo/blob/master/manuals/foreign.texi

STklos

STklos does not have native types, but it has generic functions and classes which can enforce some typing. Given the use of define-generic and define-method:

    (define-method indexed-string ((str <string>) (i <integer>))
      (string-append str (number->string i)))