On this page:
2.1.1 Nominal v.s. Structural Types
2.1.2 Struct-Based Types and Struct Type Properties
2.1.3 Type Implementations and Generativity
2.1.4 Defining Types
2.1.5 Compile-Time Type Information

2.1 Interfaces Common to All Types🔗ℹ

The rebellion/type module is broken down into a collection of modules, such as rebellion/type/record and rebellion/type/tuple. Each module is meant for working with a specific kind of data type. However, these modules have a few things in common, documented below.

2.1.1 Nominal v.s. Structural Types🔗ℹ

Any type created in rebellion/type is a nominal type, not a structural type. This means creating a new type, such as with define-record-type, creates a new named unique type that is distinct from all other types. The functions created by one use of define-record-type will not work on instances created via another use of define-record-type, even if both types are named the same and have exactly the same fields.

2.1.2 Struct-Based Types and Struct Type Properties🔗ℹ

All types are created using Racket structure types, and the created struct types can have structure type properties attached to them. Each module typically provides default structure type properties for the types it creates, based on how its types are typically used. These defaults can be freely overriden when desired.

2.1.3 Type Implementations and Generativity🔗ℹ

The structure, or shape of a type is distinct from an implementation of that type. Each rebellion/type module reflects this distinction by providing two different interfaces for shapes and implementations. A rebellion/type module for working with kind types provides:

A kind descriptor can be created for a type using the make-kind-implementation function provided by the corresponding rebellion/type/kind module. Multiple calls to such a function with the same type will produce distinct implementations that are not equal? to each other, meaning that the rebellion/type modules create generative types.

Type descriptors may be uninitialized. An uninitialized type descriptor cannot be used to create or interact with instances of the type until after initialization is complete. Uninitialized type descriptors are fairly rare: they can only be obtained via the #:property-maker mechanism for specifying structure type properties of created types. This is because the property-making function needs to receive the type descriptor in order to return implementations of type properties, but this happens before the type is created. Property makers can’t use the descriptor’s constructor and accessor immediately, but they can refer to them in implementations of properties such as prop:custom-write (since by the time the constructor or accessor is actually used, the type is created and the descriptor is initialized). This delayed initialization step is necessary to allow property makers to be defined without mutual recursion and in separate modules from the type definitions they’re used for, allowing reuse of generic property makers.

2.1.4 Defining Types🔗ℹ

Each rebellion/type/kind module provides a define-kind-type form that creates a new type and binds its constructor, predicate, and accessors to variables. These forms are how most users of the rebellion/type modules are expected to create types, similar to how most Racket struct types are created with the struct form rather than the dynamic make-struct-type function.

2.1.5 Compile-Time Type Information🔗ℹ

Some types encapsulate compile-time information about their name, size, fields, and other properties inside a type binding. Type bindings are used by macros such as enum-out to generate code specialized to that type. Type bindings are created by type definition macros and extracted using syntax classes. For example, define-enum-type creates a type binding using define-syntax and the corresponding syntax class enum-id extracts that binding.

At present, not all kinds of types support type bindings — follow issue #179 for updates on this feature and the current status of supported type bindings. More advanced compile-time type functionality such as a full static type system and checker are out of scope for now, but it is hoped that such an effort can either build on top of the Rebellion type libraries. If you are interested in such a project please reach out to the Rebellion project owners, we’d love to hear more!