Functions and Closures in GoloScript

A comprehensive guide to functions, lambdas, and closures in GoloScript, with practical examples and use cases.


Introduction

Functions and closures are fundamental building blocks in GoloScript. Functions allow you to encapsulate reusable code, while closures enable powerful functional programming patterns by combining functions with variable capture.

Key Concepts


Function Syntax

Basic Function with Parameters

function add = |a, b| {
  return a + b
}

add(5, 3)  # Returns 8

Function Without Parameters

# Syntax without pipes
function getGreeting = {
  return "Hello from GoloScript!"
}

getGreeting()  # Returns "Hello from GoloScript!"

Implicit Return

The last expression in a function is automatically returned:

function square = |n| {
  n * n  # Automatically returned
}

function isEven = |n| {
  n % 2 == 0
}

square(7)    # Returns 49
isEven(10)   # Returns true

Multiple Return Values

Return arrays (tuples) to return multiple values:

function divMod = |a, b| {
  let quotient = a / b
  let remainder = a % b
  return [quotient, remainder]
}

let result = divMod(17, 5)
println(result[0])  # 3 (quotient)
println(result[1])  # 2 (remainder)

Nested Functions

Define local functions within other functions:

function calculate = |a, b| {
  function helper = |x| {
    return x * 2
  }

  return helper(a) + helper(b)
}

calculate(3, 5)  # Returns 16 (3*2 + 5*2)

Variadic Functions (Varargs)

GoloScript supports variable arguments with the args... syntax (similar to JavaScript’s rest parameters).

Varargs Only

# Function accepts variable number of arguments
function test = |args...| {
  println("Number of arguments:", len(args))
  return args
}

println(test())           # [] (empty array)
println(test(1, 2, 3))    # [1, 2, 3]

Mixed Parameters with Varargs

You can combine regular parameters with varargs. Important: only the last parameter can be varargs.

function test = |a, b...| {
  println("a:", a)           # First argument
  println("b:", b)           # Rest of arguments (array)
  println("type of b:", type(b))
  println("length of b:", len(b))
  return b
}

let result = test(1, 2, 3, 4)
println("result:", result)      # [2, 3, 4]
println("result[0]:", result[0]) # 2
println("result[1]:", result[1]) # 3

Practical Example: sum Function

function sum = |numbers...| {
  var total = 0
  foreach n in numbers {
    total = total + n
  }
  return total
}

println(sum(1, 2, 3, 4, 5))      # 15
println(sum(10, 20, 30))         # 60
println(sum())                    # 0

Alternative: Passing Arrays Explicitly

If you prefer, you can always pass an array explicitly:

function sumAll = |numbers| {
  var total = 0
  foreach n in numbers {
    total = total + n
  }
  return total
}

sumAll(array(1, 2, 3, 4, 5))  # Returns 15

Recursive Functions

Functions can call themselves:

function factorial = |n| {
  if n <= 1 {
    return 1
  }
  return n * factorial(n - 1)
}

function fibonacci = |n| {
  if n <= 1 {
    return n
  }
  return fibonacci(n - 1) + fibonacci(n - 2)
}

factorial(5)    # Returns 120
fibonacci(7)    # Returns 13

Pure vs Impure Functions

Pure functions always return the same output for the same input with no side effects:

function purePlus = |a, b| {
  return a + b
}

purePlus(2, 3)  # Always returns 5
purePlus(2, 3)  # Still returns 5

Impure functions modify external state or have side effects:

var state = 0
function impurePlus = |a, b| {
  state = state + 1
  return a + b + state
}

impurePlus(2, 3)  # Returns 6 (state is now 1)
impurePlus(2, 3)  # Returns 7 (state is now 2)

Closure Syntax

Closures are anonymous functions (lambdas) that can be assigned to variables. GoloScript supports multiple syntactic variations.

Simple Lambda (Single Parameter, Compact)

let double = |x| -> x * 2
let triple = |x| -> x * 3
let add = |a, b| -> a + b

double(5)      # Returns 10
triple(5)      # Returns 15
add(3, 7)      # Returns 10

Lambda with Block Body

# Single parameter with block
let double1 = |x| {
  return x * 2
}

# Multiple parameters with block
let add1 = |a, b| {
  return a + b
}

