Learn Quill -- A Beginner's Guide

Welcome! This guide will teach you everything you need to start writing real programs in Quill. No prior programming experience is required -- we will explain every concept from the ground up.

What is Quill?

Quill is a programming language designed to read like plain English. Instead of cryptic symbols and jargon, Quill uses everyday words like is, say, give back, and for each. Under the hood, Quill compiles to JavaScript, which means your Quill programs can do anything JavaScript can -- build websites, create servers, automate tasks, and more.

Think of Quill as a translator: you write instructions in something close to English, and Quill converts them into code that your computer understands.

Your first program

Every programming journey begins with a "Hello, World!" program. In Quill, it is just one line:

say "Hello, World!"

That is it! The word say tells Quill to print something to the screen, and "Hello, World!" is the text you want to display.

How to run it

1. Create a file called hello.quill and type the line above into it.

2. Open your terminal (command prompt) and run:

quill run hello.quill

3. You should see Hello, World! printed on the screen. Congratulations -- you just ran your first program!

Try it yourself: Change the text inside the quotes to your own name, like say "Hello, my name is Alex!", and run it again.

Comments

What are comments? Comments are notes you write inside your code that the computer completely ignores. They exist solely for humans -- to explain what your code does, why you made a certain decision, or to leave a reminder for your future self.

Think of comments like sticky notes on a recipe. The oven does not read them, but they help you remember why you added extra garlic.

In Quill, comments start with two dashes (--) and continue to the end of the line:

-- This is a comment. Quill ignores this entire line.
say "Hello!"  -- You can also put comments at the end of a line

Why use comments?

-- TODO: add error handling here later
-- Calculate the user's age from their birth year
age is 2026 - birthYear
Tip: Good comments explain why you did something, not what the code does. The code itself shows the "what" -- your comments should add the "why."

Variables

What is a variable? A variable is like a labeled box where you store information. You give the box a name and put a value inside it. Later, you can look at what is inside the box, change its contents, or use it somewhere else in your program.

In Quill, you create a variable with the word is:

name is "Alice"
age is 25
active is yes

Reading that out loud: "name is Alice, age is 25, active is yes." It reads like English!

Using are for lists

When your variable holds a collection of things (a list), you can use are instead of is for better readability. They work exactly the same way -- it is purely a style choice:

colors are ["red", "green", "blue"]
scores are [95, 87, 100, 72]

Reassigning variables

You can change the value inside a variable at any time by using is again:

count is 0
say count        -- prints 0

count is count + 1
say count        -- prints 1

count is count + 1
say count        -- prints 2

Think of it like erasing the old label on your box and writing a new value inside.

What kinds of values can go in a variable?

Quill has six types of values (we will cover each in detail in the next section):

greeting is "Hello there"     -- Text (a string of characters)
temperature is 72.5           -- Number (whole or decimal)
isRaining is no               -- Boolean (yes or no)
fruits are ["apple", "pear"]  -- List (an ordered collection)
person is {name: "Bob"}       -- Object (a collection of named values)
result is nothing             -- Nothing (absence of a value)
Common mistake: Variable names cannot contain spaces. Use firstName or first_name instead of first name.
Try it yourself: Create variables for your own name, age, and favorite color, then use say to print each one.

Data Types in Depth

Every value in Quill has a type -- it is a category that describes what kind of information the value holds. Understanding types will help you use them correctly.

Text (Strings)

Text is any sequence of characters wrapped in double quotes. Programmers often call these "strings" (as in, a string of characters).

greeting is "Hello, World!"
empty is ""                      -- an empty string (no characters)
sentence is "I have 3 cats."   -- numbers inside quotes are still text

String interpolation lets you embed variables and expressions inside a string using curly braces {}:

name is "World"
say "Hello, {name}!"          -- Hello, World!
say "2 + 2 = {2 + 2}"          -- 2 + 2 = 4
say "Name length: {length(name)}"  -- Name length: 5

Escape sequences let you include special characters:

say "She said \"hello\" to me"   -- She said "hello" to me
say "Line one\nLine two"         -- prints on two separate lines

Numbers

Numbers in Quill work like you would expect. They can be whole numbers (integers) or decimals:

wholeNumber is 42
decimal is 3.14
negative is -10
zero is 0

You can do math with numbers (covered in the Operators section), and you can mix them into strings with interpolation:

price is 9.99
say "The total is ${price}"  -- The total is $9.99

