Advanced Features¶
Language: Español | English
This chapter covers Crespi's advanced features: memoization, tail-call optimization, and more.
Memoization¶
Memoization caches function results to avoid repeated calculations.
The @memoize Decorator¶
Apply automatic memoization to a function:
@memoize
fn fibonacci(n: Int) -> Int {
if n <= 1 {
return n
}
return fibonacci(n - 1) + fibonacci(n - 2)
}
print(fibonacci(40)) // Fast thanks to cache
Without Memoization (Slow)¶
Without memoization, Fibonacci calculates the same values many times:
// Without @memoize - very slow for large n
fn fib_slow(n: Int) -> Int {
if n <= 1 {
return n
}
return fib_slow(n - 1) + fib_slow(n - 2)
}
// fib_slow(40) would take a very long time
The memoize() Function¶
You can also use the memoize() function directly:
fn factorial(n: Int) -> Int {
if n <= 1 {
return 1
}
return n * factorial(n - 1)
}
var factorial_memo = memoize(factorial)
print(factorial_memo(100)) // Uses cache
print(factorial_memo(100)) // Retrieves from cache instantly
When to Use Memoization¶
- Recursive functions with repeated subproblems
- Pure functions (no side effects)
- Expensive calculations with the same arguments
// Good case: expensive calculation, repeated arguments
@memoize
fn calculate_route(origin: String, destination: String) -> String {
// Simulate expensive calculation
return origin + " -> " + destination
}
// Bad case: function with side effects
// Do not use @memoize here
fn get_time() -> String {
// This would return the same cached value forever
return "current time"
}
With Short Syntax¶
@memoize
fn square(n: Int) -> Int = n * n
@memoize
fn cube(n: Int) -> Int = n * n * n
print(square(1000)) // Calculated
print(square(1000)) // From cache
Function Inlining¶
Function inlining replaces function calls with the function body, eliminating call overhead.
The @inline Decorator¶
Mark functions for inlining at compile time:
@inline
fn double(x: Int) -> Int {
return x * 2
}
fn main() {
// The compiler replaces this with: result = 21 * 2
var result = double(21)
print(result)
}
When to Use @inline¶
- Small utility functions called frequently
- Functions in performance-critical loops
- Simple getters/setters
@inline
fn square(n: Int) -> Int = n * n
@inline
fn isPositive(n: Int) -> Bool = n > 0
// Good candidates: small, frequently called
for i in range(1000000) {
if isPositive(i) {
total = total + square(i)
}
}
Limitations¶
- Recursive functions are never inlined (would cause infinite expansion)
- Functions with closures are not inlined
- The decorator has no effect on the interpreter (runtime behavior unchanged)
Auto-Inlining (at -O2)¶
At optimization level -O2, the compiler automatically inlines small functions (≤5 statements) even without the decorator:
// This function is auto-inlined at -O2
fn add(a: Int, b: Int) -> Int {
return a + b
}
See Compiler Documentation for more details.
Tail-Call Optimization (TCO)¶
Crespi automatically optimizes tail-recursive functions, allowing deep recursion without stack overflow.
What is Tail Recursion?¶
A recursive call is "tail" when it's the last operation of the function:
// Tail recursion - optimizable
fn factorial_tail(n: Int, acc: Int = 1) -> Int {
if n <= 1 {
return acc
}
return factorial_tail(n - 1, n * acc) // Last operation
}
// NOT tail recursion
fn factorial_normal(n: Int) -> Int {
if n <= 1 {
return 1
}
return n * factorial_normal(n - 1) // Multiplication after
}
Benefits of TCO¶
// Without TCO, this would cause stack overflow
// With TCO, it works for very large values
fn sum_to(n: Int, acc: Int = 0) -> Int {
if n <= 0 {
return acc
}
return sum_to(n - 1, acc + n)
}
print(sum_to(10000)) // 50005000 - Works thanks to TCO
Converting to Tail Recursion¶
General pattern: use an accumulator:
// Fibonacci with accumulators (TCO)
fn fibonacci_tail(n: Int, a: Int = 0, b: Int = 1) -> Int {
if n == 0 {
return a
}
if n == 1 {
return b
}
return fibonacci_tail(n - 1, b, a + b)
}
print(fibonacci_tail(50)) // Fast and efficient
Conversion Examples¶
Power:
// Normal
fn power(base: Int, exp: Int) -> Int {
if exp == 0 {
return 1
}
return base * power(base, exp - 1) // Not tail
}
// With TCO
fn power_tail(base: Int, exp: Int, acc: Int = 1) -> Int {
if exp == 0 {
return acc
}
return power_tail(base, exp - 1, acc * base) // Is tail
}
print(power_tail(2, 20)) // 1048576
Single-Expression Functions¶
Compact syntax for simple functions:
// Standard form
fn double(x: Int) -> Int {
return x * 2
}
// Single expression (equivalent)
fn double(x: Int) -> Int = x * 2
Multiple Parameters¶
fn sum(a: Int, b: Int) -> Int = a + b
fn average(a: Float, b: Float) -> Float = (a + b) / 2.0
// For complex logic, use standard form
fn maximum(a: Int, b: Int) -> Int {
if a > b {
return a
}
return b
}
In Classes¶
class Vector(let x: Float, let y: Float) {
fn magnitude() -> Float = (this.x * this.x + this.y * this.y)
fn scale(factor: Float) -> Vector = Vector(this.x * factor, this.y * factor)
}
Automatic Semicolon Insertion (ASI)¶
Crespi automatically inserts semicolons at the end of lines when appropriate.
When It Works¶
// Semicolons are optional at end of line
var x = 10
var y = 20
print(x + y)
// Equivalent to:
var x = 10;
var y = 20;
print(x + y);
When to Use Explicit Semicolons¶
For multiple statements on one line:
var a = 1; var b = 2; print(a + b)
Advanced Closures¶
Encapsulated State¶
fn create_module() -> Dict[String, () -> Int | (Int) -> ()] {
var _private = 0
fn increment() {
_private += 1
}
fn get() -> Int {
return _private
}
fn set(value: Int) {
_private = value
}
return {
"increment": increment,
"get": get,
"set": set
}
}
var module = create_module()
module["increment"]()
module["increment"]()
print(module["get"]()) // 2
Currying¶
fn curry_add(a: Int) -> (Int) -> Int {
fn add_b(b: Int) -> Int {
return a + b
}
return add_b
}
var add_5 = curry_add(5)
print(add_5(3)) // 8
print(add_5(10)) // 15
Manual Memoization¶
fn [T, U] create_cache(fn: (T) -> U) -> (T) -> U {
var cache = {}
fn cached(arg: T) -> U {
var key = str(arg)
if cache.contains(key) {
return cache[key]
}
var result = fn(arg)
cache[key] = result
return result
}
return cached
}
fn expensive_calc(n: Int) -> Int {
// Simulate expensive calculation
return n * n
}
var cached_calc = create_cache(expensive_calc)
print(cached_calc(100)) // Calculates
print(cached_calc(100)) // From cache
Higher-Order Functions¶
Function Composition¶
fn [T, U, V] compose(f: (U) -> V, g: (T) -> U) -> (T) -> V {
fn composed(x: T) -> V {
return f(g(x))
}
return composed
}
fn double(x: Int) -> Int = x * 2
fn increment(x: Int) -> Int = x + 1
var double_then_increment = compose(increment, double)
print(double_then_increment(5)) // 11 (5*2 + 1)
var increment_then_double = compose(double, increment)
print(increment_then_double(5)) // 12 ((5+1) * 2)
Partial Application¶
fn [A, B, R] partial(fn: (A, B) -> R, first_arg: A) -> (B) -> R {
fn applied(second_arg: B) -> R {
return fn(first_arg, second_arg)
}
return applied
}
fn power(base: Int, exp: Int) -> Int {
var r = 1
var i = 0
while i < exp {
r = r * base
i += 1
}
return r
}
var power_of_2 = partial(power, 2) // base = 2
print(power_of_2(3)) // 2^3 = 8
print(power_of_2(4)) // 2^4 = 16
Pipeline¶
fn [T] pipe(value: T, functions: List[(T) -> T]) -> T {
var result = value
for fn in functions {
result = fn(result)
}
return result
}
fn double(x: Int) -> Int = x * 2
fn increment(x: Int) -> Int = x + 1
fn square(x: Int) -> Int = x * x
var result = pipe(3, [double, increment, square])
print(result) // ((3*2)+1)^2 = 49
Design Patterns¶
Custom Iterator¶
fn create_range(start: Int, end: Int) -> Dict[String, () -> Bool | () -> Int] {
var current = start
fn has_next() -> Bool {
return current < end
}
fn next() -> Int {
var value = current
current += 1
return value
}
return {
"has_next": has_next,
"next": next
}
}
var range = create_range(1, 5)
while range["has_next"]() {
print(range["next"]())
}
// 1, 2, 3, 4
Observer (Simplified)¶
fn [T] create_observable() -> Dict[String, Any] {
var _observers = []
var _value: T? = null
fn subscribe(observer: (T) -> ()) {
_observers.push(observer)
}
fn notify() {
for obs in _observers {
obs(_value)
}
}
fn set(new_value: T) {
_value = new_value
notify()
}
fn get() -> T? {
return _value
}
return {
"subscribe": subscribe,
"set": set,
"get": get
}
}
var state = create_observable()
state["subscribe"](fn(value: Int) {
print("Observer 1: " + str(value))
})
state["subscribe"](fn(value: Int) {
print("Observer 2: " + str(value))
})
state["set"](42)
// Observer 1: 42
// Observer 2: 42