File: type.txt

package info (click to toggle)
scheme48 1.9.2-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 18,332 kB
  • sloc: lisp: 88,907; ansic: 87,519; sh: 3,224; makefile: 771
file content (240 lines) | stat: -rw-r--r-- 7,777 bytes parent folder | download | duplicates (16)
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240

			   The Type System


Scheme 48 has a rudimentary type system.  Its main purpose is to
generate helpful compile-time diagnostics.

Currently you don't get much checking beyond wrong number of arguments
warnings unless you're compiling a package that has an (OPTIMIZE ...)
clause in its definition (e.g. (OPTIMIZE EXPAND) or (OPTIMIZE
AUTO-INTEGRATE)).  The reason that type checking is disabled most of
the time is that it increases compilation time by about 33%.

A design goal is to assign types to all valid Scheme programs.  That
is, type warnings should not be generated for programs that could work
according to Scheme's dynamic semantics.  For example, no warning
should be produced for

    (define (foo x y) (if x (+ y 1) (car y)))

Warnings could in principle be produced for particular calls to FOO
that would definitely go wrong, such as (foo #t 'a).

The type system assumes that all code is potentially reachable.  This
means that there will be some warnings for programs that cannot go
wrong, e.g. (if #t 3 (car 7)).

Additionally, it's assumed that in a (BEGIN ...) or combination, every
argument or command will always be executed.  This won't be the case
if there can be a throw out of the middle.  For example, in

    (call-with-current-continuation
      (lambda (k)
        (if (not (number? x))
	    (k #f))
        (+ x 1)))

the type system might deduce that X must be a number (which is false).

The type reconstruction algorithm (such as it is) is in
bcomp/recon.scm.  The implementation finds some specific procedure
types for LAMBDA expressions, but generally gives up pretty quickly.

Notation
--------

F : T means that form F has static type T.  T1 <= T2, or T1 is under
T2, means that T1 is a subtype of T2; that is, if a form of type T2 is
acceptable in some context, then so is a form of type T1.

Non-expressions
---------------

Not every valid Scheme form is an expression.  Forms that are not
expressions are syntactic keywords, definitions, types, and structure
names.

If a name is bound to a macro or special operator, then an occurrence
of that name has type :SYNTAX.  E.g.

    cond : :syntax

Definitions have type :DEFINITION.  E.g.

    (begin (define x 1) (define y 2)) : :definition

Thus type checking subsumes syntax checking.

Types (other than :TYPE itself?) have type :TYPE.

The type of a structure is its interface.  E.g.

    (define-structure foo (export a b) ...)
    foo : (export a b)

Values
------

All expressions have type :VALUES.  They may have more specific
types as well.

If E1 ... En have types T1 ... Tn with Ti <= :VALUE, then
the expression (VALUES E1 ... En) has type (SOME-VALUES T1 ... Tn).

If T <= :VALUE then (SOME-VALUES T) is equivalent to T.

Procedure types
---------------

Procedure types have the form (PROCEDURE T1 T2), where T1 and T2 are
under :VALUES.  Examples:

    (lambda (x) (values x 1)) :
       (procedure (some-values :value) (some-values :value :number))

    cons : (procedure (some-values :value :value) :pair)

Fixed-arity procedure types (PROCEDURE (SOME-VALUES T1 ... TN) T) are
so common that the abbreviated syntax (PROC (T1 ... Tn) T) is
defined to mean the same thing.  E.g.

    cons : (proc (:value :value) :pair)

E : (PROCEDURE T1 T2) means that in a call to a value of E, if the
argument sequence has any type other than T1, then the call can be
expected to "go wrong" (provoke a type error) at run time.  This is
not to say it will definitely go wrong, but that it is just a matter
of luck if it doesn't.  If the argument sequence does have type T1,
then the call might or might not go wrong, and any return value(s)
will have type T2.

For example,

    (lambda (x) (+ (begin (set! x '(3)) 5) (car x))) :
       (proc (:pair) :value),

because if the arguments to + are evaluated from right to left, and X
is not a pair, then there will be a run time type error.

Some primitive procedures have their own special typing rules.
Examples include VALUES, CALL-WITH-VALUES, and PRIMITIVE-CATCH.

Variable types
--------------

Assignable variables have type (VARIABLE T), where T for now will
always be :VALUE.  In (SET! V E), V must have type (VARIABLE T) for
some T.

Loopholes
---------

The construct (loophole T E) is considered to have type T no matter
what type E has.  Among other things, this allows a rudimentary static
abstract data type facility.  For example, record types defined using
DEFINE-RECORD-TYPE (rts/bummed-jar-defrecord.scm) are established as
new base types.

Type lattice
------------

The subtype relation is implemented by the procedure COMPATIBLE-TYPES?
(in bcomp/mtypes.scm).  If (COMPATIBLE-TYPES? T1 T2) is 'definitely,
then T1 <= T2.  If it's #T, then T1 and T2 intersect.

The type lattice has no bottom or top elements.

The types :SYNTAX, :VALUES, :DEFINITION, :STRUCTURE, and :TYPE are
incomparable and maximal.

The following are a comprehensive set of subtyping rules for the type
system as it stands.  Additional rules may be added in the future.

   - (SOME-VALUES T1 ... Tn) <= :VALUES.

   - If T1 <= T1', ..., Tn <= Tn' then (SOME-VALUES T1 ... Tn) <=
     (SOME-VALUES T1' ... Tn').

   - T <= (SOME-VALUES T).

   - Basic value types, which include :NUMBER, :CHAR, :BOOLEAN, :PAIR,
     :STRING, and :UNSPECIFIC, are all under :VALUE.

   - If T1' <= T1 and T2 <= T2', then (PROCEDURE T1 T2) <= (PROCEDURE
     T1' T2').

   - (VARIABLE T) <= T.

   - :ZERO, the result type of infinite loops and calls to
     continuations, is under :VALUE, but perhaps shouldn't be.  (E.g.
     maybe it should be just under :VALUES instead.)

   - (EXPORT (<name> T) ...) is under :STRUCTURE.
     [Not yet implemented.]

Type well-formedness
--------------------

In (SOME-VALUES T1 ... Tn), T1 ... Tn must be under :VALUE.

In (PROCEDURE T1 T2), T1 and T2 must be under :VALUES.

In (VARIABLE T), T must be under :VALUE.

Module system
-------------

The rules for interfaces and structures are not yet very well worked
out.

Interfaces are types.  The type of a structure is its interface.
(Compare with Pebble's "bindings" and "declarations".)

An interface has the basic form (EXPORT (<name> <type>) ...).
There are two kinds of abbreviations:
  - (EXPORT ... <name> ...) means the same as
    (EXPORT ... (<name> :VALUE) ...)
  - (EXPORT ... ((<name1> <name2> ...) <type>) ...) means the same as
    (EXPORT ... (<name1> <type>) (<name2> <type>) etc. ...)

Distinct interfaces are not comparable.

If a form S has type (EXPORT ... (name T) ...), then the form
(STRUCTURE-REF S name) has type T.  Note that T needn't be a :VALUE
type; e.g.

    (structure-ref scheme cond) : :syntax

When a package is loaded or otherwise compiled, the type that is
reconstructed or inherited for each exported name is checked against
the type specified in the signature.  (Cf. procedure SCAN-STRUCTURES
in bcomp/scan.scm.)

<explain the role of the expander in type checking... compile-call
doesn't do much checking if the arguments aren't expanded...>

Future work
-----------

There probably ought to be dependent sums and products and/or
universal and existential types.  In particular, it would be nice to
be able to get static checking for abstract types, even if they're not
implemented using records.

Type constructors (like STREAM-OF or COMPUTATION-OF) would be nice.

There are many loose ends in the implementation.  For example, type
and type constructor names aren't always lexically scoped; sometimes
their scope is global.  Packages that open the LOOPHOLES structure
(which exports LOOPHOLE) don't always open TYPES (which would be a bad
idea given the way TYPES is currently defined); LOOPHOLE works in
spite of that.

Figure out whether :TYPE : :TYPE.


-----

Original by JAR, 20 July 93.
Updated by JAR, 5 December 93.