Booleans (Yes / No)

A boolean is a value that is either true or false -- like a light switch that is either on or off. In Quill, you can write booleans as yes/no or true/false:

isLoggedIn is yes
hasPermission is no
gameOver is false
ready is true

Booleans are essential for making decisions in your code (which we will cover in the Control Flow section).

Lists

A list is an ordered collection of values, wrapped in square brackets and separated by commas. Think of it like a numbered shopping list:

fruits are ["apple", "banana", "cherry"]
numbers are [10, 20, 30]
mixed are ["hello", 42, yes]     -- lists can hold different types
empty are []                          -- an empty list

Access individual items by their position (index). Important: counting starts at 0, not 1:

colors are ["red", "green", "blue"]
say colors[0]   -- red   (first item)
say colors[1]   -- green (second item)
say colors[2]   -- blue  (third item)
Common mistake: Lists start counting at 0, not 1. The first item is at position [0], the second at [1], and so on. This catches almost every beginner off guard!

Objects

An object is a collection of named values (called "properties"), wrapped in curly braces. Think of it like a contact card where each piece of info has a label:

user is {name: "Alice", age: 30, active: yes}

Access properties using a dot:

say user.name     -- Alice
say user.age      -- 30
say user.active   -- yes

Nothing

nothing represents the absence of a value. It is like an empty box -- the box exists, but there is nothing inside it. In other programming languages, this concept is called null or nil.

result is nothing

if result is nothing:
  say "No result yet"

Functions that do not explicitly return a value give back nothing automatically.

Try it yourself: Create a variable for each data type and use say to print them all. Try using string interpolation to build a sentence like "My name is {name} and I am {age} years old."

Operators

What are operators? Operators are symbols that perform actions on values -- like the + sign on a calculator. They take one or more values and produce a result.

Math operators

These work just like the math you already know:

OperatorWhat it doesExampleResult
+Addition2 + 35
-Subtraction10 - 46
*Multiplication3 * 721
/Division15 / 43.75
%Modulo (remainder)10 % 31

Here is a practical example:

price is 29.99
quantity is 3
subtotal is price * quantity
tax is subtotal * 0.08
total is subtotal + tax
say "Your total is ${total}"  -- Your total is $97.1676

What is modulo? The % operator gives you the remainder after division. For example, 10 % 3 is 1 because 10 divided by 3 is 3 with a remainder of 1. This is useful for checking if a number is even or odd:

number is 7
if number % 2 is 0:
  say "Even"
otherwise:
  say "Odd"         -- prints "Odd"

Negative numbers

x is -5
y is -x   -- y is 5 (flips the sign)

String concatenation with +

The + operator also works with text. It joins (concatenates) two strings together:

first is "Hello"
second is " World"
combined is first + second
say combined   -- Hello World
Tip: For combining text and variables, string interpolation ("Hello, {name}!") is usually cleaner than concatenation ("Hello, " + name + "!").
Try it yourself: Write a program that calculates the area of a rectangle. Create variables for width and height, multiply them, and print the result.

Comparisons

What are comparisons? Comparisons check the relationship between two values and give back a boolean (yes or no). They are how your program asks questions like "is the user old enough?" or "does this list contain the item?"

Quill comparisons read like English sentences:

QuillWhat it meansExample
x is greater than yIs x larger than y?10 is greater than 5 gives yes
x is less than yIs x smaller than y?3 is less than 7 gives yes
x is equal to yAre x and y the same?5 is equal to 5 gives yes
x is yShorthand for equalname is "Alice" gives yes if name is Alice
x is not yAre x and y different?5 is not 3 gives yes
list contains xIs x inside the list?colors contains "red"

Here are several examples:

age is 20

if age is greater than 18:
  say "You are an adult"

if age is equal to 20:
  say "You are exactly 20"

if age is not 21:
  say "You are not 21"

Using contains

The contains keyword checks whether a list includes a particular item, or whether a string includes a particular substring:

colors are ["red", "blue", "green"]

if colors contains "red":
  say "Red is in the list!"

if colors contains "purple":
  say "This will not print"

message is "Hello, World!"
if message contains "World":
  say "Found it!"
Try it yourself: Create a list of your favorite foods and use contains to check if "pizza" is in the list. Print a different message depending on the result.

Logic Operators

