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.