← Back to Blogs
HN Story

Phel v0.36.0: Bringing a Numeric Tower and First-Class Vars to PHP

May 8, 2026

Phel v0.36.0: Bringing a Numeric Tower and First-Class Vars to PHP

Phel, a Clojure-flavored Lisp that compiles to PHP, has released version 0.36.0. This update represents a major leap in the language's precision and flexibility, introducing a robust numeric tower and first-class Var handles. For developers looking to combine the expressive power of Lisp with the ubiquity of the PHP ecosystem, these changes provide critical tools for high-precision arithmetic and advanced metaprogramming.

The Numeric Tower: Precision and Arbitrary Scale

One of the most significant additions in v0.36.0 is the implementation of a "numeric tower," allowing Phel to handle numbers with far greater precision than standard PHP floats and integers.

Rationals and BigIntegers

  • Exact Rationals: Phel now supports ratio literals (e.g., 1/2, -3/4), which parse directly to Rational types. This eliminates rounding errors inherent in floating-point division for fractions.
  • BigInteger Support: The language now includes BigInteger for arbitrary-precision integers. Notably, PHP integers now auto-promote to BigInteger upon overflow during arithmetic operations (+, -, *, **), ensuring calculations don't silently wrap or lose precision.
  • BigDecimal: For fixed-precision decimal arithmetic, BigDecimal is now available, utilizing M-suffix literals (e.g., 1.5M).

Behavioral Changes in Arithmetic

To support this tower, several core behaviors have changed. Most notably, (/ int int) now returns a Rational if the result is not an integer, rather than defaulting to a float. Developers requiring float division should explicitly use (double ...) or ensure one operand is a float (e.g., (/ 1.0 2)).

First-Class Vars and Dynamic Binding

Version 0.36.0 introduces first-class Var handles, moving beyond simple symbol resolution to allow developers to manipulate the variables themselves as objects.

New Var Capabilities

  • Var Handles: The new (var sym) special form and #'sym reader macro allow for the creation and manipulation of Var handles.
  • Dynamic Rebinding: With with-redefs and with-bindings, developers can now rebind dynamic vars from a map, with the system ensuring that original values are restored even if an exception occurs.
  • Observability: The PhelVarStateRegistry now supports "watches," allowing developers to use add-watch and remove-watch to trigger actions when a Var is altered via alter-var-root.

Expanded Type System and Collections

Beyond numbers and vars, Phel has expanded its set of native value types to better integrate with modern application requirements:

  • UUIDs: Native support for UUIDs via #uuid "...", random-uuid, and parse-uuid.
  • Persistent Queues: A new PersistentQueue provides a FIFO structure with amortized O(1) performance for push, peek, and pop operations.
  • PHP Integration: The PhpClass value type now wraps normalized PHP class/interface Fully Qualified Names (FQNs), streamlining the bridge between Lisp and PHP.
  • MapEntry: The MapEntry type is now equal by value to a 2-element vector, improving the ergonomics of hash map iteration.

Performance and Tooling Improvements

Efficiency gains in this release focus heavily on the developer experience (DX) during the boot process:

  • Faster Boot Times: The REPL and test runner now prune common heavy directories like vendor/, .git/, and node_modules/ during scans, with directory scans memoized per process.
  • Optimized Testing: The phel test command now includes powerful new flags: --list to discover tests without running them, --last-failed to iterate on regressions, and --slowest=N to identify performance bottlenecks in the test suite.
  • Namespace Standardization: The core library has transitioned to dot-separated class FQNs for (use ...) statements, providing a more consistent syntax across the ecosystem.

Summary of Breaking Changes

Users upgrading to v0.36.0 should be aware of a few breaking shifts:

  1. Division: (/ int int) now returns Rational instead of float when the result is non-integer.
  2. UUIDs: uuid? now strictly accepts Phel\Lang\Uuid objects rather than raw strings.
  3. Binding: The binding function now throws an InvalidArgumentException for non-dynamic vars; with-redefs should be used instead.

References

HN Stories