tinct
A general-purpose programming language that puts structured data first — making it natural to define, compose, query, and transform data without reaching for a separate tool.
Also: a testbed for fully automated agentic virtuous-loop software development.
Vision: One language where structured data is the native citizen. No impedance mismatch between your data model and your transformation logic — no shell pipelines to glue things together, no separate query language, no JSON-in-strings. Lazy evaluation keeps large structures efficient, Hindley-Milner types catch shape errors before they reach production, and generator-native pipelines (think jq, but typed and composable) make data flow a first-class concern.
Syntax at a Glance
[
# Data -- just key-value pairs
base: [timeout: 30 retries: 3]
# Composition -- merge, override, no repetition
dev: [merge base [env: "dev"]]
prod: [merge base [env: "prod" timeout: 60]]
# Functions -- first-class, lazy
double: [fn@Number [x@Number] [* x 2]]
# Pipelines -- chain transformations
active-names: [sort
[map [fn [u] u.name]
[filter [fn [u] u.active] users]]]
]
Key Features
Single bracket syntax
[] for everything: dicts, function calls, type annotations, and document separators. One rule, no special forms to memorize.
[name: "alice" active: true] # dict
[map [fn [u] u.name] users] # function call
[x@Int: 42] # annotated entry
Dict-first
Dicts are the fundamental data structure. Lists are dicts with consecutive integer keys, so all operations work uniformly on both.
[a: 1 b: 2] # dict — string keys
[10 20 30] # list — integer keys 0, 1, 2
Bare references
Quoted strings are literals; bare identifiers are variable references. The $ prefix disambiguates data from calls.
[env: "production" tier: "api"] # quoted strings are literals
[base: [env: environment]] # environment is a variable
Implied call
Function application uses [f args...] where f is a bare identifier, making calls concise. The $ prefix forces data interpretation.
[+ x 1]
[map [fn [u] u.name] users]
Lazy evaluation
Everything is a thunk — computed only when materialized. Unused branches cost nothing; large structures can be partially accessed without evaluating the whole.
[
all-records: [load "large-dataset.json"]
first-name: all-records.0.name # materializes only what's needed
]
Type inference
Hindley-Milner inference with row polymorphism. Annotate where you want precision; the rest is inferred. Type errors are reported before evaluation runs.
[
double: [fn@Number [x@Number] [* x 2]]
result: [double 21] # inferred: Int
]
Union types and algebraic data types
x@[Int Null] annotates a nullable value. Multi-entry [type ...] declarations define structural ADTs; [match] destructures them with exhaustiveness checking.
Result: [type [ok: a] [err: Str]]
parse: [fn@Result [input@Str]
[match [try [json-parse input]]
[ok: v] [ok: v]
[err: msg] [err: [str "parse failed: " msg]]]]
Pattern matching
[match x ...] dispatches on type, literal value, or structure. Dict and seq patterns bind fields. Guards and or-patterns extend arms.
[match event
[click: [x: cx y: cy]] [handle-click cx cy]
[key: [code: k]] [handle-key k]
_ [ignore]]
Type classes
Constrained polymorphism for overloaded builtins. Eq a => a → a → Bool statically rejects [= fn1 fn2]. Full Haskell-style class and instance declarations.
sorted: [sort items key: [fn [x] x.priority]] # Comparable constraint enforced
Named arguments
Call sites pass named arguments after positional ones. Functions declare named parameters with optional defaults.
[fetch url timeout: 60 retries: 3]
No null
Missing keys are always errors. Use get-or for optional access, keeping the absence of a value explicit.
data.missing # error: key not found
[get-or data "missing" "fallback"] # explicit optional access
% pipeline
Multi-document files pass the output of one document to the next as %. Transform data across stages without intermediate variables or a shell pipeline.
[users: [...]]
---
[active: [filter [fn [u] u.active] %.users]]
---
[sort %.active]
Standard library
stdlib/prelude.llt is written in Tinct itself, covering collection operations, string manipulation, math, and control flow. It is loaded automatically into every evaluation.
Supplemental modules are available but must be loaded explicitly with [include libdir "name.llt"]: