# (lispkit type)

Library `(lispkit type)` provides a simple, lightweight type abstraction mechanism. It allows for creating new types at runtime that are disjoint from all existing types. The library provides two different types of APIs: a purely procedural API for type creation and management, as well as a declarative API which allows for introducing extensible types in a declarative fashion.

A core feature of the type abstraction mechanism is a means that allows for determining the type of any LispKit value. Procedure `type-of` implements this type introspection feature. It returns a *type tag*, i.e. a symbol representing the type, for any given object.

## Usage of the procedural API

New types are created with procedure `make-type`. `make-type` accepts one argument, which is a type label. The type label is either a string or a symbol that is used for debugging purposes but also for defining the string representation of a *type tag* that represents the new type.

The following line introduces a new type for *intervals*:

```scheme
(define-values (interval-type-tag
                new-interval
                interval?
                interval-ref
                make-interval-subtype)
  (make-type 'interval))
```

`(make-type 'interval)` returns five values:

* `interval-type-tag` is an uninterned symbol with the string representation `"interval"` which represents the new type.
* `new-interval` is a procedure which takes one argument, the internal representation of the interval, and returns a new object of the new interval type.
* `interval?` is a type test predicate which accepts one argument and returns `#t` if the argument is of the new interval type, and `#f` otherwise.
* `interval-ref` takes one object of the new interval type and returns its internal representation. `interval-ref` is the inverse operation of `new-interval`.
* `make-interval-subtype` is a type generator (similar to `make-type`), a function that takes a type label and returns five values representing a new subtype of the interval type.

Now it is possible to implement a constructor `make-interval` for intervals:

```scheme
(define (make-interval lo hi)
  (if (and (real? lo) (real? hi) (<= lo hi))
      (new-interval (cons (inexact lo) (inexact hi)))
      (error "make-interval: illegal arguments" lo hi)))
```

`make-interval` first checks that the constructor arguments are valid and then calls `new-interval` to create a new interval object. Interval objects are represented via pairs whose *car* is the lower bound, and *cdr* is the upper bound. Nevertheless, pairs and interval objects are distinct values as the following code shows:

```scheme
(define interval-obj (make-interval 1.0 9.5))
(define pair-obj (cons 1.0 9.5))

(interval? interval-obj)        ⇒ #t
(interval? pair-obj)            ⇒ #f
(equal? interval-obj pair-obj)  ⇒ #f
```

The type is displayed along with the representation in the textual representation of interval objects: `#interval:(1.0 . 9.5)`.

Below are a few functions for interval objects. They all use `interval-ref` to extract the internal representation from an interval object and then operate on the internal representation.

```scheme
(define (interval-length interval)
  (let ((bounds (interval-ref interval)))
    (- (cdr bounds) (car bounds))))

(define (interval-empty? interval)
  (zero? (interval-length interval)))
```

The following function calls show that `interval-ref` fails with a type error if its argument is not an interval object.

```scheme
(interval-length interval-obj)
⇒ 8.5
(interval-empty? '(1.0 . 1.0))
⇒ [error] not an instance of type interval: (1.0 . 1.0)
```

## Usage of the declarative API

The procedural API provides the most flexible way to define a new type in LispKit. On the other hand, this approach comes with two problems:

1. a lot of boilerplate needs to be written, and
2. programmers need to be experienced to correctly encapsulate new data types and to provide means to extend them.

These problems are addressed by the declarative API of `(lispkit type)`. At the core, this API defines a syntax `define-type` for declaring new types of data. `define-type` supports defining simple, encapsulated types as well as provides a means to make types extensible.

The syntax for defining a simple, non-extensible type has the following form:

`(define-type` *name* *name?*\
&#x20;   `((`*make-name* *x* ...`)` *expr* ...`)`\
&#x20;   *name-ref*\
&#x20;   *functions*`)`

*name* is a symbol. It defines the name of the new type and the identifier *name* is bound to a type tag representing the new type. *name?* is a predicate for testing whether a given object is of type *name*. *make-name* defines a constructor which returns a value representing the data of the new type. *name-ref* is a function to unwrap values of type *name*. It is optional and normally not needed since *functions* can be declared such that the unwrapping happens implicitly. All functions defined via `define-type` take an object (usually called `self`) of the defined type as their first argument.

There are two forms to declare a function as part of `define-type`: one providing access to `self` directly, and one only providing access to the unwrapped data value:

`((`*name-func self y ...*`)` *expr* ...`)`

provides access directly to *self* (which is a value of type *name*), and

`((`*name-func* `(`*repr*`)` *y ...*`)` *expr* ...`)`

which provides access only to the unwrapped data `repr`.

With this new syntax, type `interval` from the section describing the procedural API, can now be re-written like this:

