Pillar 04 · The polish
Abstraction
Say what you mean with a small, orthogonal core: pattern matching instead of
switch ladders, Result instead of thrown exceptions, named records instead of
tuples. One good way per concept — not TypeScript's accreted layers.
Too many ways to say the same thing
TypeScript grew by accretion, and it kept every layer. You can model a fixed set of cases as
an enum, a union of string literals, or a const object. You can
describe a shape with an interface, a type, or a
class. There are namespaces and modules, decorators and
higher-order functions, five idioms for nearly everything — each with its own edge
cases.
For a human that's flexibility. For an agent it's a minefield: more ways to express a thing means more ways to pick the one with the footgun, and more idioms to keep straight while doing it. Glyph's abstraction pillar is a deliberate subtraction — one sharp tool per job, chosen so the obvious way is also the safe way.
01 match is the only conditional
There is no if/else statement. Branching is match,
everywhere, and match is an expression — it produces a value. One
construct covers a boolean test, a union dispatch, and a value table, so there's no decision
to make about which branching form to reach for.
let label: string; if (score > 90) label = "A"; else if (score > 80) label = "B"; else label = "C";
let label = match score > 90 {
true => "A",
false => match score > 80 {
true => "B",
else => "C",
},
}
02 Tagged unions and pattern matching
The way to model "one of these shapes" is a tagged union, and the way to take it apart is
match, which destructures the payload in the same step it selects the case. No
discriminant field to hand-maintain, no switch ladder on a string tag, no cast
inside each branch to recover the shape.
type Feed =
| Loading
| Loaded(Array<Post>)
| Failed(string)
fn view(feed: Feed) -> string {
return match feed {
Loading => "...",
Loaded(posts) => render(posts), // posts is Array<Post> here
Failed(msg) => msg, // msg is string here
}
}
03 Result over exceptions
Failure is a value, not a hidden control-flow jump. A function that can fail says so in its
return type — Result<T, E> — and you compose failures with the
? operator instead of wrapping call sites in try/catch
and hoping the right thing throws the right class. The error path is as visible as the happy
path.
04 Named records over positional tuples
Data is described with named fields, always. There's no object-literal shorthand and no
reaching for an anonymous tuple whose [0] and [1] you have to
remember. The shape reads the same at the definition, the construction, and the use.
function origin(): [number, number] {
return [0, 0];
}
const p = origin();
p[0]; p[1]; // x? y? row? col?
type Point = { x: number, y: number }
fn origin() -> Point {
return { x: 0, y: 0 }
}
05 One primitive per job
When a concept genuinely needs its own tool, Glyph gives it exactly one. Shared mutable
state lives in a std/store. A file or socket handle that must be closed once is
an owned resource. Iteration is for x in xs. Each is a single,
named, purpose-built primitive — not a pattern you re-derive from a grab-bag of
general parts and get subtly wrong.
Abstraction here means fewer, sharper tools — not more. Every concept has an obvious form, and the obvious form is the one you're meant to use.
The benchmark — coming
The way to measure this is surface area: how many distinct concepts a reader has to hold to understand a program, and how much code it takes to express a common task cleanly. Glyph isn't in real-world use yet, so the chart below is a placeholder that fills in with real measurements once there's a body of real programs to compare.
TypeScript vs Glyph
A smaller core is easier for a person to learn once and easier for a model to get right every time. That's the whole polish layer in one sentence: take away the choices that only added risk, and what's left is a language that says what it means.