double1(5)    # Returns 10
add1(3, 7)    # Returns 10

Closure Without Parameters - Arrow Syntax

let getMessage = -> "Hello from closure!"
let getNumber = -> 42
let getPi = -> 3.14159

getMessage()  # Returns "Hello from closure!"
getNumber()   # Returns 42

Closure Without Parameters - Block Syntax

let c2 = {
  "Block syntax"
}

c2()  # Returns "Block syntax"

Closure with Multiple Statements

let complex = |x| -> {
  let doubled = x * 2
  let squared = doubled * doubled
  return squared
}

complex(3)  # Returns 36 (3*2 = 6, 6*6 = 36)

Array of Closures

let funcs = [
  -> 10,
  -> 20,
  -> 30
]

funcs[0]()  # Returns 10
funcs[1]()  # Returns 20
funcs[2]()  # Returns 30

Conditional Closures

let isWeekend = -> false
let message = if isWeekend() {
  -> "Rest!"
} else {
  -> "Work!"
}

message()  # Returns "Work!"

Variable Capture

One of the most powerful features of closures is their ability to “capture” variables from their surrounding scope.

Basic Variable Capture

function makeMultiplier = |factor| {
  return |x| -> x * factor
}

let times2 = makeMultiplier(2)
let times5 = makeMultiplier(5)
let times10 = makeMultiplier(10)

times2(7)   # Returns 14
times5(7)   # Returns 35
times10(7)  # Returns 70

Each closure captures its own copy of the factor parameter.

Private State with Closures

Closures can maintain private state:

function makeCounter = {
  var count = 0
  return {
    count = count + 1
    return count
  }
}

let counter1 = makeCounter()
let counter2 = makeCounter()

counter1()  # Returns 1
counter1()  # Returns 2
counter1()  # Returns 3

counter2()  # Returns 1 (separate counter)
counter2()  # Returns 2

Each counter has its own independent count variable.

Multiple Functions Sharing State

A factory can return multiple closures that share the same private state:

function makeBank = |initialBalance| {
  var balance = initialBalance

  let deposit = |amount| {
    balance = balance + amount
    return balance
  }

  let withdraw = |amount| {
    if amount > balance {
      println("Insufficient funds!")
      return balance
    }
    balance = balance - amount
    return balance
  }

  let getBalance = {
    return balance
  }

  return [deposit, withdraw, getBalance]
}

let account = makeBank(100)
let depositFn = account[0]
let withdrawFn = account[1]
let getBalanceFn = account[2]

getBalanceFn()      # 100
depositFn(50)       # 150
withdrawFn(30)      # 120
getBalanceFn()      # 120

All three functions share and can modify the same balance variable.

Nested Closures

Closures can capture from multiple levels:

function makeAdder = |a| {
  return |b| {
    return |c| {
      return a + b + c
    }
  }
}

let add5 = makeAdder(5)
let add5and10 = add5(10)
let result = add5and10(3)

println(result)  # 18 (5 + 10 + 3)

Array of Closures with Captured Variables

let operations = array(
  |x| -> x + 1,
  |x| -> x * 2,
  |x| -> x - 5,
  |x| -> x / 2
)

let value = 10
operations: get(0)(value)  # 11
operations: get(1)(value)  # 20
operations: get(2)(value)  # 5
operations: get(3)(value)  # 5

Lazy Evaluation

Closures are not evaluated until called, enabling lazy evaluation:

let expensiveCalc = -> {
  println("Computing expensive result...")
  return 42 * 42
}

println("Closure created but not called yet")
let result = expensiveCalc()  # Now it computes
println(result)  # 1764

Higher-Order Functions

Higher-order functions take functions as parameters or return functions.

Functions Taking Functions

function apply = |f, x| {
  return f(x)
}

let double = |x| -> x * 2
let square = |x| -> x * x

apply(double, 5)  # Returns 10
apply(square, 5)  # Returns 25

Functions Returning Functions

function makeAdder = |n| {
  return |x| -> x + n
}

let add5 = makeAdder(5)
let add10 = makeAdder(10)

add5(3)   # Returns 8
add10(3)  # Returns 13

Function Composition

Combine functions by applying one to the result of another:

function compose = |f, g| {
  return |x| -> f(g(x))
}

let addOne = |x| -> x + 1
let multiplyByTwo = |x| -> x * 2