```scheme
(define-type interval
  interval?
  ((make-interval lo hi)
    (if (and (real? lo) (real? hi) (<= lo hi))
        (cons (inexact lo) (inexact hi))
        (error "make-interval: illegal arguments" lo hi)))
  ((interval-length (bounds))
    (- (cdr bounds) (car bounds)))
  ((interval-empty? self)
    (zero? (interval-length self))))
```

`interval` is a standalone type which cannot be extended. `define-type` provides a simple means to make types extensible such that subtypes can be created reusing the base type definition. This is done with a small variation of the `define-type` syntax:

`(define-type` (*name* *super*) *name?*\
&#x20;   `((`*make-name* *x* ...`)` *expr* ...`)`\
&#x20;   *name-ref*\
&#x20;   *functions*`)`

In this syntax, *super* refers to the type extended by *name*. All extensible types extend another extensible type and there is one supertype called `obj` provided by library `(lispkit type)` as a primitive.

With this syntactic facility, `interval` can be easily re-defined to be extensible:

```scheme
(define-type (interval obj)
  interval?
  ((make-interval lo hi)
    (if (and (real? lo) (real? hi) (<= lo hi))
        (cons (inexact lo) (inexact hi))
        (error "make-interval: illegal arguments" lo hi)))
  ((interval-length (bounds))
    (- (cdr bounds) (car bounds)))
  ((interval-empty? self)
    (zero? (interval-length self))))
```

It is now possible to define a `tagged-interval` data structure which inherits all functions from `interval` and encapsulates a tag with the interval:

```scheme
(define-type (tagged-interval interval)
  tagged-interval?
  ((make-tagged-interval lo hi tag)
    (values lo hi tag))
  ((interval-tag (bounds tag))
    tag))
```

`tagged-interval` is a subtype of `interval`; i.e. values of type `tagged-interval` are also considered to be of type `interval`. Thus, `tagged-interval` inherits all function definitions from `interval` and defines a new function `interval-tag` just for `tagged-interval` values. Here is some code explaining the usage of `tagged-interval`:

```scheme
(define ti (make-tagged-interval 4.0 9.0 'inclusive))
(tagged-interval? ti)       ⇒ #t
(interval? ti)              ⇒ #t
(interval-length ti)        ⇒ 5.0
(interval-tag ti)           ⇒ inclusive
(interval-tag interval-obj)
⇒ [error] not an instance of type tagged-interval: #interval:((1.0 . 9.5))
```

Constructors of extended types, such as `make-tagged-interval` return multiple values: all the parameters for a super-constructor call and one additional value (the last value) representing the data provided by the extended type. In the example above, `make-tagged-interval` returns three values: `lo`, `hi`, and `tag`. After the constructor `make-tagged-interval` is called, the super-constructor is invoked with arguments `lo` and `hi`. The result of `make-tagged-interval` is a `tagged-interval` object consisting of two state values contained in a list: one for the supertype `interval` (consisting of the bounds `(lo . hi)`) and one for the subtype `tagged-interval` (consisting of the tag). This can also be seen when displaying a `tagged-interval` value:

```scheme
ti ⇒ #<tagged-interval (4.0 . 9.0) inclusive>
```

This is also the reason why function `interval-tag` gets access to two unwrapped values, `bounds` and `tag`: one (`bounds`) corresponds to the value associated with type `interval`, and the other one (`tag`) corresponds to the value associated with type `tagged-interval`.

## Type introspection

LispKit defines a type tag for every different type of object. The type of objects created with the `make-type` facility are represented with unintered symbols whose string representation matches the type label provided to `make-type`. Enum and record objects come with a corresponding type tag as well. All standard, built-in types use interned symbols as type tags. Procedure `type-of` returns the type tag associated with the value of the argument of `type-of`.

