tock/notes.md
2024-06-23 15:58:17 -04:00

3.3 KiB

type system

data types:

i64, f64 - numbers bool - logical boolean values {fn [type1 type2 ... -> rettype]} / {fn [type1 type2 ...]} => [fn type1 type2 ... rettype]

{array [type length]} => [array type length]

(struct name ^type1 member1 ^type2 member2 ^type3 member3) ^{tuple [^{name member1} type1 ^{name member2} type2 ^{name member3} type3]} ^{name name} [tuple ^{name member1} type1 ^{name member2} type2 ^{name member3} type3]

(enum name (clause1 type1 type2 ...) (clause2 type1 type2 ...) clause3) {variant [^{tuple [type1 type2 ...]} clause1 ^{tuple [type1 type2 ...]} clause2 ^void clause3]}

Variables have both a datatype and an isolation modifier. There are three possible isolation types:

  • const - this is the default, if no isolation modifier is given. No in-place mutations are possible with const values.
  • val - a val variable can be mutated in-place, but changes only affect that variable. If it is assigned to any other variable or passed as a parameter, it is copied if necessary and can be treated as a new, totally distinct value.
  • mut - Only valid on function parameters. Denotes a value in which mutations to the parameter are visible from the calling function. Any assignments of a mut value to other variables (except being further passed as a mut parameter) makes a copy.

All datatypes can have the following modifiers:

  • ref - a ref is analogous to a full pointer or object reference. Copies of the same ref can exist in multiple places, and refer to the same object in memory. Changes to data mutated via a ref are immediately visible to any other code that has the same ref. refs are created with the box function. References that point in the middle of a structure are not possible; use a tuple or an enum or something.
  • opt - equivalent to (enum [opt type] (some type) none), assuming we had generics, which atm we do not. no idea what destructuring helpers make sense here yet.
  • array - contiguously-allocated values of any type. size can be dynamically specified, bounds are checked on access.

Q: should const structures be able to modify ref members directly? leaning towards yes. ref seems to inherently imply interior mutability - after all, the value a given ref points to is not constant.

Interesting thought: we only need GC for refs! local variables + parameters can live in a stack-based arena, and globals are statically allocated.

typed dynamic dispatch

(protocol name (method name [^mut self ^type1 arg1 ^type2 arg2 -> rettype]))

(impl protocol type (method name [^mut self ^type1 arg1 ^type2 arg2 -> rettype] body...))

type restrictions

  • scoped - would be nice to be able to define destructors and have some kind of RAII, but it's probably overkill

memory management

  • "hot reload" implies "given the previous source code and a memory, I can reason about the types of everything in the memory"
  • compacting garbage collection is simply the degenerate case of rearranging memory to be legible to new code!
  • we have typed roots (globals), and we can follow typed references from there
    • stack is not accessible from wasm, and GC / reload would only happen when wasm code returns to JS - no suspension