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 toRationaltypes. This eliminates rounding errors inherent in floating-point division for fractions. - BigInteger Support: The language now includes
BigIntegerfor arbitrary-precision integers. Notably, PHP integers now auto-promote toBigIntegerupon overflow during arithmetic operations (+,-,*,**), ensuring calculations don't silently wrap or lose precision. - BigDecimal: For fixed-precision decimal arithmetic,
BigDecimalis now available, utilizingM-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#'symreader macro allow for the creation and manipulation ofVarhandles. - Dynamic Rebinding: With
with-redefsandwith-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
PhelVarStateRegistrynow supports "watches," allowing developers to useadd-watchandremove-watchto trigger actions when aVaris altered viaalter-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, andparse-uuid. - Persistent Queues: A new
PersistentQueueprovides a FIFO structure with amortized O(1) performance forpush,peek, andpopoperations. - PHP Integration: The
PhpClassvalue type now wraps normalized PHP class/interface Fully Qualified Names (FQNs), streamlining the bridge between Lisp and PHP. - MapEntry: The
MapEntrytype 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/, andnode_modules/during scans, with directory scans memoized per process. - Optimized Testing: The
phel testcommand now includes powerful new flags:--listto discover tests without running them,--last-failedto iterate on regressions, and--slowest=Nto 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:
- Division:
(/ int int)now returnsRationalinstead offloatwhen the result is non-integer. - UUIDs:
uuid?now strictly acceptsPhel\Lang\Uuidobjects rather than raw strings. - Binding: The
bindingfunction now throws anInvalidArgumentExceptionfor non-dynamic vars;with-redefsshould be used instead.