What are logic operators? Logic operators let you combine multiple conditions together. They answer questions like "is the user logged in AND an admin?" or "is it Saturday OR Sunday?"

OperatorWhat it meansGives yes when...
andBoth must be trueyes and yes
orAt least one must be trueyes or no, no or yes, yes or yes
notFlips the valuenot no

Simple example:

age is 25
name is "Alice"

if age is greater than 18 and name is "Alice":
  say "Hello, adult Alice!"

Using or:

day is "Saturday"

if day is "Saturday" or day is "Sunday":
  say "It's the weekend!"
otherwise:
  say "It's a weekday."

Using not:

isLoggedIn is no

if not isLoggedIn:
  say "Please log in first."

Real-world example: Checking if someone can rent a car (must be at least 21 and have a license):

age is 23
hasLicense is yes

if age is greater than 20 and hasLicense:
  say "You can rent a car!"
otherwise:
  say "Sorry, you cannot rent a car."
Tip: and is stricter than or. With and, ALL conditions must be true. With or, just ONE needs to be true.

Output

What is output? Output is how your program communicates with the outside world -- specifically, by printing text to the screen. In Quill, you use the say keyword.

You can say anything -- text, numbers, variables, expressions, even entire lists:

say "Hello!"             -- prints text
say 42                    -- prints a number
say 2 + 2                 -- prints 4 (evaluates the expression first)

name is "Alice"
say name                  -- prints Alice
say "Hi, {name}!"         -- prints Hi, Alice!

colors are ["red", "blue"]
say colors                -- prints ["red", "blue"]

Control Flow -- if / otherwise

What is control flow? Control flow is how your program makes decisions. Without it, your code would run every line from top to bottom with no ability to react to different situations. With if and otherwise, your program can choose different paths based on conditions.

Think of it like a fork in the road: "IF it is raining, take an umbrella. OTHERWISE, wear sunglasses."

Basic if

The simplest form -- do something only when a condition is true:

age is 20

if age is greater than 18:
  say "You are an adult"

Notice the colon : at the end of the if line, and that the next line is indented (shifted to the right with spaces). This indentation tells Quill which code belongs inside the if block.

if / otherwise

When you want to do one thing if the condition is true and a different thing if it is false:

temperature is 35

if temperature is greater than 30:
  say "It's hot outside!"
otherwise:
  say "It's not too hot."

Chained conditions: if / otherwise if / otherwise

When you have more than two possibilities, chain them together:

temp is 72

if temp is greater than 80:
  say "It's hot!"
otherwise if temp is greater than 60:
  say "It's nice"
otherwise if temp is greater than 40:
  say "It's chilly"
otherwise:
  say "It's cold!"

Quill checks each condition from top to bottom and runs the first one that is true. If none are true, it runs the otherwise block.

Nested ifs

You can put an if inside another if for more complex logic:

isLoggedIn is yes
isAdmin is no

if isLoggedIn:
  say "Welcome back!"
  if isAdmin:
    say "You have admin access."
  otherwise:
    say "You have regular access."
otherwise:
  say "Please log in."

Real-world example: grading system

score is 85

if score is greater than 89:
  say "Grade: A"
otherwise if score is greater than 79:
  say "Grade: B"
otherwise if score is greater than 69:
  say "Grade: C"
otherwise if score is greater than 59:
  say "Grade: D"
otherwise:
  say "Grade: F"
Common mistake: Forgetting the colon : at the end of the if, otherwise if, or otherwise line. Every condition line must end with a colon.
Try it yourself: Write a program that checks a variable called hour (a number from 0 to 23) and prints "Good morning", "Good afternoon", or "Good evening" depending on the time of day.

Loops

What is a loop? A loop repeats a block of code multiple times. Instead of writing the same thing over and over, you tell the computer "do this for each item" or "keep doing this while some condition is true."

Think of it like a factory assembly line: the same action is performed on each item that comes down the belt.

for each -- iterating over a list

The for each loop goes through every item in a list, one at a time:

fruits are ["apple", "banana", "cherry"]

for each fruit in fruits:
  say "I like {fruit}"

This prints:

-- I like apple
-- I like banana
-- I like cherry

The variable fruit automatically takes on the value of each item as the loop runs. You can name it anything you want.

Use range() to loop over a sequence of numbers:

for each i in range(1, 6):
  say i   -- prints 1, 2, 3, 4, 5

while -- repeating until something changes

A while loop keeps running as long as its condition is true:

