cd /news/developer-tools/quill-a-readable-language-where-mone… · home › topics › developer-tools › article
[ARTICLE · art-163015] src=github.com ↗ pub= topic=developer-tools verified=true sentiment=↑ positive

Quill – a readable language where money is exact and failures can't hide

Quill, a programming language in early development, is being designed so that intent is clear, failures are explicit, and correctness is part of the code, with an interpreter that already runs features including exact money arithmetic, `maybe` values in place of `null`, `check` tests, and contracts via `require` and `ensure`. The language's money handling makes `0.1 + 0.2` equal `0.3`, rejects adding dollars to euros, and splits `100 USD` into `33.34, 33.33, 33.33` so no cent is lost. Quill can also call AI models through `ask "..." as Review`, checking the answer against every rule of the `Review` type, and call web services through `bring http` behind a `permit net`; its developers state nothing is ready for production and invite early feedback on the design.

read14 min views1 publishedNov 3, 2026
Quill – a readable language where money is exact and failures can't hide
Image: Michielbdejong (auto-discovered)

Write what you mean.

Intent is clear, failures are explicit, and correctness is part of the code.

Quill is in early development. The design is being written down, and the interpreter runs the first features so far: say, values, var / set, exact arithmetic, {values} inside text, when, for loops, get and sum, lists, records, check and doc examples, functions (fn, return), money ( 100 USD), maybe, failures ( fails, on, try), and contracts ( require, ensure, where). Nothing here is ready for production. If you care about how a language should feel, this is the best moment to help shape it.

⚠️

Quill is a programming language where intent is clear, failures are explicit, and correctness is part of the code. It is for backend engineering, financial software, automation, reliable business applications, and AI and machine learning. A beginner should be able to read it. The same care applies when a program asks a model: the answer may be missing, and the call may fail.

The syntax stays human-readable. when, found, missing, fails, on, and check are the words that give Quill its voice.

Quill can call web services and ask AI models, each behind a permit. A web server and a database are next. That is the path to a service you can deploy.

What works now:

  • Code reads like what it means. Indentation-based, no semicolons, no braces.
  • Money is exact.100 USD + 50 USD is150 USD .0.1 + 0.2 is0.3 . Dollars and euros cannot be added;convert 100 USD to EUR at 0.92 changes one at the rate you write.split 100 USD into 3 gives33.34, 33.33, 33.33 , so no cent is lost, andround total to cents rounds the way banks do.
  • Ask for the data.sum fee of roster adds each fee.get fee of roster takes that field.get place of desk.bookings reads a field of a list on a record.students where score >= 50 keeps only the records that match, andsum fee of roster where paid == false adds what is still owed.count of students where paid == false says how many still owe,average score of students gives the class average, andstudents sorted by highest score ranks them. A missing amount is left out.for remains when each record needs its own step.
  • Text tools read like English.trim name ,uppercase code ,length of name ,email contains "@" ,code starts with "INV" ,replace "-" with " " in code ,split line by "," , andjoin names with ", " .
  • Nothing is missing by surprise. There is nonull . Amaybe says the value might be missing, and both cases have to be handled.
  • Ask a model, and trust the answer.ask "Rate this review: {text}" as Review asks Claude, or any OpenAI-compatible model, and checks the answer against every rule ofReview . A model that fails or breaks the rules is a failure the program handles.
  • Call other services.bring http , thenfetch(url) orpost(url, json(order)) . The host needs apermit net , andNetworkDown has to be handled.
  • JSON you can trust.body as Booking reads JSON and checks every field and rule before the program uses it, or fails withInvalid .json(booking) writes it back.
  • Programs grow across files.bring billing uses the functions and types written in billing.quill. A mistake in a brought file names that file.
  • Failures are part of the function.fails Declined names the failure,fail Declined leaves with it, and the caller handles it withon (stop, or carry on with a value in its place) or passes it on withtry .
  • Functions state their rules.require says what a function needs andensure says what it promises.where 1..500 gives a parameter its range. A broken one stops the program and names the value that broke it.
  • Errors teach. Every error explains what went wrong, why, and how to fix it.quill explain shows how Quill read the code.
  • Tests live with the code.check lines are tests, and examples in doc comments run as tests too.

