Cure v0.17.0 :: Proofs & Polish—Toward Idris
by Aleksei Matiushkin
For seven versions Cure has been building infrastructure: a lexer, a
parser, a bidirectional type checker, refinement types, Z3 integration,
FSMs, a protocol system, an effect system. It worked. It even shipped
real programs. But the gap between Cure as a language and Cure as a
dependently-typed language was conspicuous. Every time you wanted to
say "this function’s return type is Vector(T, m + n)", the compiler
nodded politely and moved on.
In v0.17.0 it stops nodding.
Sigma types, Pi types, equality types
Three shapes that have been missing arrive together:
-
Sigma types pair a value with a type that depends on it.
Sigma(n: Nat, Vector(T, n))is "a pair of a natural number and a vector of exactly that length."Cure.Types.Sigmarecognises the surface syntax, subtypes componentwise, and round-trips to plain tuples at runtime. -
Pi types are function types whose return depends on the arguments:
fn append(xs: Vector(T, m), ys: Vector(T, n)) -> Vector(T, m + n)At each call site the checker substitutes the actual arguments into the return type, normalises with
Cure.Types.Reduce, and resolves the result. Trivial arithmetic never touches the SMT solver. -
Equality types let programs carry proofs that two terms are equal.
refl(x) : Eq(T, x, x)is the single constructor;rewrite eq in expris the single eliminator. Both vanish at runtime via the:cure_reflatom. The newStd.Equalmodule exposesrefl,sym,trans, andcong.
The glue is a small terminating normaliser, Cure.Types.Reduce, that
folds closed type-level arithmetic, booleans, comparisons, and pair
projection. Vector(T, 2 + 3) is the same type as Vector(T, 5) --
no solver round-trip required.
Implicit arguments and the unification trace
Implicit arguments land with {T} braces:
fn id({T}, x: T) -> T = x
At each call site, first-order unification with an occurs check
(Cure.Types.Unify) solves the implicit from the explicit arguments.
When it fails, Cure emits a :unification_trace pipeline event: the
LSP renders it in hover, the CLI prints it in error output. You can
see where the constraint died—which argument, which position,
which prior substitution was responsible. Implicit parameters are
erased at codegen: they cost nothing at runtime.
Hole-driven development
The most practical consequence of the dependent-types stack is that you can leave holes in your program and ask the type checker what to put there.
fn safe_head(xs: List(T)) -> T = ?body
Compile, and Cure tells you:
?body : T
in scope:
xs : List(T)
Anonymous ?? holes are numbered ?_1, ?_2, ... in source order.
Every hole encountered during checking emits a :hole_goal event
carrying the goal type and the local context at the hole position.
The REPL’s new :holes meta-command lists every hole recorded during
the last evaluation. This is how Idris users write programs; it is
now how Cure users can write programs.
Totality, done gently
A function is total when it terminates on every input and its
pattern matching is exhaustive. Cure.Types.Totality classifies
every function as :total, :partial, or :unknown, combining
coverage (via Cure.Types.PatternChecker) with a structural-recursion
check. The default is report-only; decorating with @total true upgrades
the classification to a hard requirement:
@total true
fn factorial(n: Int) -> Int
| 0 -> 1
| n -> n * factorial(n - 1)
Direct structural recursion only in v0.17.0—mutual recursion joins the party in v0.18.0.
Path-sensitive refinement
The refinement system now flows along if and match guards.
if x != 0 then 100 / x else 0
Inside the then branch, Cure.Types.PathRefinement refines x to
{x: Int | x != 0}, so the division is safe without an explicit
refinement annotation. The new Std.Refine stdlib module provides
drop-in refinements for everyday needs: NonZero, Positive,
Negative, NonNegative, Percentage, Probability, and predicate
helpers.
A real REPL
Cure.REPL is a complete rewrite. Multi-line input (terminated with
a blank line or ;;), meta-commands :t, :doc, :effects,
:load, :reload, :use, :holes, :env, :reset, :fmt,
:help, :quit. Command history persists to ~/.cure_history.
:t gives you the type; :effects gives you the inferred effect
set; :doc prints the attached ## block; :holes lists the holes
from the last evaluation with their goals and contexts.
Watch mode
cure watch lib/ --action check
Cure.Watch recompiles, type-checks, or runs tests on every save
with a 200 ms debounce. No dependency on :file_system—a small
polling fallback is included so it runs out of the box on any BEAM.
LSP polish
Cure.LSP.Server gains seven new capabilities at once:
- Inlay hints (
textDocument/inlayHint). - Signature help (
textDocument/signatureHelp). - Formatting (
textDocument/formatting) routed through the round-trip-testedCure.Compiler.Printer. prepareRename+ rename (textDocument/rename).- Code lenses (
textDocument/codeLens). - Semantic tokens (
textDocument/semanticTokens/full). - Workspace symbols (
workspace/symbol).
Unification traces and hole goals are rendered in hover; pattern coverage warnings appear as diagnostics.
Project, tooling, doctests
cure new <name> [--lib | --app | --fsm]with three project templates.Cure.locklockfile;cure deps update;cure deps tree;Cure.Project.resolve_git_dep/2for git-based dependencies viagit = "...",ref = "..."inCure.toml.cure bench—benchmark runner forbench/**/*.cure.cure test --filter PATTERN --doctests.
Any ## or ### block immediately above a function whose body
contains cure> / => pairs becomes a test case:
###
Adds two integers.
cure> Demo.add(2, 3)
=> 5
###
fn add(a: Int, b: Int) -> Int = a + b
cure test --doctests runs them. The implementation lives in
Cure.Doc.Doctests; a worked example is in
examples/doctest_demo.cure.
Error catalog, doubled
Ten new codes (E011-E020) in Cure.Compiler.Errors cover
implicit-argument failures, sigma destructuring, totality failures,
unfilled holes, refinement counterexamples, dependent-type
mismatches, equality proof mismatches, and doctest mismatches. Every
code has a detailed explanation available via cure explain Eddd or
cure why Eddd.
Language-layer polish
- Fenced multi-line docstrings.
###...###now tokenises to a single:doc_commentwith common leading indentation stripped. The single-line## textform is unchanged and now composes correctly with multi-clause functions in every position. - Trailing
?on identifiers.even?,is_empty?,positive?lex and compile as single identifiers.?namehole prefixes are preserved.!stays reserved for effect annotations and FSM hard events. - Soft keywords as function names.
fn spawn(...),fn send(...),fn receive(...)are accepted in definitions, soStd.Fsmcompiles end-to-end without carve-outs. @externmodule-name resolution. Dotted PascalCase atoms likeElixir.Cure.FSM.Builtinsnow pass through cleanly.- Zero-warning codegen. Mangled variables preserve the
user-visible
_nameshape (_V_namerather thanV__name), soerl_lintnever warns about deliberately unused parameters.
Ecosystem groundwork
- MIT license—
LICENSEat repo root. - Hex package metadata—
mix.exscarries a fullpackageblock and a docs extras list (CHANGELOG, TUTORIAL, DEPENDENT_TYPES, ...). - VS Code extension scaffold—
vscode-cure/contains a TypeScriptlanguage-clientwrapper, a TextMate grammar, language configuration, and a README. Marketplace publication is deferred to a later release; for nownpm run compile && F5gets you a working development host. - Package registry design—
docs/PACKAGE_REGISTRY.mdsketches the manifest, version-constraint solver, and index service that will land in v0.18.0+.
Regression CI
mix cure.check.stdlib # 20 Std.* modules; errors and warnings fail
mix cure.check.examples # 24 examples with per-file expected output
mix cure.check # both, sequenced
mix check # mix alias: stdlib + cure.check
test/cure/regression_test.exs wraps both as :regression-tagged
ExUnit tests so mix test surfaces them too. The GitHub Actions
workflow (.github/workflows/ci.yml) runs the whole suite on every
push and every pull request.
The numbers
- ~11 new Elixir modules; ~12 new test files; ~155 new tests (830 total, 0 failures).
- 2 new stdlib
.curemodules (Std.Equal,Std.Refine); 20 total. - 7 new examples—one for every major new feature.
- 3 new docs (
DEPENDENT_TYPES.md,TUTORIAL.md,PACKAGE_REGISTRY.md) plus refreshedLANGUAGE_SPEC.md,TYPE_SYSTEM.md,STDLIB.md. - Zero credo issues in strict mode.
mix formatclean.mix compile --warnings-as-errorsclean. mix checkgreen (20 stdlib + 24 examples).
Upgrade notes
v0.17.0 is source-compatible with v0.16.0. Three small nudges if you are porting existing code:
- The lexer now treats a trailing
?on an identifier as part of the name.x?y(no space) used to tokenise as three tokens; nowx?is one identifier followed byy. Add whitespace (x ? y) or parentheses if you need the old behaviour. mix.exsversion bumped to0.17.0. If you depend on Cure via path, regenerate your downstreamCure.lockwithmix cure.compile_stdlib && mix cure.check.- Module-level docstrings written as
## ...lines betweenmod Xand the first definition now reliably attach to the first definition inside the block. Previously they would sometimes silently drop. No change is required—behaviour is strictly more correct.
What’s next—v0.18.0 Bring the Furniture
The deferred "new ideas" from the v0.17.0 plan come home:
proofcontainers.assert_typebuiltin.- Records with default field values.
- Finished
@derive(Show, Eq, Ord)on records. - Property-based testing in
Std.Test. Std.Iterlazy iterator protocol.- First half of the package registry—version-constraint parser,
resolver, deeper
Cure.locksemantics. - Mutual-recursion totality.
Acknowledgements
Thanks to the Finitomata project for the FSM inspiration that landed in v0.16.0 and continues to pay dividends, and to the Idris community for the hole-driven-development workflow this release adopts.
Getting started
git clone https://github.com/am-kantox/cure-lang.git
cd cure
mix deps.get && mix test
mix escript.build
./cure version # Cure 0.17.0
./cure repl # new multi-line REPL with :t, :doc, :holes
./cure watch lib/ # recompile on save
The repository is at github.com/am-kantox/cure-lang. The rewrite is complete. The type system is honest. The tools work. Time to use the language.