count is 0

while count is less than 5:
  say count
  count is count + 1

This prints 0, 1, 2, 3, 4 and then stops because count is no longer less than 5.

break -- stopping a loop early

Sometimes you want to stop a loop before it finishes naturally. The break keyword exits the loop immediately:

numbers are [3, 7, 2, 9, 1, 4]

for each n in numbers:
  if n is 9:
    say "Found 9!"
    break           -- stop searching, we found it
  say "Checking {n}..."

continue -- skipping an item

The continue keyword skips the rest of the current iteration and jumps to the next one:

for each i in range(1, 6):
  if i is 3:
    continue       -- skip the number 3
  say i            -- prints 1, 2, 4, 5

Real-world example: finding a user

users are [
  {name: "Alice", role: "admin"},
  {name: "Bob", role: "user"},
  {name: "Charlie", role: "user"}
]

for each user in users:
  if user.role is "admin":
    say "Admin found: {user.name}"
    break

Real-world example: summing numbers

prices are [9.99, 24.50, 3.75, 15.00]
total is 0

for each price in prices:
  total is total + price

say "Total: ${total}"   -- Total: $53.24
Common mistake: infinite loops. A while loop that never has its condition become false will run forever and freeze your program. Always make sure something inside the loop changes the condition. For example, forgetting count is count + 1 inside a while count is less than 5: loop means count stays at 0 forever.
Try it yourself: Create a list of numbers and write a loop that prints only the even ones. (Hint: use % and continue.)

Functions

What is a function? A function is a reusable block of code that performs a specific task. Think of it like a recipe: you define it once with a name and instructions, then you can use (or "call") it whenever you need it.

Without functions, you would have to copy and paste the same code every time you wanted to use it. Functions let you write it once and reuse it everywhere.

Defining a function

In Quill, you define a function with the word to, followed by the function name and its parameters:

to greet name:
  say "Hello, {name}!"

Read it like English: "To greet (someone by) name: say Hello, name!"

Calling a function

To use a function you have defined, write its name followed by the values you want to pass in (called "arguments"), wrapped in parentheses:

greet("Alice")    -- Hello, Alice!
greet("Bob")      -- Hello, Bob!
greet("Charlie")  -- Hello, Charlie!

See how we wrote the greeting logic once but used it three times with different names? That is the power of functions.

Returning values with give back

Functions can calculate something and give the result back to you using give back:

to add a b:
  give back a + b

result is add(3, 4)
say result   -- 7

The function add takes two numbers, adds them, and gives the result back. You can store that result in a variable or use it directly.

Multiple parameters

Functions can accept any number of parameters, listed by name in the definition:

to fullName first last:
  give back "{first} {last}"

say fullName("John", "Doe")   -- John Doe

Functions with no parameters

to sayHello:
  say "Hello, World!"

sayHello()   -- Hello, World!

Real-world example: a simple calculator

to calculate a operation b:
  if operation is "add":
    give back a + b
  otherwise if operation is "subtract":
    give back a - b
  otherwise if operation is "multiply":
    give back a * b
  otherwise if operation is "divide":
    give back a / b
  otherwise:
    say "Unknown operation"
    give back nothing

say calculate(10, "add", 5)       -- 15
say calculate(20, "divide", 4)    -- 5
Common mistake: Forgetting to use give back in a function that should return a value. Without it, the function returns nothing by default.
Try it yourself: Write a function called isEven that takes a number and gives back yes if the number is even, or no if it is odd.

Lambda Functions

What is a lambda? A lambda is a small, anonymous (unnamed) function that you write in a single line. Think of it as a mini function -- a quick shortcut when you need a simple operation and do not want to define a whole named function.

In Quill, you create a lambda with the with keyword:

double is with x: x * 2
say double(5)   -- 10
say double(3)   -- 6

This is equivalent to writing a full function:

-- This does the same thing as the lambda above
to double x:
  give back x * 2

Multiple parameters

add is with a, b: a + b
say add(3, 4)   -- 7

Using lambdas with list operations

Lambdas really shine when used with functions like map, filter, and reduce that transform lists:

numbers are [1, 2, 3, 4, 5]

-- Double every number
doubled is map(numbers, with x: x * 2)
say doubled   -- [2, 4, 6, 8, 10]

-- Keep only even numbers
evens is filter(numbers, with x: x % 2 is 0)
say evens   -- [2, 4]

