The Phel standard library ships with helper functions and macros that make it easier to inspect values during development. These tools are perfect for quick debugging without setting up external tools.
dbg
dbg evaluates an expression, prints the expression together with the resulting value, and finally returns that value. It is handy for quick one-off inspections in the middle of a pipeline:
# OUTPUT:
; (inc 41) => 42
Use cases:
- Debugging data transformation pipelines
- Checking intermediate values in threading macros
- Quick value inspection without breaking code flow
spy
spy works like dbg but lets you provide an optional label so you can distinguish multiple probes:
# OUTPUT:
; SPY "before" => 11
; SPY "after" => 22
Use cases:
- Multiple debug points in the same function
- Tracking values at different stages
- Distinguishing between similar expressions
tap
tap passes the value through unchanged while optionally executing a handler for side effects (logging, assertions, etc.). Without a handler the value is printed using print-str:
# OUTPUT:
; TAP => (0 1 2)
; count 3
Use cases:
- Non-intrusive debugging in pipelines
- Logging without modifying data flow
- Custom inspection with handler functions
- Assertions during development
dotrace
dotrace wraps a function so every call and result are printed with indentation that reflects nesting depth. This is useful to understand the flow of recursive functions:
# OUTPUT:
; TRACE t00: (fib 3)
; TRACE t01: | (fib 2)
; TRACE t02: | | (fib 1)
; TRACE t02: | | => 1
; TRACE t03: | | (fib 0)
; TRACE t03: | | => 0
; TRACE t01: | => 1
; TRACE t04: | (fib 1)
; TRACE t04: | => 1
; TRACE t00: => 2
Use cases:
- Understanding recursive function behavior
- Debugging complex call chains
- Visualizing function execution flow
- Performance analysis (counting calls)
Trace Utilities
You can reset the tracing counters between runs with reset-trace-state! and configure the amount of zero-padding for trace identifiers with set-trace-id-padding!.
# Reset counters
# Adjust ID padding (default is 2)
# t000, t001, etc.
Best Practices
Use dbg for Quick Checks
# Instead of breaking the pipeline
# Just add dbg where needed
Use spy with Labels
# Clear labels help identify output
Use tap for Custom Logic
# Custom validation during development
Use dotrace Sparingly
Tracing generates a lot of output. Use it for specific functions you need to understand, not entire codebases:
# Good: Trace specific recursive function
# Bad: Don't trace everything
# (def traced-everything (dotrace 'main main))
Removing Debug Code
All these helpers are designed to be easy to add and remove:
# During development
# For production - just remove the (dbg)
Consider using a macro to conditionally enable debugging:
# Only runs when DEBUG env var is set
Next Steps
- For deeper debugging, set up XDebug
- Use PHP native tools for familiar debugging
- Check the API documentation for more debug functions