Release: 0.53 - Floor Raised
0.53.0 - Floor Raised
Released 2026-09-24 · GitHub release
PHP 8.5 minimum + 2-4x faster assoc/get-in/update-in + short type tags (^map, ^vector) + phel bench --ab
⚖️ Changed
- BREAKING: PHP 8.5 is the minimum. The floor moves now: once
1.xstarts, raising it needs a major. (#3302) - BREAKING (PHP API):
FilesystemFacadeInterfacemoves fromPhel\FilesystemtoPhel\Shared\Facade.Phel\Fiber\FiberFacadeInterfaceis gone: type-hintPhel\Fiber\FiberFacadeinstead. (#2859) - BREAKING (PHP API):
MetaInterface::withMeta()always returns a copy. Symbols, keywords, atoms and functions are no longer changed in place, so keep the returned value. For atoms, useAtom::alterMeta()andAtom::resetMeta(). (#3311, ADR 0019) - BREAKING (PHP API): lint rule codes move to the public
Phel\Shared\LintRuleCodes. The strings stay the same, sophel lint --format=jsonconsumers keep working. Renaming a code is now a breaking change.Phel\Shared\Exceptions\ErrorCodeis public too. (#3309)
🎉 Added
- Short type tags for Phel values:
^map,^vector,^set,^list,^keyword,^symboland^atom.?mapandmap|nullwork too.(defn f [^map m] (:k m))gets the fast->findlookup without the full interface name. See "Type tags" indocs/spec/language-surface.md. (#3319) - Collection methods that return a copy are
#[NoDiscard]. Dropping the result of(.put m k v), or of an inlined(conj v x)/(assoc m :b 2)outside tail position, now warns instead of doing nothing. Cast to(void)when it is deliberate. Transients are exempt, exceptTransientArrayMap::put, which can return a new transient. (#3303) phel bench --ab=<git-ref> --pairs=Ncompares the working tree with a git ref, one A/B run per pair. Each row shows the mean delta, ornoisewhen the pairs disagree on the sign.--tolerancefails only when every pair is slower by more than the tolerance. The working tree is never touched. Seedocs/benchmarking.md. (#3324)- Public PHP API, since compiled code calls it:
Phel\Lang\GetIn,Phel\Lang\AssocInandPhel\Lang\Collections\ValueIdentity. (#3320 #3321 #3328)
⚡ Performance
Literal call shapes compile to direct code, with the same results. A key list from a variable, apply, or a local that shadows the core fn keeps the runtime call.
(assoc m k1 v1 k2 v2 ...): one step per pair, 3.4x to 3.7x faster.(assoc! t ...)is 3.4x faster. Thetransientdocstring now says when a transient pays off: about eight writes on a map. (#3317 #3318)(get-in m [k1 k2 ...]): 2.9x faster for two keys, 4.5x for eight keys with a default. (#3320)(assoc-in m [...] v)and(update-in m [...] f & args): 1.9x to 2.8x faster. (#3328)- At optimization level 2,
(update m k (fn [v] ...))in tail position inlines a simple literal fn: no closure, about 9% faster on a 90-key map. Off at the default level, for a:redefupdate, and for a body that could notice it runs in the caller's frame. (#3322) Runtime: assocof the value an index already holds returns the same vector: 2.4x faster on 100 elements. Same forrest/subvecvectors and structs. A new value costs about 3% more on a vector and 5% less on a hash map. (#3321)intowith a transducer, the core transducers andreducebuild fewer closures.(into [] (comp (map f) (filter :alive)) coll)drops from 14.2μs to 11.8μs,(reduce + 0 v)over 32 ints from 9.6μs to 6.8μs. One change, toward Clojure: a custom transducer that calls its reducing fn with extra arguments reachesconj's variadic arity instead of throwing. (#3323)
🐛 Fixed
Compiler:
- BREAKING: an octal escape above
\377is a compile error. It used to wrap silently:"\400"compiled to NUL. Clojure rejects it too. Char-literal errors now show file, line, snippet and caret. (#3301) - A bare tag after a
:useimport,:asalias included, resolves to the imported class. It used to reach the generated PHP unqualified and fail at call time. The tag resolves where it is declared, so a caller mapping the same alias to another class no longer infers the wrong type and throwsTypeError. Union and intersection tags ondefstructanddefinterfaceresolve the same way. (#3319) Collections: assocon a map keeps a new value that is=to the stored one but not identical:-0.0over0.0, aBigIntover an equalint, a vector with new metadata. The unchanged check compares by identity, as Clojure does. A PHP array always counts as a change, so two recursive arrays no longer throwNesting level too deep. Applies to every map type, transients, structs and vectors. (#3321) Tooling:phel test --parallelworkers run with the OPcache JIT off. A php.ini that enables the JIT, as on every GitHub runner, crashed workers with SIGBUS on arm64 macOS. A dead worker now reports its exit code or signal and its output. Also fixed: a large stderr write no longer hangs the run, stray output counts as a crash, and a namespace that kills its worker on every retry no longer stops the rest of the queue. (#3334)
👥 Contributors
Full Changelog: v0.52.0...v0.53.0
Downloads
v0.53.0
- phel.phar (2.67 MB)