Functions and Processes in Topaz
In Topaz, functions are more accurately referred to as Processes. They are the primary building blocks for logic and abstraction. A core tenet of the language is that processes are first-class citizens, a principle that grants them incredible power and flexibility by deeply embedding functional programming concepts into the language.
Defining Processes
Processes are typically created as anonymous functions using the λ
(lambda) special form. To give a process a name, you bind this anonymous process to an identifier using the $
special form.
Syntax:
($ PROCESS_NAME
(λ (PARAM_1 PARAM_2 ...)
;; Body expression(s) go here.
;; The result of the last expression is returned.
)
)
Example:
;; A simple process that adds two numbers
($ add
(λ (x y)
(+ x y)
)
)
;; Calling the process is like any other operation
(add 5 10) ;; Evaluates to 15
First-Class Citizens
Being "first-class" means processes are treated like any other data value (such as a number or a string). They can be:
1. Stored in Variables/Identifiers
You can bind a process to an identifier. This allows you to refer to it by name, and even assign it to other identifiers.
($ greeting-process (λ (name) (CONCAT "Hello, " name)))
($ say-hello greeting-process) ;; Assigning the process value to a new identifier
(say-hello "Topaz") ;; Evaluates to "Hello, Topaz"
2. Passed as Arguments
You can pass processes as arguments to other processes. This is a fundamental concept for enabling powerful patterns like callbacks and higher-order functions.
;; A process that takes another process 'f' and a value 'v',
;; and applies 'f' to 'v'.
($ apply-it
(λ (f v)
(f v)
)
)
($ square (λ (n) (* n n)))
;; Here, we pass the 'square' process as an argument.
(apply-it square 10) ;; Evaluates to 100
;; Generic higher-order function example
($ compose
(λ [A B C] (f: (Process B C) g: (Process A B))
(λ (x: A)
(f (g x))
)
)
)
3. Returned from Other Processes
A process can create and return another, often specialized, process.
;; This process doesn't just return a value; it returns a new process.
($ make-adder
(λ (amount-to-add)
;; This inner lambda is what gets returned.
(λ (n)
(+ n amount-to-add)
)
)
)
;; Create specialized versions of the adder.
($ add-five (make-adder 5))
($ add-ten (make-adder 10))
(add-five 100) ;; Evaluates to 105
(add-ten 100) ;; Evaluates to 110
This powerful pattern utilizes Closures, where the returned inner process "remembers" the amount-to-add
value from the environment in which it was created.
Recursion
In Topaz, as in many functional languages, iteration is most idiomatically achieved through recursion, rather than traditional for
or while
loops. This approach avoids side effects and state mutations, aligning with the principle of immutability.
;; A recursive process to calculate the factorial of a number.
($ factorial
(λ (n)
(IF (<= n 1)
1 ;; Base case: factorial of 0 or 1 is 1.
(* n (factorial (- n 1))) ;; Recursive step: n * factorial(n-1).
)
)
)
(factorial 5) ;; Evaluates to 120 (which is 5 * 4 * 3 * 2 * 1)
;; Tail-recursive version for better performance
($ factorial-tail
(λ (n)
($ factorial-helper
(λ (n acc)
(IF (<= n 1)
acc
(factorial-helper (- n 1) (* n acc))
)
)
)
(factorial-helper n 1)
)
)
Generic and Higher-Order Processes
Topaz v1.0 supports both generic type parameters and higher-order functions, enabling powerful abstractions.
Generic Processes
Generic processes can work with multiple types by declaring type parameters:
;; A generic identity function
($ identity
(λ [T] (value: T)
value
)
)
;; A generic map function for vectors
($ vector-map
(λ [A B] (f: (Process A B) vec: (Vector A))
;; Implementation would be provided by the standard library
(vector:map f vec)
)
)
;; Usage with type inference
($ numbers [1 2 3 4])
($ doubled (vector-map (λ (x) (* x 2)) numbers)) ;; A=Integer, B=Integer inferred
Currying and Partial Application
Topaz naturally supports currying through its lambda syntax:
;; A curried addition function
($ add-curried
(λ (x)
(λ (y)
(+ x y)
)
)
)
;; Create specialized functions through partial application
($ add-five (add-curried 5))
($ add-ten (add-curried 10))
(io:print (core:to-string (add-five 7))) ;; Prints "12"
(io:print (core:to-string (add-ten 20))) ;; Prints "30"