In Lispex, functions are the primary building blocks for logic and abstraction. Functions are first-class citizens: you can bind them, pass them, and return them.
Defining Functions
Create named functions with define sugar, and anonymous functions with lambda.
Named Functions with define
(define (add x y)
(+ x y))
(add 5 10) ; ⇒ 15
Anonymous Functions with lambda
(lambda (n) (* n 2))
Functions as First-Class Citizens
Being "first-class" means functions are treated like any other data value (such as a number or a list). They can be:
1. Stored in bindings
Bind functions like any other value.
(define square (lambda (n) (* n n)))
(define square-alias square)
(square-alias 10) ; ⇒ 100
2. Passed as Arguments
You can pass functions as arguments to other functions. This is fundamental to higher-order functions.
(define (apply-it f v)
(f v))
(define square (lambda (n) (* n n)))
(apply-it square 10) ; ⇒ 100
3. Returned from Other Functions
A function can create and return another function. This powerful pattern is explained in the "Closures" section below.
Understanding Closures
When a function is created, it captures the environment (the variables and their values) in which it was defined. This captured environment is called a closure. This allows a function to "remember" values, even when it's called outside of its original scope.
Example: A Function Factory
(define (make-adder amount-to-add)
(lambda (n)
(+ n amount-to-add)))
(define add-five (make-adder 5))
(define add-ten (make-adder 10))
(add-five 100) ; ⇒ 105
(add-ten 100) ; ⇒ 110
Recursion
In Lispex, iteration is most idiomatically achieved through recursion. This approach avoids side effects and state mutations.
(define (factorial n)
(if (<= n 1)
1
(* n (factorial (- n 1)))))
(factorial 5) ; ⇒ 120