aboutsummaryrefslogtreecommitdiffhomepage
path: root/notes/gradual_dynamic_typing.txt
blob: 97a7ac1bd91cfd2358a915ade98c5995b783e840 (plain)
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

Gradual/Dynamic typing
======================

SLUL is meant to be a primarily statically typed language.
But in some cases it could perhaps be useful to support gradual typing.

The structural part of typing itself should be fairly simple.
There could be an "Object" type which can be anything:

- bigint
- string
- list

(At least the list type should be mutable)

But things like arena/mutability qualifiers and lifetime specifications
are trickier.

Should dynamic typing always imply "mutable"?
- Maybe not?
- Could have "var" and non-qualified (=const/immutable).

Arenas and lifetimes?
- ...?


Top-level definitions
---------------------

Instead of:

    type Thing = struct {
        int x
        string s
    }

    data [3]int items = [1, 2, 3]

    func .new(arena) -> arena Thing
    func var Thing.do_stuff(int x) -> string
        lifetime this >= return


It could be something like this:

    def Thing { x, s }

    # XXX but we can't have pointers in the data segment in SLUL!!!
    #def items = [1, 2, 3]

    def .new -> Thing
    def var Thing.do_stuff(x) ->

These will be automatically translated to something like this:

    type Thing = DynObj

    #data DynObj items = # ...!!??!

    func .new(arena) -> arena Thing
    func var Thing.do_stuff(arena, arena DynObj x) -> arena DynObj


Function bodies
---------------

- Using .typescoped identifers requires a known type!
- Calling functions may require an arena
    - Perhaps it could be implicitly supplied in dyn-functions?

Example function body:

    dyn var Thing.do_stuff(x) ->
    {
        # Implicit arena ref
        # (with static typing, this would require "ref"/"arena"/"own" or "stack")
        var Thing t = .new()
        Enum e = .value
        var i = 123

        # is the array or the reference mutable?
        # or should arrays be value-like objects and use refcounting?
        var arr = [1, 2, 3]
        const c = true

        # What if we want to assign some other type to t?
        # ...
    }

Ways to completely avoid types in constructor calls:
- "Magically" find the matching type by checking which methods are called (!)
  (but this is complex, and there will be ambuigities)
    var t = .new()
    t.do_stuff()
- Use some special constructor syntax, like C++.
- Require an explicit type in the expr:
    var t = Thing.new()
    t.do_stuff()

Ways to distinguish between "ref", "var ref", "ref var" and "var ref var":
    - only support "ref" and "var ref var"
    - different keywords:
        const = "ref"
        var   = "var ref var"
        final = "ref var", or
        let   = "ref var"
        ref   = "var ref"

How to handle lifetimes/arenas for return values?
- Always the same arena as the implicit arena parameter?


Runtime behavior
----------------

Encode basic types directly in machine words:
- low integers (e.g. -16384..16383)
  (unfortunately, enums values can't go into that range,
   because we need to store the type also!)
- booleans
- single ASCII character strings
- none
- empty list

Use references for all other types
(these either need to be immutable or behave as value types)
- bigints
- strings
- lists
- dynamic structs
- references to statically typed values.

Implicit boxing/casts from/to dynamic types.
Type mismatches or overflow generate a runtime error.

Machine-word values (example values)
------------------------------------

0x0000 - 0x7FFF = low integers (2's complement signed 15 bit value)
0x8100          = none
0x8200          = empty list
0x8300 - 0x8301 = boolean
0x8400 - 0x847F = single ASCII character string
0x8500          = empty string

Object types (example values)
-----------------------------

The type depends on the first 32-bit word:

0x0000 - 0x87FF = machine-word value that goes into a reference
                  (if this should be allowed to happen).
0x82xx          = short list,   up to 255 elements
0x85xx          = short string, up to 255 bytes
0x8600          = long string
0x8700          = 64 number (followed by high-order 32 bits)
0x8800          = big integer (TODO decide how these should work)
>= 0x10000      = Struct. The value is the type ID + 0x10000


The type ID is per module. Hence, dynamic types cannot be used
in interfaces!

Runtime support
---------------

There could be a separate module called "dynamic" that provides
runtime support for dynamic typing (and maybe GC as well?).
All modules that use dynamic typing must include it.


Problems
--------

When passing a dynamic struct or array to a function,
*all* elements have to be type checked, and then the
struct/array needs to be converted to a static typed value.