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 →