-- Sum all numbers
total is reduce(numbers, with a, b: a + b, 0)
say total   -- 15
Tip: Use lambdas for short, simple operations. If your logic is more than one line, use a regular function with to instead -- it will be easier to read.

Type Annotations

What are type annotations? Type annotations are optional labels you add to your function parameters and return values to specify what kind of data they expect. They are like signs on the doors of a building: "Numbers Only" or "Text Required."

They do not change how your code runs, but they help catch mistakes early and serve as built-in documentation.

Basic syntax

Use as after a parameter name to declare its type, and -> before the colon to declare the return type:

to add a as number, b as number -> number:
  give back a + b

This says: "The add function takes two numbers and gives back a number."

More examples

to greet name as text -> text:
  give back "Hello, {name}!"

to isAdult age as number -> boolean:
  give back age is greater than 17

Generic types with of

For lists, you can specify what type of items they contain using of:

to sum items as list of number -> number:
  total is 0
  for each item in items:
    total is total + item
  give back total

to first items as list of text -> text:
  give back items[0]

Checking types with quill check

Type annotations are optional and do not affect how your program runs. However, you can use the quill check command to analyze your code and catch potential type mismatches before you run it:

quill check myprogram.quill
Tip: You do not need to add types everywhere. Start without them while learning, and add them later when you want more safety and documentation in larger programs.

Pattern Matching

What is pattern matching? Pattern matching is like a smart switch statement. Instead of writing a long chain of if/otherwise if, you say "match this value" and list the possible cases. It is cleaner and easier to read when you are comparing one value against many options.

Think of it like a vending machine: you insert a code, and the machine matches it to the right product.

Basic syntax

status is "active"

match status:
  when "active":
    say "User is active"
  when "inactive":
    say "User is inactive"
  when "banned":
    say "User is banned"
  otherwise:
    say "Unknown status"

Matching numbers

code is 404

match code:
  when 200:
    say "OK"
  when 404:
    say "Not found"
  when 500:
    say "Server error"
  otherwise:
    say "Unexpected code: {code}"

When to use match vs if/otherwise

Use match when you are comparing one value against several specific options. Use if/otherwise when your conditions involve different variables, ranges, or complex expressions.

-- Good use of match: one value, many options
match dayOfWeek:
  when "Monday":
    say "Start of the work week"
  when "Friday":
    say "Almost weekend!"
  otherwise:
    say "Just another day"

-- Better as if/otherwise: complex conditions
if age is greater than 18 and hasLicense:
  say "Can drive"
Tip: The otherwise clause is optional but recommended. It catches any value you did not explicitly list, preventing unexpected behavior.

Error Handling

What are errors? Errors happen when something goes wrong while your program is running -- like trying to read a file that does not exist, dividing by zero, or receiving unexpected data. Without error handling, your program would crash. With it, you can catch the problem and respond gracefully.

Think of it like a safety net under a tightrope walker. If they fall, the net catches them instead of letting disaster happen.

Basic syntax: try / if it fails

try:
  result is riskyOperation()
if it fails error:
  say "Something went wrong: {error}"

The code inside try: runs normally. If anything inside it causes an error, Quill immediately jumps to the if it fails block. The variable after if it fails (here called error) holds the error message.

Real-world example: loading a configuration file

try:
  data is readFile("config.json")
  say "Config loaded successfully"
if it fails error:
  say "Could not load config, using defaults"
  data is defaultConfig()

Real-world example: parsing user input

to parseAge input:
  try:
    age is toNumber(input)
    if age is less than 0:
      say "Age cannot be negative"
      give back nothing
    give back age
  if it fails err:
    say "'{input}' is not a valid number"
    give back nothing
Tip: Only wrap code in try if it might genuinely fail. Do not use it to hide bugs -- fix the bug instead.

Classes

What is a class? A class is a blueprint for creating things (called "objects" or "instances"). Imagine you are building houses: the blueprint (class) describes what every house looks like -- how many rooms, what color the door is, how to open it. Each actual house you build from that blueprint is an instance.

Defining a class

In Quill, you define a class with describe:

describe Dog:
  name is ""
  breed is ""
  age is 0

  to bark:
    say "{my.name} says: Woof!"

  to describe:
    say "{my.name} is a {my.age}-year-old {my.breed}"

Notice the my keyword -- it refers to the specific instance's own properties. Think of it like saying "my own name" or "my own age."