let addThenMultiply = compose(multiplyByTwo, addOne)
let multiplyThenAdd = compose(addOne, multiplyByTwo)

addThenMultiply(5)      # (5 + 1) * 2 = 12
multiplyThenAdd(5)      # (5 * 2) + 1 = 11

Applying a Function Multiple Times

function applyTwice = |f, x| {
  return f(f(x))
}

function applyNTimes = |f, n, x| {
  var result = x
  var i = 0
  while i < n {
    result = f(result)
    i = i + 1
  }
  return result
}

let increment = |x| -> x + 1

applyTwice(increment, 10)       # Returns 12
applyNTimes(increment, 5, 10)   # Returns 15

Map - Transform Array Elements

let numbers = array(1, 2, 3, 4, 5)
let doubled = numbers: map(|x| -> x * 2)
let squared = numbers: map(|x| -> x * x)

println(numbers)   # [1, 2, 3, 4, 5]
println(doubled)   # [2, 4, 6, 8, 10]
println(squared)   # [1, 4, 9, 16, 25]

Filter - Select Array Elements

let isEven = |x| -> x % 2 == 0
let isPositive = |x| -> x > 0

let values = array(-2, -1, 0, 1, 2, 3, 4, 5)
let evens = values: filter(isEven)
let positives = values: filter(isPositive)

println(values)      # [-2, -1, 0, 1, 2, 3, 4, 5]
println(evens)       # [-2, 0, 2, 4]
println(positives)   # [1, 2, 3, 4, 5]

Reduce - Fold to a Single Value

let nums = array(1, 2, 3, 4, 5)
let sum = nums: reduce(0, |acc, x| -> acc + x)
let product = nums: reduce(1, |acc, x| -> acc * x)

println(sum)      # 15
println(product)  # 120

Currying and Partial Application

function curry = |f| {
  return |a| -> |b| -> f(a, b)
}

let addNormal = |a, b| -> a + b
let addCurried = curry(addNormal)

let add3 = addCurried(3)
let add7 = addCurried(7)

add3(5)  # Returns 8
add7(5)  # Returns 12

Practical Use Cases

1. Factory Functions

Create objects or configurations with closures:

function createPerson = |name, age| {
  let getName = -> name
  let getAge = -> age
  let greet = -> "Hello, I'm " + name + " and I'm " + str(age)

  return [getName, getAge, greet]
}

let alice = createPerson("Alice", 30)
let bob = createPerson("Bob", 25)

println(alice[2]())  # "Hello, I'm Alice and I'm 30"
println(bob[2]())    # "Hello, I'm Bob and I'm 25"

2. Configuration Builders

Build and configure objects step-by-step:

function createConfig = {
  var host = "localhost"
  var port = 8080
  var debug = false

  let setHost = |h| { host = h }
  let setPort = |p| { port = p }
  let setDebug = |d| { debug = d }
  let build = -> "Config: " + host + ":" + str(port) + " (debug=" + str(debug) + ")"

  return [setHost, setPort, setDebug, build]
}

let config = createConfig()
config[0]("api.example.com")
config[1](443)
config[2](true)
println(config[3]())  # "Config: api.example.com:443 (debug=true)"

3. Event Handlers

Simulate event handling with closures:

function createButton = |label| {
  var clickCount = 0
  let onClick = |handler| {
    clickCount = clickCount + 1
    handler(label, clickCount)
  }
  return onClick
}

let button = createButton("Submit")

let handleClick = |label, count| {
  println("Button '" + label + "' clicked " + str(count) + " times")
}

button(handleClick)  # "Button 'Submit' clicked 1 times"
button(handleClick)  # "Button 'Submit' clicked 2 times"
button(handleClick)  # "Button 'Submit' clicked 3 times"

4. Memoization (Caching)

Cache function results to avoid recomputation:

function memoize = |fn| {
  var cache = map[]
  return |arg| {
    let cached = cache: get(str(arg))
    if not isNull(cached) {
      println("(from cache)")
      return cached
    }
    println("(computing...)")
    let result = fn(arg)
    cache = map[cache, [str(arg), result]]
    return result
  }
}

let expensiveFn = |n| -> n * n
let memoized = memoize(expensiveFn)

