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!
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?
- To explain complex logic so others (or future you) can understand it
- To temporarily disable a line of code without deleting it
- To leave TODO notes for things you want to fix later
-- TODO: add error handling here later
-- Calculate the user's age from their birth year
age is 2026 - birthYear
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)
firstName or first_name instead of first name.
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:
\"-- a literal double quote inside a string\\-- a literal backslash\n-- a new line (moves to the next line)\t-- a tab (adds horizontal spacing)
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)
[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.
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:
| Operator | What it does | Example | Result |
|---|---|---|---|
+ | Addition | 2 + 3 | 5 |
- | Subtraction | 10 - 4 | 6 |
* | Multiplication | 3 * 7 | 21 |
/ | Division | 15 / 4 | 3.75 |
% | Modulo (remainder) | 10 % 3 | 1 |
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
"Hello, {name}!") is usually cleaner than concatenation ("Hello, " + name + "!").
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:
| Quill | What it means | Example |
|---|---|---|
x is greater than y | Is x larger than y? | 10 is greater than 5 gives yes |
x is less than y | Is x smaller than y? | 3 is less than 7 gives yes |
x is equal to y | Are x and y the same? | 5 is equal to 5 gives yes |
x is y | Shorthand for equal | name is "Alice" gives yes if name is Alice |
x is not y | Are x and y different? | 5 is not 3 gives yes |
list contains x | Is 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!"
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?"
| Operator | What it means | Gives yes when... |
|---|---|---|
and | Both must be true | yes and yes |
or | At least one must be true | yes or no, no or yes, yes or yes |
not | Flips the value | not 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."
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"
: at the end of the if, otherwise if, or otherwise line. Every condition line must end with a colon.
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
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.
% 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
give back in a function that should return a value. Without it, the function returns nothing by default.
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
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
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"
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
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
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.
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.
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"
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
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.
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
Next Steps
Congratulations! You have learned the fundamentals of Quill. Here is where to go from here:
- Standard Library -- explore the built-in functions available to every Quill program
- Testing -- learn more about writing and running tests
- Tools -- discover the Quill CLI tools for formatting, checking, and building
- Examples -- see complete working programs to learn from
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!