The creator of 1SubML, a minimalist programming language experiment, just released version 1 after laying out plans last August. The headline shift: he ditched the structural polymorphism from his prior project, PolySubML, for spine constructors—a nominal typing hack. This fix addressed soundness holes but rippled through the design, killing or altering planned features. Why care? Type system bugs like PolySubML’s “writable types” violation can undermine proofs and runtime safety. Developers chasing dependent types or advanced generics need to watch this pivot closely.
Core Flaw in PolySubML and the Spine Constructor Fix
PolySubML promised polymorphic types via structural subtyping. Take [T]. T -> (T, T): it subtypes [T]. T -> (T, any) because the structure aligns. Sounds elegant, but pathology struck. In complex cases—think nested generics or recursive types—PolySubML broke the writable types property. Writes through subtypes failed to update supertypes reliably, a classic type theory no-go.
Spine constructors salvage it. Polymorphic “spines” (the quantified binders like [T]) demand exact matches—no subtyping. Non-poly parts still subtype normally. So [T]. T -> (T, int) subtypes [T]. T -> (T, any), but [T]. T -> (T, T) does not. Explicit :> casts bridge compatible spines structurally. Implementation details live here, but the upshot: decidable unification, no more soundness gaps. Tradeoff? Less flexible than full structural polys, more like ML-style functors with a twist.
This wasn’t cosmetic. PolySubML was the foundation; 1SubML was to extend it with modules and higher-kinded types (HKTs). Spine constructors forced a redesign. HKTs survived but simplified—no implicit coercion between poly-kinded types. Modules now lean on explicit spine matching, echoing Haskell’s type families but lighter.
Ripple Effects: Existentials, Patterns, and Dropped Dreams
Existential destructuring highlights the pain. PolySubML treated generic functions and existential records symmetrically. Define a generic: special function syntax pins the poly type. Consume an existential record: pattern matching on type fields extracts and instantiates the hidden type. Example:
let foo : { type t; zero: t; add: (t, t) -> t } =
if whatever then
{ zero = 0; add = fun (a, b) -> a + b }
else
{ zero = 0.0; add = fun (a, b) -> a + b }
In PolySubML, patterns like { zero; add; type t } unpacked existentials cleanly. Spines broke symmetry: no implicit subtyping means patterns can’t coerce spines. 1SubML added explicit spine checks in patterns—verbose, but sound. You destructure with spine { ... }, forcing poly matches upfront.
Other cuts: Originally planned implicit HKD (higher-kinded dependent) proofs vanished; spines don’t support them without exponential blowup in checking. No auto-deriving for poly records—implementors write spines manually. New additions fill gaps: first-class spines as values, letting you pass type constructors dynamically. Total: 80% of August plans landed, per the author’s tally, but internals rewired 40% of the typechecker.
What Stays, What Ships, and Real-World Implications
Core vision holds: 1SubML delivers substructural types (linear/affine by default), ML modules with spines, and basic HKTs—all in under 10k LOC of Rust. Benchmarks? Trivial programs typecheck in microseconds; pathological PolySubML cases now halt. No FFI yet, but planned for v1.1.
Skeptical take: Spine constructors dodge PolySubML’s pitfalls but sacrifice expressiveness. Structural fans will gripe—why not Agda/Coq-style cumulativity? Fair point; spines are pragmatic for a solo dev. Matters for indie PL hackers: proves you can build sound polys without a 100kLOC theorem prover. For crypto/security coders (my beat), linear types curb side-channel leaks via resource tracking. Download at repo, test the typechecker yourself. If it scales to real deps, watch this space—could inspire safer smart contracts.
Bottom line: Plans evolved under soundness pressure. Result? A leaner, debuggable type system. Progress, not perfection.