Functions
Functions are fundamental building blocks in Quartz. They encapsulate reusable code and help organize your programs.
Defining Functions
Basic Syntax
fn greet() {
io.println("Hello!")
}
// Call the function
greet()
With Parameters
fn greetPerson(name) {
io.println("Hello, " + name + "!")
}
greetPerson("Alice") // Hello, Alice!
greetPerson("Bob") // Hello, Bob!
Multiple Parameters
fn introduce(name, age, city) {
io.println(name + " is " + age + " years old, from " + city)
}
introduce("Alice", 30, "Boston")
Return Values
Returning a Value
fn add(a, b) {
return a + b
}
let sum = add(5, 3)
io.println("Sum: " + sum) // Sum: 8
Early Return
fn getGrade(score) {
if (score >= 90) {
return "A"
}
if (score >= 80) {
return "B"
}
if (score >= 70) {
return "C"
}
return "F"
}
io.println(getGrade(85)) // B
Returning Without a Value
Functions without return implicitly return null:
fn doSomething() {
io.println("Doing something...")
// No return - returns null
}
let result = doSomething()
// result is null
Default Parameters
fn greet(name, greeting = "Hello") {
io.println(greeting + ", " + name + "!")
}
greet("Alice") // Hello, Alice!
greet("Bob", "Howdy") // Howdy, Bob!
fn createUser(name, age = 0, city = "Unknown") {
return {
"name": name,
"age": age,
"city": city
}
}
let user1 = createUser("Alice")
let user2 = createUser("Bob", 30)
let user3 = createUser("Charlie", 25, "NYC")
Variadic Functions
Use the rest parameter ...args to accept any number of arguments:
fn sum(...numbers) {
let total = 0
for (let i = 0; i < len(numbers); i = i + 1) {
total = total + numbers[i]
}
return total
}
io.println(sum(1, 2, 3)) // 6
io.println(sum(1, 2, 3, 4, 5)) // 15
Lambda Expressions
Anonymous functions with concise syntax:
Single Expression
// Lambda that returns expression result
let square = |x| => x * x
let add = |a, b| => a + b
io.println(square(5)) // 25
io.println(add(3, 4)) // 7
Block Body
let factorial = |n| => {
if (n <= 1) {
return 1
}
return n * factorial(n - 1)
}
io.println(factorial(5)) // 120
No Parameters
let sayHello = || => io.println("Hello!")
sayHello()
Higher-Order Functions
Functions that take functions as arguments or return functions:
Functions as Arguments
fn applyToEach(arr, func) {
let result = []
for (let i = 0; i < len(arr); i = i + 1) {
result[i] = func(arr[i])
}
return result
}
let numbers = [1, 2, 3, 4, 5]
let squares = applyToEach(numbers, |x| => x * x)
// squares = [1, 4, 9, 16, 25]
Returning Functions
fn makeMultiplier(factor) {
return |x| => x * factor
}
let double = makeMultiplier(2)
let triple = makeMultiplier(3)
io.println(double(5)) // 10
io.println(triple(5)) // 15
Compose Functions
fn compose(f, g) {
return |x| => f(g(x))
}
let addOne = |x| => x + 1
let square = |x| => x * x
let addThenSquare = compose(square, addOne)
io.println(addThenSquare(4)) // 25 (4+1=5, 5*5=25)
Closures
Functions that capture variables from their enclosing scope:
fn makeCounter() {
let count = 0
return || => {
count = count + 1
return count
}
}
let counter = makeCounter()
io.println(counter()) // 1
io.println(counter()) // 2
io.println(counter()) // 3
// Each counter is independent
let anotherCounter = makeCounter()
io.println(anotherCounter()) // 1
Practical Closure Example
fn makeBankAccount(initialBalance) {
let balance = initialBalance
return {
"deposit": |amount| => {
balance = balance + amount
return balance
},
"withdraw": |amount| => {
if (amount > balance) {
io.println("Insufficient funds!")
return balance
}
balance = balance - amount
return balance
},
"getBalance": || => balance
}
}
let account = makeBankAccount(100)
io.println("Balance: " + account["getBalance"]()) // 100
account["deposit"](50)
io.println("Balance: " + account["getBalance"]()) // 150
account["withdraw"](30)
io.println("Balance: " + account["getBalance"]()) // 120
Recursion
Functions that call themselves:
fn factorial(n) {
if (n <= 1) {
return 1
}
return n * factorial(n - 1)
}
io.println(factorial(5)) // 120
Fibonacci
fn fibonacci(n) {
if (n <= 1) {
return n
}
return fibonacci(n - 1) + fibonacci(n - 2)
}
for (let i = 0; i < 10; i = i + 1) {
io.print(fibonacci(i) + " ")
}
// 0 1 1 2 3 5 8 13 21 34
Tail Recursion (Optimized)
fn factorialTail(n, acc = 1) {
if (n <= 1) {
return acc
}
return factorialTail(n - 1, n * acc)
}
io.println(factorialTail(10)) // 3628800
Function Scope
let globalVar = "I'm global"
fn outer() {
let outerVar = "I'm in outer"
fn inner() {
let innerVar = "I'm in inner"
io.println(globalVar) // Accessible
io.println(outerVar) // Accessible
io.println(innerVar) // Accessible
}
inner()
// innerVar not accessible here
}
outer()
// outerVar not accessible here
Common Patterns
Callback Pattern
fn fetchData(url, onSuccess, onError) {
try {
let data = http.get(url)
onSuccess(data)
} catch (e) {
onError(e.message)
}
}
fetchData(
"https://api.example.com/users",
|data| => io.println("Got data: " + data),
|err| => io.println("Error: " + err)
)
Memoization
fn memoize(func) {
let cache = {}
return |arg| => {
let key = str(arg)
if (cache[key] != null) {
return cache[key]
}
let result = func(arg)
cache[key] = result
return result
}
}
let slowFib = |n| => {
if (n <= 1) return n
return slowFib(n - 1) + slowFib(n - 2)
}
let fastFib = memoize(slowFib)
io.println(fastFib(30)) // Much faster!
Partial Application
fn partial(func, firstArg) {
return |secondArg| => func(firstArg, secondArg)
}
fn add(a, b) {
return a + b
}
let addFive = partial(add, 5)
io.println(addFive(3)) // 8
io.println(addFive(10)) // 15
Best Practices
Keep Functions Small
Each function should do one thing well. If it's getting long, break it up.
Use Descriptive Names
Function names should describe what they do: calculateTotal not calc.
Limit Parameters
More than 3-4 parameters? Consider passing an object instead.
Avoid Side Effects
Functions should preferably return values rather than modify external state.
Next: Classes & OOP →