-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathtype.scrbl
159 lines (132 loc) · 7.52 KB
/
type.scrbl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#lang scribble/manual
@(require (for-label racket/base
rebellion/type/enum
rebellion/type/enum/binding
rebellion/type/record
rebellion/type/singleton
rebellion/type/tuple
rebellion/type/wrapper)
(submod rebellion/private/scribble-evaluator-factory doc)
(submod rebellion/private/scribble-cross-document-tech doc)
scribble/example)
@(define make-evaluator
(make-module-sharing-evaluator-factory
#:public (list 'rebellion/type/record
'rebellion/type/singleton
'rebellion/type/tuple
'rebellion/type/wrapper)
#:private (list 'racket/base)))
@title[#:style (list 'toc)]{Data Types}
@defmodule[rebellion/type]
A @deftech{data type} or simply @deftech{type} is a set of values together with
1) some basic operations on those values and 2) a predicate to test whether a
value is within that set. There are many different kinds of data types, with
different basic operations and meant for different use cases.
@(examples
#:eval (make-evaluator) #:once #:label #f
(define-tuple-type point (x y))
(point 7 42))
@(examples
#:eval (make-evaluator) #:once #:label #f
(define-record-type circle (radius color))
(circle #:radius 5 #:color 'red))
@(examples
#:eval (make-evaluator) #:once #:label #f
(define-wrapper-type inches)
(inches 17))
@(examples
#:eval (make-evaluator) #:once #:label #f
(define-singleton-type undefined)
undefined)
@local-table-of-contents[]
@section{Interfaces Common to All Types}
The @racketmodname[rebellion/type] module is broken down into a collection of
modules, such as @racketmodname[rebellion/type/record] and @racketmodname[
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.
@subsection[#:tag "nominal"]{Nominal v.s. Structural Types}
Any type created in @racketmodname[rebellion/type] is a @deftech{nominal type},
not a @deftech{structural type}. This means creating a new type, such as with
@racket[define-record-type], creates a new named unique type that is distinct
from all other types. The functions created by one use of @racket[
define-record-type] will not work on instances created via another use of
@racket[define-record-type], even if both types are named the same and have
exactly the same fields.
@subsection{Struct-Based Types and Struct Type Properties}
All types are created using Racket @tech/reference{structure types}, and the
created struct types can have @tech/reference{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.
@subsection{Type Implementations and Generativity}
The structure, or @deftech{shape} of a type is distinct from an @deftech{
implementation} of that type. Each @racketmodname[rebellion/type] module
reflects this distinction by providing two different interfaces for shapes and
implementations. A @racketmodname[rebellion/type] module for working with @var[
kind] types provides:
@itemlist[
@item{A @racket[_kind]@racketidfont{-type?} type which describes the shape of a
@racket[_kind] type but does not provide any constructors, accessors, or
predicates. Two @racket[_kind]@racketidfont{-type?} values are equal whenever
the names, field counts, etc. of the types they describe are equivalent. That
is, @racket[_kind]@racketidfont{-type?} values are compared using structural
equality. The term @deftech{shape of a type} or simply @tech{shape}, refers
to a @racket[_kind]@racketidfont{-type?} value. When used without any
qualification, the term @tech{type} typically refers to such a value as
well.}
@item{A @racket[_kind]@racketidfont{-descriptor?} type which provides an actual
implementation of a @racket[_kind] type, including a predicate and any
relevant constructors and accessors. Such an implementation is called a
@deftech{type descriptor}, and allows generically operating on unknown types
at runtime. At runtime, the term @deftech{implementation of a type}, or
simply @tech{implementation}, refers to a type descriptor.}]
A @racket[_kind] descriptor can be created for a type using the
@racketidfont{make-}@racket[_kind]@racketidfont{-implementation} function
provided by the corresponding @racketmodfont{rebellion/type/}@racket[_kind]
module. Multiple calls to such a function with the same type will produce
distinct implementations that are not @racket[equal?] to each other, meaning
that the @racketmodname[rebellion/type] modules create @deftech{generative
types}.
Type descriptors may be @emph{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 @racket[#: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 @emph{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
@racket[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.
@subsection{Defining Types}
Each @racketmodfont{rebellion/type/}@racket[_kind] module provides a
@racketidfont{define-}@racket[_kind]@racketidfont{-type} form that creates a new
type and binds its constructor, predicate, and accessors to variables. These
forms are how most users of the @racketmodname[rebellion/type] modules are
expected to create types, similar to how most Racket struct types are created
with the @racket[struct] form rather than the dynamic @racket[make-struct-type]
function.
@subsection{Compile-Time Type Information}
Some types encapsulate compile-time information about their name, size, fields,
and other properties inside a @deftech{type binding}. Type bindings are used by
macros such as @racket[enum-out] to generate code specialized to that type. Type
bindings are created by type definition macros and extracted using
@syntax-tech[#:key "syntax clas" #:normalize? #f]{syntax classes}. For
example, @racket[define-enum-type] creates a type binding using
@racket[define-syntax] and the corresponding syntax class @racket[enum-id]
extracts that binding.
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!
@include-section[(lib "rebellion/type/tuple.scrbl")]
@include-section[(lib "rebellion/type/record.scrbl")]
@include-section[(lib "rebellion/type/enum.scrbl")]
@include-section[(lib "rebellion/type/singleton.scrbl")]
@include-section[(lib "rebellion/type/wrapper.scrbl")]
@include-section[(lib "rebellion/type/object.scrbl")]
@include-section[(lib "rebellion/type/struct.scrbl")]