Version: 5.100.0
Status: Self-hosting research language (not production-hardened). Installable
via pip install -e . from the repo root; the glass console script runs
files or starts a REPL.
Glass is a pure functional language designed for transparent local reasoning.
- Semantic types over structural ones.
NonEmpty<String>notstring;Int where (x > 0)not justInt. - Immutability and explicit effects by default. (v0.5.)
- Locality of reasoning. Functions understood from signature + body alone.
- No indexed iteration. Transformations are
map/filter/fold. - First-class uncertainty. Probabilistic values are a primitive. (Future.)
- Errors are values, not exceptions. (v0.1.)
- Match is total. Non-exhaustive matches are a type error. (v0.1.)
- Domain constraints belong in the signature. Refinement types. (v0.4.)
- Side-effects belong in the signature. Algebraic effects. (v0.5.) Effects are polymorphic — higher-order fns adapt to their callbacks. (v0.7.)
- The language must be expressive enough to describe itself. Self-hosting is v1.0. v0.6 made mutual recursion work; v0.7 made effect-polymorphic higher-order fns work; v0.8 added records (the natural shape for AST nodes in self-hosted Glass-in-Glass). Closes the last load-bearing structural gap for v1.0.
Glass takes the Rust experience seriously without adopting Rust's solution. Rust's central insight is that safety can come from compiler-enforced visibility at every dangerous-operation site (mutation, lifetime, send/sync). Glass takes the dual route: safety through type-system-tracked semantics — no mutation in the pure core, and the operations that DO matter (IO, randomness, model inference, eventually quantum measurement, eventually private data flow) are visible at every call site through effect labels.
Absorbed: sum types + exhaustive matching (v0.1), errors as values (v0.1), safe stdlib defaults (v0.2), parametric polymorphism (v0.3), refinement types (v0.4), effect tracking (v0.5), tuples (v0.6), effect polymorphism (v0.7), records with named fields (v0.8).
Deliberately not taken: ownership/borrowing, lifetimes, C++-adjacent syntax.
This section is the substantive one. Glass isn't an "AI language" in the sense of having a tensor library or autodiff baked in; those are libraries. What Glass has that the AI era needs is a type system that takes seriously the fact that a non-trivial fraction of any post-AI codebase will call into opaque, untrusted, probabilistic black boxes, and the type system should make that fact visible, contained, and auditable.
v0.7 ships an !{Inference} effect label and a model_call built-in:
fn ask(prompt: String) : String !{Inference} = model_call(prompt)
fn ask_and_show(prompt: String) : String !{IO, Inference} =
let answer : String = model_call(prompt) in
print("answer: " ++ answer)
The label is the load-bearing part. The runtime detail of model_call —
which model, how it's hosted, whether it's local or remote — is configurable;
the type-system fact is permanent. Every function that touches a model
declares so. Every caller of such a function picks up the effect or
declares it themselves. The audit question "does this code path call a
model?" is a textual search over signatures, not a runtime trace.
The same machinery distinguishes !{Inference} from !{IO}, !{Random},
and any future label (!{TrustedModel}, !{Sampled}, !{Private}).
Conflating them loses signal; keeping them separate keeps audit questions
answerable. See examples/features/ai.glass.
Model output is untrusted by default. A refinement type on the let-binding that receives model output makes the contract explicit:
fn ask_nonempty(prompt: String) : String !{Inference} =
let answer : String where (string_length(answer) >= 1) = model_call(prompt) in
answer
If the model returns a value violating the refinement, the program raises at the boundary — not 50 functions later when a downstream parse fails on something unexpected. The refinement is the contract; the model is the supplier; Glass enforces. The compiler statically discharges refinements that follow by implication from the program's own reasoning, leaving only the genuinely external checks (like model output) at runtime.
This is the substantive type-system answer to "untrusted AI output": not a parsing schema (which catches shape but not semantics), and not a runtime guard (which is just a check buried in code) — but a type-level contract that travels with the value through every fn that handles it.
v0.7's effect-polymorphic map/filter/fold mean that batching model
calls is the obvious code, not a separate abstraction:
fn batch_ask(prompts: List<String>) : List<String> !{Inference} =
map(prompts, fn(p: String) -> model_call(p))
The same map works for pure transformations, !{IO} callbacks, and
!{Inference} callbacks. The signature of batch_ask correctly inherits
!{Inference} from its lambda. No "async map" / "io map" / "model map"
proliferation; one generic primitive that adapts.
Effect labels are just identifiers. Different trust levels are different labels:
!{TrustedModel}— output of a model the program is willing to take semantic content from!{UntrustedModel}— output that must pass refinements before use!{HumanReviewed}— output that has been audited by a person!{Sandbox}— execution in a restricted environment
The Glass type system doesn't pick which labels matter for a given
deployment. It provides the machinery; a security team or a deployment
defines the policy by which labels callers must declare and how they
combine. Forbidden combinations (calling !{UntrustedModel} from a code
path that hasn't !{HumanReviewed} the result) become declarable
type-system errors.
A pure functional core is naturally differentiable. The subset of Glass without effects, without refinements that introduce nondifferentiable discontinuities, and over a numeric type (Float, post-v0.8) is a differentiable function. The compiler can extract that subset for autodiff backends. This isn't unique to Glass — Dex, JAX-like systems, and others have explored this — but the combination with effect typing means the differentiable-vs-effectful boundary is machine-checkable, not convention.
A Glass signature carries more information than most:
fn validate_kyc<E>(
doc: String,
reviewer: (String) -> Bool !{E}
) : Result<Identity, ValidationError> !{IO, Inference, E}
The reader (human or LLM) sees: takes a document string and a reviewer callback; the reviewer may do whatever effects E; returns either an Identity or a ValidationError; the overall fn does IO, calls a model, and propagates whatever the reviewer does. No reading of the body required to answer "what does this fn touch?"
This makes Glass a candidate lingua franca for human–AI–audit collaboration. The signature is the spec; the body is the implementation; the LLM can read either without confusing them.
The honest answer is nobody knows. The useful version of the question is: what languages will be useful for whatever comes after, in the direction that already seems to be forming?
The direction that seems to be forming:
- Verified AI. Models that come with formal guarantees on their outputs. Refinement types are the type-system anchor for "this output satisfies property P," whether the proof of P comes from training, from a verifier network, or from runtime checking.
- Hybrid symbolic/neural. Programs where some functions are neural black boxes and others are formal logic. The type system mediates the trust boundary — what the formal side is willing to assume about the neural side's outputs.
- Multi-agent systems. Programs that orchestrate multiple autonomous agents. Capability typing describes which agents may be invoked from where, with what privileges, and what trust relationships exist between them. Effect labels for inter-agent communication, message provenance, and authorization.
- Programs that explain themselves. Types carry enough semantics that an LLM can answer "what does this code do?" from signatures alone. The documentation is the type; the type is checked by the compiler.
- The end of code as a separate artifact. What humans write becomes intent (in restricted natural language or in formal logic); the AI produces implementations in a formal language like Glass; the type system mediates trust. The artifact a human reviews and the artifact a machine executes are derivable from each other.
The bet behind Glass: the formal artifact that all three audiences (humans, AI, verification tools) read the same way is what matters. Glass's transparency axiom — readable from local context alone, semantic types in signatures, effects in signatures — is precisely the requirement for that artifact. Not because Glass is special; because the requirement is real.
Glass tries to name new concepts with Glass-native vocabulary rather than echo existing ones (e.g. SQL, lenses, prisms-as-optics). Names that echo existing things make people search for the wrong reference. New concepts deserve new names, even when the new names are unfamiliar.
- Glass. The language itself. Transparent local reasoning.
- Pane. The query layer — a pane of glass: a typed, declarative,
composable view into data, descended from the Glass primitives
filter/map/fold. Built in Glass; seeexamples/pane/. - Frost. The zero-knowledge extension of Pane. Frosted glass preserves
structure while obscuring contents — the right metaphor for zero-knowledge:
the auditor reads the query shape, the verifier checks the constraint, the
witness stays private. Built from scratch in Glass as a zk-STARK; see
examples/frost/. - Quartz. Glass's native back end (
quartz.py): Glass → C. Built — a Glass-written compiler (glassc.glass) runs on top of it, and Glass self-hosts as of v4.76.
Core PL terminology (effects, refinements, polymorphism, sum types, exhaustive matching) is kept as industry-standard. There's nothing gained by renaming "effect" to something Glass-flavored; effects are a known concept and Glass uses them the standard way.
Glass already expresses much of SQL's declarative spirit using existing
primitives — filter is WHERE, map is SELECT, fold is GROUP BY
aggregation. The Pane layer makes this first-class: a small, total query
algebra with a reference interpreter, built in Glass and shaped so the same
query AST can be lowered into a zero-knowledge circuit (Frost). See
examples/pane/.
Design goals for Pane:
- Pure-functional from the ground up. No mutable cursor abstraction or implicit table state. Queries are values; running them is a function application.
- Types describe schemas. A table is
List<Row>whereRowis a user-defined record type (records, since v0.8). Joins are statically type-checked. - Refinements describe constraints. "Every age in this column is ≥ 0"
is
Column<Int where (age >= 0)>, enforced at insert time. - Effects describe what queries do. A read-only query is pure. A
query that modifies storage carries
!{Storage}. A randomised sampling query carries!{Random}. A query that consults a model carries!{Inference}. The signature describes what the query CAN do before you run it.
Frost is Pane with privacy. Some columns are private witnesses; the query result is a zero-knowledge proof that the result was correctly computed from the private data.
- Columns marked
!{Private}are witness data — flow into the query but never out to the result. - A query function returning
Result<T, E> !{Private}describes "computes T from private inputs"; the compiler can lower it to a ZK circuit where the verifier sees only T and E. - Refinement predicates on output rows become the statement the proof
attests.
SELECT name FROM users WHERE balance >= 100becomesresult : List<String> where (each_balance_ge_100(result)), and the ZK backend proves the predicate without revealing balances.
The position to aim at: a query the auditor reads in Glass syntax is the spec the ZK proof attests, is the circuit the prover runs, is the constraint the verifier checks. One artifact, three audiences. This is the inverse of the current state, where each role gets a different artifact and they may not even agree.
!{Measure} for quantum measurement. Linear types for no-cloning.
Uncertain<T> (a possible future addition) for probabilistic outcomes.
Refinement types put preconditions in signatures (m > 1, exp >= 0,
x in field). Effects mark !{CryptoRandom}, !{ConstantTime}. Linear
types handle one-time-use values like nonces, and refinement types put the
verification obligations in the signature, where the compiler discharges what
it can prove and leaves the rest to runtime.
Python was the bootstrap. The plan was always to leave it — and Glass has: the route taken was direct self-hosting via native compilation, not the OCaml-and-SMT detour this section once projected.
- The Python era.
glass.py, a tree-walking interpreter, grew the type system (Hindley-Milner, effect rows, refinement types) and remains the reference. - Glass-in-Glass.
examples/selfhost/prism.glassreimplements the whole front end — lexer, parser, type inference, evaluator — in Glass. - Quartz.
quartz.pycompiles Glass to C; a Glass-written compiler (glassc.glass) runs on top of it. At v4.76 the bootstrap fixpoint closed:glass.pyis now only a one-time bootstrap and a differential-testing oracle. See docs/self-hosting.md.
Keywords: let, fn, in, if, then, else, match, true,
false, type, where.
Comments #, identifiers (lowercase-start for bindings, uppercase-start
for types/constructors/effects), integer/string/bool literals, list
literals [...], tuple literals (a, b, c).
*, /, +, -, ++, ==, !=, <, >, <=, >=, |>.
[], [a, b, c], and spread anywhere — [...a, x, ...b] — spliced from
other lists at the head, the middle, or repeatedly. A spread desugars to ++
(the general case) or stays a Cons chain (the tail-only form [h, ...t]),
so it is exactly list concatenation with prettier syntax. (Spread also binds
the tail in patterns; see Patterns below.)
let NAME : TYPE = EXPR
fn NAME(p1: T1) : TR = EXPR
fn NAME(p1: T1) : TR !{Eff1, Eff2} = EXPR
fn NAME<T1>(p1: T1) : TR = EXPR
fn NAME<T1, Eff>(p1: (T1) -> T1 !{Eff}) : T1 !{Eff} = EXPR # effect-polymorphic
type NAME<T1> = | Variant1 | Variant2(T1)
Int,String,BoolList<T>- Tuple:
(T1, T2, T3) - Function:
(T1, T2) -> TRor(T1, T2) -> TR !{Eff1, Eff2} - User ADT (sum):
Name<T1>— multiple variants - User record:
Name<T1>— single shape with named fields - Refined:
BaseType where (predicate)— at fn-param and let-binding sites
type User = {
id: Int,
name: String,
age: Int
}
let alice : User = User { id: 1, name: "Alice", age: 30 }
let n : String = alice.name # field access
fn greet(u: User) : String = # destructuring in patterns
match u { User { name, age } => "hi " ++ name }
Records are nominal — two record types with identical field shapes but
different names are distinct types. The field set in a literal must
match the declaration exactly: no missing, no extras. Record patterns
in v0.8 bind all named fields by their declared names; renaming and
"rest" syntax (User { name, .. }) are post-v0.8.
Records compose with polymorphism: type Container<T> = { item: T, count: Int }
gives Container<Int> and Container<String> as distinct instantiations,
with type parameters propagating through field access.
The macro role records play: in Glass-in-Glass at v1.0, the AST of the language will be represented by records (one per node kind) plus sum types (for kind discrimination). Records and effects together make every data flow visible at signature level, which is the v1.0+ self-hosting requirement.
_, lowercase ident, literal patterns[],[h, ...t]list patternsCtor,Ctor(p1, ...)constructor patterns(p1, p2, p3)tuple patternsName { field1, field2 }record patterns (v0.8)
fn name(...) : T !{IO, Random} declares side-effects. Body effects must
be a subset of declared. Effects propagate through call chains. Effects
are part of TyFn, so an effectful fn cannot be passed where a pure one
is expected.
A type parameter appearing in an effect-row position is an
effect-row variable. A fn that declares !{E} where E is a type
parameter is polymorphic over its effects:
fn map<A, B, E>(xs: List<A>, f: (A) -> B !{E}) : List<B> !{E}
At each call site, E is bound to whatever the callback's effects
actually are. map(xs, double) (pure callback) gives a pure map;
map(xs, print) (!{IO} callback) gives map !{IO};
map(xs, model_call) (!{Inference} callback) gives map !{Inference}.
Built-ins map, filter, fold are effect-polymorphic. Prelude
helpers map_option, bind_option, map_result, bind_result are
effect-polymorphic. The same primitive composes through pure,
effectful, and mixed code without proliferation.
Built-in effect labels: IO (print), Random (random_int),
Inference (model_call). Custom effect labels are just identifiers —
no declaration needed.
BaseType where (predicate). Predicates type-check as Bool with the binder
in scope, and are runtime-checked at fn entry, let binding, and let-in.
Obligations the compiler can prove by implication / subsumption
(alpha-equivalence and interval reasoning) are discharged statically — no
runtime check is emitted. A general SMT backend was sketched in early
roadmaps but not pursued; this implication-based discharge is what shipped.
fn name<A, B>(...). Rigid type variables inside the body, fresh
instantiation at call sites. Same machinery for type vars and effect
vars: rigid inside, instantiated outside.
Two-pass type checker. Pass 1 registers types and fn signatures; pass 2 checks fn bodies and runs let initializers. Top-level fns can reference each other in either order.
type Option<T> = | None | Some(T)
type Result<T, E> = | Ok(T) | Err(E)
type Pair<A, B> = | Pair(A, B)
# All effect-polymorphic — propagate callback effects through monadic chains.
fn map_option<A, B, Eff>(o, f) : Option<B> !{Eff}
fn bind_option<A, B, Eff>(o, k) : Option<B> !{Eff}
fn map_result<A, B, Err, Eff>(r, f) : Result<B,Err> !{Eff}
fn bind_result<A, B, Err, Eff>(r, k) : Result<B,Err> !{Eff}
fn fst<A, B>(p) : A
fn snd<A, B>(p) : B
fn string_contains(s: String, needle: String) : Bool # v0.8.1
print(s: String) -> String !{IO}random_int(lo: Int, hi: Int) -> Int !{Random}model_call(prompt: String) -> String !{Inference}len,head(Option),tail(Option),reverse,range,string_length,int_to_string— all pure.substring(s: String, start: Int, end: Int) -> String— extract chars[start, end); clamps at end-of-string; raises on negative indices orstart > end. (v0.8.1)string_index_of(s: String, needle: String) -> Option<Int>— first occurrence, orNone. (v0.8.1)read_file(path: String) -> Result<String, String> !{File}— read a file's contents; returnsErrwith the OS message on failure. The!{File}effect makes every file read visible at every call site. (v0.8.3)write_file(path: String, content: String) -> Result<Int, String> !{File}— writecontenttopath; returns the byte count inOkor the OS message inErr. Same!{File}effect asread_file. Pairs withread_fileto enable Glass-side build pipelines. (v3.13)run_command(cmd: String, args: List<String>) -> Result<(Int, String, String), String> !{Process}— invoke an external program with arguments; returnsOk((exit_code, stdout, stderr))orErr(message). Distinct!{Process}effect — process spawning is its own capability, strictly more powerful than file I/O. 30-second timeout enforced. (v3.13)map,filter,fold— all effect-polymorphic.
The release-by-release design audits — how refinement subsumption, the
self-hosting stages, the REPL, the regex engine, and the rest were built and
verified — live in docs/design-notes.md, kept out of
this spec. The terse version log is in CHANGELOG.md.
The original roadmap below (records → OCaml+SMT → Glass-in-Glass → Quartz) has long been overtaken. Glass self-hosts (v4.76), and the work since has built Pane (a query language) and Frost (a zero-knowledge prover) on top of it. The OCaml/SMT step was never taken — self-hosting went straight through native compilation via Quartz.
The current, forward-looking roadmap lives in docs/roadmap.md;
the full history is in CHANGELOG.md.