**(type-of&#x20;*****obj*****)**     <img src="/files/STqjiJsrexexyFklGQwH" alt="" data-size="line">

Returns a list of type tags, i.e. symbols, associated with the type of *obj*. The type tags in the list are sorted, starting with the most specific type. If no type can be determined, `type-of` returns the empty list.

Type tags of custom types are returned as the first value of `make-type`. Type tags of records can be retrieved via `record-type-tag` from the corresponding record type object. Similarly, type tags of enum objects are accessible via `enum-type-type-tag`. Type tags of native types are typically exported by the libraries providing access to the type. All standard, built-in types are represented by the following interned symbols: `void`, `end-of-file`, `null`, `boolean`, `symbol`, `fixnum`, `bignum`, `integer`, `rational`, `flonum`, `real`, `complex`, `number`, `char`, `string`, `bytevector`, `pair`, `list`, `box`, `mpair`, `array`, `vector`, `gvector`, `values`, `procedure`, `parameter`, `promise`, `environment`, `hashtable`, `port`, `input-port`, `output-port`, `record-type`, and `error`. For undefined values `#f` is returned.

## Type management

**(make-type&#x20;*****type-label*****)**     <img src="/files/STqjiJsrexexyFklGQwH" alt="" data-size="line">

Based on a string or symbol *type-label*, creates a new, unique type, and returns a type tag representing the new type as well as five values dealing with this new type:

1. The first value is a type tag, i.e. an uninterned symbol representing the new type which has the same string representation as *type-label*.
2. The second value is a unary procedure returning a new object of the new type which is wrapping the argument of the procedure (i.e. the internal representation of the new type).
3. The third value is a type test predicate which accepts one argument and returns `#t` if the argument is of the new type, and `#f` otherwise.
4. The fourth value is a procedure which takes one object of the new type and returns its internal representation (that was passed to the procedure returned as the second value).
5. The fifth value is a type generator procedure (similar to `make-type`), a function that takes a type label and returns five values representing a new subtype of the new type.

*type-label* has two uses: it is used for debugging purposes and determines the string representation of the corresponding type tag. It is shown when an object's textual representation is used. In particular, calling the third procedure (the type de-referencing function) will result in an error message exposing the type label if the argument is of a different type than expected.

**(define-type&#x20;*****name name?*****&#x20;((*****make-name x ...*****)&#x20;*****e ...*****)&#x20;*****func*****&#x20;...)**     <img src="/files/gEcsZuRGyhWFw4tM60U7" alt="" data-size="line">\
\&#xNAN;**(define-type&#x20;*****name name?*****&#x20;((*****make-name x ...*****)&#x20;*****e ...*****)&#x20;*****ref*** ***func*****&#x20;...)**

Defines a new standalone type *name* consisting of a type test predicate *name?*, a constructor *make-name*, and an optional function *ref* used to unwrap values of type *name*. *ref* is optional and normally not needed since functions *func* can be declared such that the unwrapping happens implicitly. All functions *func* defined via `define-type` take an object (usually called `self`) of the defined type as their first argument.

There are two ways to declare a function as part of `define-type`: one providing access to `self` directly, and one only providing access to the unwrapped data value:

* `((`*name-func self y ...*`)` *expr* ...`)` provides access directly to *self* (which is a value of type *name*), and
* `((`*name-func* `(`*repr*`)` *y ...*`)` *expr* ...`)` provides access only to the unwrapped data `repr`.

**(define-type (*****name super*****)&#x20;*****name?*****&#x20;((*****make-name x ...*****)&#x20;*****e ...*****)&#x20;*****func*****&#x20;...)**     <img src="/files/gEcsZuRGyhWFw4tM60U7" alt="" data-size="line">\
\&#xNAN;**(define-type (*****name super*****)&#x20;*****name?*****&#x20;((*****make-name x ...*****)&#x20;*****e ...*****)&#x20;*****ref*** ***func*****&#x20;...)**

This variant of `define-type` defines a new extensible type *name* extending supertype *super*, which also needs to be an extensible type. A new extensible type *name* comes with a type test predicate *name?*, a constructor *make-name*, and an optional function *ref* used to unwrap values of type *name*. *ref* is optional and normally not needed since functions *func* can be declared such that the unwrapping happens implicitly. All functions *func* defined via `define-type` take an object (usually called `self`) of the defined type as their first argument.

There are two ways to declare a function as part of `define-type`: one providing access to `self` directly, and one providing access to the unwrapped data values (one for each type in the supertype chain):

* `((`*name-func self y ...*`)` *expr* ...`)` provides access directly to *self* (which is a value of type *name*), and
* `((`*name-func* `(`*repr* ...`)` *y ...*`)` *expr* ...`)` provides access only to the unwrapped data values `repr`.

Constructors of extended types return multiple values: all the parameters for a super-constructor call and one additional value (the last value) representing the data provided by the extended type.

**obj**     <img src="/files/viHiVHsCY8Wn2TeCgWJj" alt="" data-size="line">

The supertype of all extensible types defined via `define-type`. The type tag of `obj` can be retrieved via `(type-of obj)`.

**(obj-type-tag&#x20;*****etype*****)**     <img src="/files/viHiVHsCY8Wn2TeCgWJj" alt="" data-size="line">

Returns the type tag associated with the supertype of all extensible types `obj`.

**(extensible-type?&#x20;*****obj*****)**     <img src="/files/STqjiJsrexexyFklGQwH" alt="" data-size="line">

Returns `#t` if *obj* is an instance of an extensible type. For example, `(extensible-type? obj)` returns `#t`.

**(extensible-type-tag&#x20;*****etype*****)**     <img src="/files/STqjiJsrexexyFklGQwH" alt="" data-size="line">

Returns the type tag associated with the extensible type `etype` defined by the `define-type` form.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://www.lisppad.app/libraries/lispkit/lispkit-type.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
