Skip to content

Instantly share code, notes, and snippets.

@laughinghan
laughinghan / Every possible TypeScript type.md
Last active May 8, 2024 11:14
Diagram of every possible TypeScript type

Hasse diagram of every possible TypeScript type

  • any: magic, ill-behaved type that acts like a combination of never (the proper [bottom type]) and unknown (the proper [top type])
    • Anything except never is assignable to any, and any is assignable to anything at all.
    • Identities: any & AnyTypeExpression = any, any | AnyTypeExpression = any
    • Key TypeScript feature that allows for [gradual typing].
  • unknown: proper, well-behaved [top type]
    • Anything at all is assignable to unknown. unknown is only assignable to itself (unknown) and any.
    • Identities: unknown & AnyTypeExpression = AnyTypeExpression, unknown | AnyTypeExpression = unknown
  • Prefer over any whenever possible. Anywhere in well-typed code you're tempted to use any, you probably want unknown.
@laughinghan
laughinghan / InterVer.md
Last active January 6, 2024 07:22
Interface Versioning - Never break backcompat, keep the API nimble

Interface Versioning (InterVer)

Never break backcompat, keep the API nimble

An extension of SemVer with a stricter (yet more realistic) backcompat guarantee, that provides more flexibility to change the API, for libraries that are packaged and downloaded (not services accessed remotely over the Internet (see Note 4)).

@laughinghan
laughinghan / MathSON.md
Last active December 7, 2023 21:52
MathSON - JSON for Math Formulae

MathSON

Status: Draft 1 In Progress. This document is undergoing its first revision. Initial implementation has begun alongside editing Draft 1. Your feedback is hoped and dreamed of.

Mathematical Structured Object Notation is a JSON-based representation for most of the common subset of what LaTeX and Presentation MathML can

Quick explanation of why the "control inversion" trick in this explanation of existential types on StackOverflow is actually currying:

  • the type of the original for-loop body was (∃B:VirtualMachine<B>) -> IO void
    • (where IO void represents that vm.run() had side-effects and a void return type)
  • the type of VMHandler.handle() is ∀B:(VirtualMachine<B> -> IO void)

If you interpret an existential type as describing a pair of a type and a value, and interpret a universal type as describing a function from a type to a value (see footnote), then those can be interpreted as:

  • the original for-loop body: (B, VirtualMachine<B>) -> IO void
  • VMHandler.handle(): B -&gt; VirtualMachine<b> -&gt; IO void

Minimalist Layout System

This super simple, fast, flexible layout system operates in a single pass (no reflow or constraint solving) and is probably <100 lines of non-comment, non-duplicate logic (there's a lot of duplicate logic between the top/left/right/bottom directional code that's not worth de-duplicating).

To layout PinLayout's Example 1:

pinlayout example 1

The code is much simpler than PinLayout's:

Extensible functions in Mechanical

Extensible functions are like Java interface, Rust trait, or Haskell typeclass methods, except the interface name is optional, you can define a standalone method signature (basically an anonymous one-method interface), or if you want you can define an interface which is a set of method signatures.

To implement an extensible function on a tag (or a pattern of multiple tagged variants), you have to either be the module that defined the extensible function, or the module that defined one of the tags that your implementation is restricted to. This is so you can't have like, two independent dependencies providing conflicting implementations of the method for the same tag. (Should it be relaxed to, you have to be the package that owns the extensible function or a tag, instead of the specific module?) This is called coherence by Rust and Haskell.

Like Rust trait/Haskell typeclass methods, and also like function overloading in C++ and Java, dispatch is static and can be based

Thoughts about how to do (minimal-ish) perfect hashing of Mechanical/EffectScript tag IDs for branch tables for pattern-matching:

  • the goal is to minimize CPU clock cycles to jump to the correct branch; the classic minimal perfect hashing algorithm is a 2-level hashing scheme which is undesirable overhead. We should be able to get it down to at worst one integer multiply, bitmask, and bitshift
  • for actual minimal perfect hashing would have to do integer modulo, which is a slow instruction, instead hash to range from 0 to nearest power-of-2 (eg for 5 items, has to 0-7) which only requires bitmasking, call that DESIRED_RANGE
  • tag IDs aren't randomly distributed across 0-INT_MAX, they're small-ish numbers maxing out in like thousands for big codebases presumably
  • find min and max tag ID; if max - min ≤ DESIRED_RANGE then we can just hash by subtracting min tag ID; otherwise let MAX_BIT_SIZE = ceil(log2(max tag ID))
  • if we can get by with just bitmask and bitshift that'd be great. Let MAX_BIT_POSITION = MAX

Assert vs Check vs Enforce vs Constrain in Mechanical

Check, Assert, and Enforce are all runtime error checks, but with very different semantics:

  • Check statements let you check for runtime error conditions and early-return, usually #err _ or #none.
  • Assert statements let you check for runtime error conditions and crash, to impose function preconditions or declare code invariants.
  • Enforce statements also check for runtime error conditions and crash, to enforce operational requirements of your program.

Check

Check statements let you early-return #err values:

Borrow Checking in Mechanical

Borrow checking serves two purposes in Mechanical. For compiling to JS, the only purpose is to detect when data structures like records or arrays can be mutated in-place rather than copied. In the future when we compile to WebAssembly or native, the second purpose will be for "compile-time reference counting", like Lobster's memory management model.

Sketch:

  • part of the inferred signature of a function, in addition to the expected type of each argument, is the "ownership level" the function needs over each argument
  • there are 5 levels of ownership:
    • unused means the argument is never used by the function never, or it's only used in pure expressions whose values are never used
  • borrow means the function just needs temporary read access. It's deduced when all operations on the argument are borrow operations, such as reading a scalar field on a record argument,