Creating instances

Create a new instance of a class using new:

rex is new Dog()
rex.name is "Rex"
rex.breed is "German Shepherd"
rex.age is 5

rex.bark()       -- Rex says: Woof!
rex.describe()   -- Rex is a 5-year-old German Shepherd

Inheritance

A class can inherit (extend) another class, gaining all of its properties and methods. The child class can add new ones or override existing ones:

describe Animal:
  name is ""
  sound is ""

  to speak:
    say "{my.name} says {my.sound}"

describe Cat extends Animal:
  sound is "Meow"

  to purr:
    say "{my.name} purrs..."

kitty is new Cat()
kitty.name is "Whiskers"
kitty.speak()   -- Whiskers says Meow
kitty.purr()    -- Whiskers purrs...

Real-world example: a game character

describe Character:
  name is "Hero"
  health is 100
  attackPower is 10

  to attack target:
    say "{my.name} attacks {target.name} for {my.attackPower} damage!"
    target.health is target.health - my.attackPower

  to isAlive:
    give back my.health is greater than 0

  to status:
    say "{my.name}: {my.health} HP"

hero is new Character()
hero.name is "Aria"
hero.attackPower is 25

villain is new Character()
villain.name is "Goblin"
villain.health is 50

hero.attack(villain)    -- Aria attacks Goblin for 25 damage!
villain.status()        -- Goblin: 25 HP
Common mistake: Forgetting to use my. when accessing a class's own properties inside its methods. Without my., Quill looks for a regular variable instead of the instance's property.
Try it yourself: Create a BankAccount class with a balance property and deposit/withdraw methods.

Algebraic Data Types

What are algebraic data types? They let you define your own custom categories of data. Think of them like creating a custom label maker: you define a set of specific variants that a value can be, and nothing else.

For example, a traffic light can only be Red, Yellow, or Green -- never Purple. Algebraic data types let you express exactly that.

Simple variants (enumerations)

The simplest form lists the possible values:

define Color:
  Red
  Green
  Blue

Now Color can only ever be Red, Green, or Blue.

Variants with data

Some variants can carry additional information using of:

define Shape:
  Circle of radius
  Rectangle of width, height
  Triangle of base, height

A Shape is always one of these three things, and each one carries exactly the data it needs.

Using with pattern matching

Algebraic types work naturally with match/when:

to area shape:
  match shape:
    when Circle radius:
      give back 3.14159 * radius * radius
    when Rectangle width height:
      give back width * height
    when Triangle base height:
      give back 0.5 * base * height

say area(Circle(5))              -- 78.53975
say area(Rectangle(4, 6))       -- 24

When to use define vs describe

Use define (algebraic types) when you have a fixed set of distinct categories, especially when different variants carry different data. Use describe (classes) when you need objects with shared behavior (methods), properties that change over time, and inheritance.

Try it yourself: Define a Result type with two variants: Success of value and Error of message. Write a function that returns one or the other and use match to handle both cases.

Pipe Operator

What is the pipe operator? The pipe operator (|) lets you chain transformations together in a readable left-to-right sequence. Think of it like an assembly line in a factory: raw material goes in one end, gets transformed at each station, and the finished product comes out the other end.

Basic usage

Instead of nesting function calls inside each other (which reads inside-out), you can pipe values through functions:

-- Without pipes (nested, reads inside-out):
result is trim(upper("  hello  "))

-- With pipes (reads left-to-right):
result is "  hello  " | upper | trim

Both produce the same result, but the pipe version reads in the order the operations happen: take the string, make it uppercase, then trim the whitespace.

Pipes with arguments

When a function takes extra arguments, the piped value becomes the first argument:

result is "hello world" | replace_text("world", "Quill")
say result   -- hello Quill

Chaining multiple steps

You can chain as many pipes as you need. For readability, you can put each step on its own line:

output is rawInput
  | trim
  | lower
  | replace_text(" ", "-")

-- If rawInput is "  Hello World  ", output is "hello-world"
Tip: Pipes are especially useful for data processing tasks where you transform a value through several steps. If you find yourself writing deeply nested function calls, consider using pipes instead.

Object Literals

What is an object? An object is a way to group related pieces of information together using named labels (keys). Think of it like a contact card: instead of having separate variables for name, phone, and email, you bundle them into one object.

Creating objects

person is {name: "Alice", age: 25, active: yes}

