Debugging & Profiling
Nostos provides built-in tools for debugging your code and measuring performance. These tools are available in the REPL and help you understand what your code is doing and where time is being spent.
The REPL
The Nostos REPL (Read-Eval-Print Loop) is your primary environment for interactive development. Start it by running nostos without arguments:
$ nostos
Nostos REPL v0.1.0
Type :help for available commands
nostos> 1 + 2
3
nostos> fib(n) = if n < 2 then n else fib(n-1) + fib(n-2)
nostos> fib(10)
55
REPL Commands
The REPL provides several special commands, all prefixed with :.
| Command | Description |
|---|---|
:help |
Show available commands |
:load <file> |
Load and execute a .nos file |
:profile <expr> |
Run expression with timing |
:debug <fn> |
Set a breakpoint on a function |
:undebug <fn> |
Remove a breakpoint |
:type <expr> |
Show the type of an expression |
:demo |
Load demo folder with examples |
Profiling with :profile
The :profile command measures how long an expression takes to execute. This is essential for identifying performance bottlenecks.
# Profile a simple expression
nostos> :profile fib(30)
Result: 832040
Time: 45.2ms
# Profile a more complex computation
nostos> :profile [1..1000].map(x => x * x).filter(x => x % 2 == 0).sum()
Result: 166666500
Time: 0.8ms
# Compare different implementations
nostos> :profile naive_sort(big_list)
Time: 1250ms
nostos> :profile quick_sort(big_list)
Time: 12ms
Profiling Tips
- Run the same expression multiple times to account for JIT warmup
- Profile with realistic data sizes
- Compare different approaches with the same input
- Look for unexpected slowdowns in hot paths
Debugging with Breakpoints
The :debug command sets breakpoints on functions. When a breakpointed function is called, execution pauses and you can inspect the arguments and state.
# Define a function
nostos> factorial(0) = 1
nostos> factorial(n) = n * factorial(n - 1)
# Set a breakpoint
nostos> :debug factorial
# Now run it - execution will pause at each call
nostos> factorial(5)
[BREAK] factorial(5)
Press Enter to continue, 'c' to continue without stopping...
[BREAK] factorial(4)
Press Enter to continue...
# Continue until done
c
Result: 120
Managing Breakpoints
# List all active breakpoints
nostos> :debug
Active breakpoints:
- factorial
- process_data
# Remove a specific breakpoint
nostos> :undebug factorial
Breakpoint removed: factorial
# Clear all breakpoints
nostos> :undebug
All breakpoints cleared
Print Debugging
Sometimes the simplest approach is the best. Use println and show to trace values through your code.
process_order(order) = {
println("Processing order: " ++ show(order))
validated = validate(order)
println("After validation: " ++ show(validated))
priced = calculate_price(validated)
println("After pricing: " ++ show(priced))
finalize(priced)
}
# The dbg function is a shorthand that prints and returns the value
# useful for inserting into expressions without changing them
calculate(x) = {
intermediate = dbg(x * 2) # Prints: [dbg] 20
intermediate + 10
}
main() = calculate(10) # Returns 30, prints "[dbg] 20"
Type Inspection with :type
Use :type to see the inferred type of any expression without evaluating it.
nostos> :type 42
Int
nostos> :type [1, 2, 3]
List[Int]
nostos> :type x => x * 2
Int -> Int
nostos> :type %{"name": "Alice", "age": 30}
Map[String, Int | String]
nostos> :type Some
T -> Option[T]
Common Debugging Patterns
Debugging Pattern Matching
When a match isn't working as expected, add a catch-all pattern that prints the unexpected value:
process(data) = match data
Some(x) -> handle(x)
None -> default_value()
other -> {
println("Unexpected value: " ++ show(other))
panic("Unhandled case in process")
}
end
Debugging Concurrent Code
For concurrent programs, include the PID in your debug output:
worker(id) = {
me = self()
log(msg) = println("[Worker " ++ show(id) ++ " PID:" ++ show(me) ++ "] " ++ msg)
log("Starting")
result = do_work()
log("Finished with: " ++ show(result))
result
}
Debugging Recursive Functions
Track recursion depth to understand the call structure:
tree_sum(Empty, depth) = {
println(String.repeat(" ", depth) ++ "Empty -> 0")
0
}
tree_sum(Node(val, left, right), depth) = {
indent = String.repeat(" ", depth)
println(indent ++ "Node(" ++ show(val) ++ ")")
l = tree_sum(left, depth + 1)
r = tree_sum(right, depth + 1)
result = val + l + r
println(indent ++ " = " ++ show(result))
result
}
Remember: Remove debug output before committing code. Consider using a DEBUG flag that can be toggled:
DEBUG = false
debug_log(msg) = if DEBUG then println("[DEBUG] " ++ msg) else ()