memoized(5)  # "(computing...)" Returns 25
memoized(5)  # "(from cache)" Returns 25
memoized(7)  # "(computing...)" Returns 49

5. Pipeline of Transformations

Chain multiple transformations:

function pipeline = |value, funcs| {
  var result = value
  foreach fn in funcs {
    result = fn(result)
  }
  return result
}

let transforms = array(
  |x| -> x + 1,
  |x| -> x * 2,
  |x| -> x - 3
)

let result = pipeline(10, transforms)
# 10 -> (+1) -> (*2) -> (-3)
# 10 + 1 = 11
# 11 * 2 = 22
# 22 - 3 = 19
println(result)  # 19

6. Partial Application

Bind some arguments to create specialized versions:

function partial = |fn, arg1| {
  return |arg2| -> fn(arg1, arg2)
}

let multiply = |a, b| -> a * b
let double = partial(multiply, 2)
let triple = partial(multiply, 3)

double(5)  # Returns 10
triple(5)  # Returns 15

7. Lazy Sequences

Generate values on-demand:

function range = |start, end| {
  var current = start
  return {
    if current <= end {
      let value = current
      current = current + 1
      return value
    }
    return null
  }
}

let numbers = range(1, 5)
println(numbers())  # 1
println(numbers())  # 2
println(numbers())  # 3
println(numbers())  # 4
println(numbers())  # 5
println(numbers())  # null (exhausted)

Syntax Comparison

Closure Syntax Overview

Use Case Syntax Example Notes
No params, compact -> expression -> 42 Most concise
No params, block { ... } { "text" } Alternative syntax
One param, compact |x| -> expr |x| -> x * 2 Clean and readable
One param, block |x| { ... } |x| { return x * 2 } More explicit
Multiple params, compact |a,b| -> expr |a,b| -> a + b Preferred for simple cases
Multiple params, block |a,b| { ... } |a,b| { return a + b } When multiple statements needed
Complex body |x| -> { ... } |x| -> { let y = x * 2; return y } Multi-statement with arrow

Function vs Closure

Feature Function Closure
Declaration function name = ... let name = ...
Parameters Required pipes: |a,b| Same syntax
Anonymous No (must be named) Yes (typically)
Variable capture Limited Full support
Use in collections Can reference by name Can store directly
Return syntax Explicit or implicit Implicit in arrow form

Quick Reference

Function Declaration Patterns

# Function with parameters
function add = |a, b| -> a + b

# Function without parameters (returns immediately)
function greet = -> "Hello"

# Function with block
function calculate = |x| {
  let y = x * 2
  y + 5
}

# Nested function
function outer = {
  function inner = |x| -> x * 2
  return inner(5)
}

Closure Patterns

# Simple lambda
let double = |x| -> x * 2

# Closure with variable capture
let makeMultiplier = |factor| -> |x| -> x * factor
let times3 = makeMultiplier(3)

# Closure with state
let makeCounter = -> {
  var count = 0
  return { count = count + 1; return count }
}

# Closure taking a function
let apply = |f, x| -> f(x)

# Closure returning a function
let compose = |f, g| -> |x| -> f(g(x))

Common Higher-Order Function Patterns

# Map
let doubled = numbers: map(|x| -> x * 2)

# Filter
let evens = nums: filter(|x| -> x % 2 == 0)

# Reduce
let sum = nums: reduce(|acc, x| -> acc + x, 0)

# Compose
let composed = compose(f, g)

# Partial application
let double = partial(multiply, 2)

# Currying
let curriedAdd = curry(|a, b| -> a + b)

Key Takeaways

  1. Functions are first-class values in GoloScript that can be passed around and used just like any other value

  2. Closures capture variables from their enclosing scope, enabling powerful abstraction patterns

  3. Variable capture creates private state and factory patterns without needing classes

  4. Higher-order functions (map, filter, reduce, compose) enable functional programming styles

  5. Multiple syntaxes offer flexibility:

    • Arrow syntax (->) for concise lambdas
    • Block syntax ({ }) for multi-statement bodies
    • Compact form for simple expressions
  6. Lazy evaluation is natural with closures - code inside isn’t executed until the closure is called

  7. Composition and partial application allow building complex functions from simpler ones

© 2026 GoloScript Project | Built with Gu10berg

Subscribe: 📡 RSS | ⚛️ Atom