say person.name    -- Alice
say person.age     -- 25

Multi-line objects

For objects with many properties, spread them across multiple lines for readability:

config is {
  host: "localhost",
  port: 3000,
  debug: yes
}

Nested objects

Objects can contain other objects:

app is {
  name: "MyApp",
  database: {
    host: "localhost",
    name: "mydb",
    pool: 5
  }
}

say app.database.host   -- localhost
say app.database.name   -- mydb

Spread Operator

What is the spread operator? The spread operator (...) "unpacks" a list or object, spreading its contents into another list or object. Think of it like pouring the contents of one box into another.

Combining lists

first are [1, 2, 3]
second are [4, 5, 6]
combined are [...first, ...second]
say combined   -- [1, 2, 3, 4, 5, 6]

You can mix spread with individual items:

middle are [3, 4]
full are [1, 2, ...middle, 5, 6]
say full   -- [1, 2, 3, 4, 5, 6]

Merging objects

Spread also works for combining objects. If both objects have the same key, the later one wins:

defaults is {color: "blue", size: 10}
overrides is {size: 20, bold: yes}
merged is {...defaults, ...overrides}
say merged   -- {color: "blue", size: 20, bold: yes}

Notice that size is 20 (from overrides), not 10 (from defaults), because overrides was spread second.

Lists (Detailed)

We introduced lists earlier, but here is a deeper look at working with them.

Creating lists

numbers are [1, 2, 3, 4, 5]
mixed are ["hello", 42, yes]
empty are []

Accessing items by index

colors are ["red", "green", "blue"]
say colors[0]   -- red   (first item)
say colors[1]   -- green (second item)
say colors[2]   -- blue  (third item)

Getting the length

say length(colors)   -- 3

Iterating over a list

fruits are ["apple", "banana", "cherry"]

for each fruit in fruits:
  say "I like {fruit}"

Using dot notation

say "hello".length   -- 5

Imports

What are imports? As your programs grow, you will want to split your code into multiple files and use code that other people have written. Imports let you bring code from other files into your current file.

Importing your own Quill files

If you have a file called helpers.quill with useful functions, you can import it:

use "helpers.quill"

This brings all definitions from helpers.quill into your current file.

Selective imports

If you only need specific functions from a file, use from:

from "math.quill" use add, subtract
from "utils.quill" use formatDate, parseJSON

This keeps your namespace clean by only bringing in what you actually need.

Importing npm packages

Since Quill compiles to JavaScript, you can use packages from the npm ecosystem:

use "express" as app
from "express" use Router, json
Imports are resolved at compile time and bring definitions from the imported file into scope.

Testing

Why does testing matter? Testing is how you make sure your code works correctly -- not just right now, but also after you make changes in the future. Think of tests like a checklist: "Does the add function still return 5 when I give it 2 and 3?"

Writing tests

In Quill, you write tests with the test keyword followed by a description:

test "addition works correctly":
  result is add(2, 3)
  expect(result).toBe(5)

Multiple tests

to isEven n:
  give back n % 2 is 0

test "isEven returns yes for even numbers":
  expect(isEven(4)).toBe(yes)
  expect(isEven(0)).toBe(yes)

test "isEven returns no for odd numbers":
  expect(isEven(3)).toBe(no)
  expect(isEven(7)).toBe(no)

Running tests

Run your tests from the terminal:

quill test

Quill will find all test blocks in your project and run them, reporting which ones passed and which ones failed.

Tip: Write tests as you build your program, not after. It is much easier to catch bugs early when you test each function right after writing it.

Generics

What are generics? Generics let you write type annotations that work with any type of content. For example, instead of writing separate functions for "a list of numbers" and "a list of text," you can write one function that works with "a list of anything."

Use the of keyword to parameterize collection types:

to sum items as list of number -> number:
  total is 0
  for each item in items:
    total is total + item
  give back total

to first items as list of text -> text:
  give back items[0]

to hasAny items as list of boolean -> boolean:
  for each item in items:
    if item:
      give back yes
  give back no
Like other type annotations in Quill, generics are optional and serve as documentation. They do not change runtime behavior.

Next Steps

Congratulations! You have learned the fundamentals of Quill. Here is where to go from here:

The best way to learn is to build something. Start small -- a calculator, a to-do list, a guessing game -- and gradually tackle bigger projects. Every expert was once a beginner. Happy coding!