None of these ideas is new on its own. Quill's goal is to make them work together, in one language a person can read and a team can trust.

macOS and Linux:

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/Ngwangshalom/Quill/releases/latest/download/quill-installer.sh | sh

Windows (PowerShell):

powershell -ExecutionPolicy Bypass -c "irm https://github.com/Ngwangshalom/Quill/releases/latest/download/quill-installer.ps1 | iex"

Or download a build for your system from the Releases page. Then try it:

quill new hello
quill run hello/hello.quill

quill new asks what kind of program you want, and every template runs and passes its tests straight away. Name one to skip the menu, like quill new orders backend:

For Templates
Everything app (a whole app: a backend, an AI assistant, exact money, data from a file, and tests)
Learning learn (exercises to fix),starter (one file)
Backend work backend (a service for web, desktop, and mobile apps),integration (call another service),webhook (receive notices)
AI ai (ask a model, check its answer),ai-backend (a model behind a service)
Business and finance finance (invoices with exact money),banking (accounts that can't go below zero)
Automation and general programs automation (read a file, write a report),library (functions other programs bring in)

Each project comes with a README.md and QUILL.md, a one-page tour of the language.

To update later, run quill update and choose a version with the arrow keys. Quill also tells you when a new version is out (set QUILL_NO_UPDATE_CHECK=1 to turn that off).

Building from source needs Rust: cargo install --git https://github.com/Ngwangshalom/Quill

The Quill Programming Language is a book that teaches Quill from the very beginning, for new and experienced programmers. It grows with the language, and every example in it is tested. Its source is in book/src.

The first time you run Quill, Cursor and VS Code get Quill's own colors, the feather on every .quill file, and underlines for mistakes. The feather is pale on a dark editor and deep blue on a light one. Each kind of word has its own color: say is nib orange #F0A35E, when is feather blue #7EAEF5, and true is green #5ECA8C. Vim, Emacs, and Sublime detect .quill files from editors. quill lsp sends the same mistakes to any editor. Set QUILL_NO_EDITOR_SETUP=1 to leave Cursor and VS Code alone.

say "Hello, World!"

= declares a value. Values can't change unless they're marked var, and changing one takes set, so every mutation is visible when you read the code.

name = "Amina"
var visits = 0

set visits = visits + 1
set name = "Omar"        -- error: name isn't marked var
set vistis = 2           -- error: no value called vistis (did you mean visits?)

Text that contains a quote is wrapped in three quotes, so the quote inside needs no backslash:

quoted = """She said "hi"."""
typed = "  amina@school.org "
email = lowercase trim typed

when
  email contains "@" and email ends with ".org" -> say "A school address"
  else -> say "Check the address"

parts = split "Amina,80,paid" by ","
say join parts with " / "
say replace "-" with " " in "INV-2026-001"

That says A school address, Amina / 80 / paid, and INV 2026 001. length of email is 16.

fn double(n: number) -> number
  return n * 2

say double(21)

fn greet(name: text)
  say "Hello, {name}"

greet("Amina")

A parameter's kind is text, number, true/false, or list of one of those. Kind names such as Int and Text come later.

when follows one simple rule: matching on a fixed set of cases (like maybe or failures) must cover every case, and matching on numbers or text must end with else.

fn find_price(name: text) -> maybe number
  when
    name == "bread" -> return found 2.50
    else -> return missing

when find_price("bread")
  found price -> say price
  missing -> say "none"

Forget the missing branch and Quill refuses to run. Named types such as User arrive later.

fn charge(amount: USD) -> text
  fails Declined, NetworkDown

var receipt = charge(250 USD)
  on Declined -> say "Card declined"
  on NetworkDown -> say "Try again"

Inside a function that declares the same failures, try passes them upward explicitly:

fn checkout(amount: USD) -> text
  fails Declined, NetworkDown
  return try charge(amount)

A statement after on, like say, runs and then stops the program. A value takes the place of the call's answer, and the program carries on:

var receipt = charge(250 USD)
  on Declined -> "declined"
  on NetworkDown -> "try again"

respond arrives with the web module.

label = when
  score >= 70 -> "A"
  score >= 50 -> "Pass"
  else -> "Fail"

The first true case gives the value. else is required, so there is always one.

total = 100 USD + 50 USD

That is 150 USD. 100 USD + 20 EUR is a mistake: Quill will not mix currencies. split, round, 20% of fee and convert ... at RATE keep money exact without hiding a cent. The currency itself is the kind: amount: USD. amount: USD where 1 USD..10000 USD also gives it a range.

sum adds one field. get takes that field, including a field further in. where keeps only the records that match. count of says how many, average gives the typical amount, and sorted by puts the records in order. A missing amount is left out. for stays when each record needs its own step.

sisters = record name = "Nchang Sisters", fee = 80000 USD
brass = record name = "Bamenda Brass", fee = 150000 USD
roster = list sisters, brass

say sum fee of roster
say get fee of roster
say get name of roster where fee > 100000 USD
say count of roster
say average fee of roster
say get name of roster sorted by highest fee

up = record place = "Up Station", acts = list "Bamenda Brass", "Hilltop Duo"
old = record place = "Old Town", acts = list "Market Drums"
desk = record bookings = list up, old
say get place of desk.bookings
first = desk.bookings at 1
say first.acts

people = get name, fee of roster
person = people at 1
say person.name

sum fee of roster is 230000 USD. sum fees of school is that one amount. get fee of roster is 80000 USD, 150000 USD. get name of roster where fee > 100000 USD is Bamenda Brass, the records whose condition is true. count of roster is 2, average fee of roster is 115000 USD, and get name of roster sorted by highest fee is Bamenda Brass, Nchang Sisters. get place of desk.bookings is Up Station, Old Town. first.acts is Bamenda Brass, Hilltop Duo. get name, fee of roster keeps those two fields together, so person.name is Nchang Sisters.

A function states what it needs and what it promises. require is checked when the function is called. ensure is checked each time it gives back, and result is the value it gives back.

fn withdraw(balance: USD, amount: USD) -> USD
  require amount > 0 USD
  ensure result >= 0 USD
  fails InsufficientFunds
  when
    amount > balance -> fail InsufficientFunds
  return balance - amount

A range goes right on the parameter with where. Both ends are included:

fn book(guests: number where 1..500) -> text
  return "Booked for {guests} guests"

book(600) is caught before the program runs. A value worked out while it runs is checked when book is called.

A broken contract stops the program and says which value broke it, and whose mistake it was:

Problem on line 2 of bank.quill:

      require amount > 0 USD
      ^^^^^^^^^^^^^^^^^^^^^^
withdraw requires amount > 0 USD, but amount is -5 USD.
The call on line 9 gave withdraw a value it doesn't accept. Check the value before calling withdraw.

Proving contracts before the program runs comes later.

A program can only reach the network, files or settings it has been granted.

permit read "config"
permit env "STRIPE_KEY"

rates = read_file("config/rates.txt")
  on FileMissing -> "USD 1"

Anything else is refused with a clear error, before the program runs when the path is written in the call. A file you bring in can't give itself permits, so a compromised dependency can't quietly read your secrets or send your data somewhere.

Examples in doc comments are real tests, so documentation can't drift out of date.

--- Adds 5% VAT to a price.
--- example: with_vat(100 USD) == 105 USD
fn with_vat(price: USD) -> USD
  return price * 1.05
use web
use db

permit net "localhost:5432"

type Booking
  id: Int
  venue: Text
  guests: Int where 1..500

route GET "/bookings/{id}"
  when db.find(Booking, id)
    found b -> respond 200, b
    missing -> respond 404, "Booking not found"

route POST "/bookings"
  booking = request.body as Booking
    on Invalid err -> respond 400, err.message
  respond 201, db.save(booking)
Problem on line 4 of shop.quill:

    total = price + "5"
                    ^^^
You're adding money in USD (price) and Text ("5").
Add or subtract two amounts in the same currency, like 100 USD + 50 USD.

And when you want to see how Quill understood your code:

$ quill explain shop.quill:4

Line 4:  total = price + "5"

  total         a new value (can't change)
  =             store the value on the right in total
  price + "5"   a calculation, a value
  price         money in USD, created on line 2

You're adding money in USD (price) and Text ("5").
Add or subtract two amounts in the same currency, like 100 USD + 50 USD.

Quill stopped here before running anything.
Command What it does
quill run file.quill Run a program
quill check file.quill Find errors without running anything
quill lsp Underline those errors in an editor
quill test Run project test files and count executed checks and doc examples
quill test file.quill Run one file and count its executed checks and doc examples
quill test folder Run the test files in one folder and its subfolders
quill explain file.quill:LINE Show how the compiler read a line
quill new NAME [TEMPLATE] Start a new project from a template
quill update Choose a version of Quill to install (also quill upgrade )

docs/spec.md defines exactly what Quill programs mean today: values and their kinds, exact numbers, operators, statements, when, tests and errors. This README shows the full design, including features that are still planned.

Quill is implemented in Rust, which gives it a single-binary install, a fast virtual machine, memory safety, and a WebAssembly build for an in-browser playground.

source (.quill) → lexer → parser → checker → bytecode compiler → virtual machine

The checker is where Quill earns its keep: type mismatches, unhandled cases, missing values, undeclared failures and broken contracts are caught before anything runs.

Quill is for backend engineering, financial software, automation, reliable business applications, and AI and machine learning. That includes asking a model and training one. Operating systems, device drivers, and 3D games stay with Rust and C++.

Foundation

  • Language specification (v0.1 draft)
  • Lexer and parser
  • say , values,var /set , expressions
  • when with conditions, andwhen that gives a value
  • Loops (for i in 1..3 , andfor hobby in hobbies )
  • Asking for data (sum fee of roster ,get fee of roster ,get place of desk.bookings ,count of roster ,average fee of roster ,roster sorted by fee )
  • Lists and records
  • Text tools (trim ,uppercase ,length of ,contains ,starts with ,replace ,split ... by ,join ... with )
  • Functions (fn double(n: number) -> number ,return , and calls)

Safety

  • Type checker with friendly errors
  • maybe ,found ,missing , and exhaustivewhen
  • fails ,fail ,on andtry
  • quill explain

Running

  • Bytecode compiler and virtual machine
  • check tests and executable doc examples
  • Exact Decimal numbers
  • Money (100 USD + 50 USD ; mixed currencies are a mistake)
  • Ranges on parameters (guests: number where 1..500 )
  • Types with rules on their fields (type Booking ,guests: number where 1..500 )
  • require /ensure contracts (runtime)

Ecosystem

  • Permissions (permit ), files, and settings
  • web module: HTTP server and routing
  • db module: PostgreSQL and SQLite
  • AI module: type-checked model calls (ask ... as Review ), Claude and OpenAI-compatible
  • Syntax highlighting for VS Code and Cursor (editors/vscode )
  • Browser playground (WebAssembly)
  • Package manager

Later

  • Safe concurrency
  • Static verification of contracts

Quill is built in the open, and contributions of every size are welcome: code, documentation, examples, bug reports, and especially opinions on the language design while it's still flexible.

  1. Read CONTRIBUTING.md
  2. Look for issues labeled good first issue
  3. Join the conversation in Discussions

Please follow our Code of Conduct in all project spaces.

Quill is licensed under the Apache License 2.0.

── more in #developer-tools 4 stories · sorted by recency
── more on @quill 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/quill-a-readable-lan…] indexed:0 read:14min 2026-11-03 · —