Release: 0.49 - Arity Lane
0.49.0 - Arity Lane
Released 2026-07-22 · GitHub release
20 new core fns (clojure.set relational helpers, atom API, pr/prn family) + trace namespace + 1.5-2x faster multi-arity calls + NaN fixes in sorted collections
🎉 Added
every-pred: complement ofsome-fn; true only when every predicate passes for every argument (#2738)mapvandfilterv: eager vector variants ofmapandfilter(#2739)unsigned-bit-shift-right: logical right shift that zero-fills the high bits (#2742)bit-and-not: bitwiseandwith the complement of each subsequent argument (#2744)rational?: predicate for rational numbers (integers,Ratio,BigDecimal; not floats) (#2745)while: evaluates its body for side effects while a test is logical true (#2746)compare-and-set!,swap-vals!,reset-vals!: complete the atom API; the-vals!variants return[old new](#2747)println-str: likeprintlnbut returns the string (#2748)distinct?: true when no two arguments are=(#2749)bounded-count: counts a collection, walking at mostnelements of a non-counted?sequence (#2750)splitv-at: eager vector variant ofsplit-at(#2751)map-invert: returns the map with keys and values swapped (#2753)infinite?: alias forinf?(#2754)select,project,rename:clojure.set-style relational helpers over sets (#2755)index:clojure.set-style grouping of a relation keyed by selected columns (#2756)subseqandrsubseq: lazy ascending/descending range queries over sorted maps and sets, honoring their comparator (#2757)random-sample: keeps each item with probabilityprob; lazy, with a transducer arity (#2759)pr,prn,pr-str,prn-str: readable-print family (strings quoted and escaped); char literals like\Aare one-char strings and print as"A"(#2743)phel\tracenamespace in the spirit ofclojure.tools.trace:trace,trace-fn,deftrace,dotrace,reset-trace-state!; prints to stderr- The REPL registers
phel.repl/print-tapon startup, so(tap> x)prints out of the box; detach with(remove-tap print-tap)
⚖️ Changed
- Gacela updated to 1.18: the persisted merged-config cache is now scoped per app root, so projects sharing the default system temp dir no longer read each other's merged config; Phel's freshness invalidator targets the scoped cache file
partition-allaccepts Clojure's[n step coll]arity (#2758)partitionaccepts Clojure's[n step coll]and[n step pad coll]arities;[n coll]unchanged (#2752)
Performance
- Opt-in
strip-symbol-metabuild option (PhelConfig::withStripSymbolMeta()) removes symbol metadata from compiled artifacts: −28% size, −40% cold require, −2MB peak on this repo.phel doc/(meta ...)over built defs return nil, and toggling forces a full recompile (#2769) - Core fns with guaranteed primitive returns (
count,compare,str,name, the print-str family) carry return tags, so bindings from them type-infer and hit the native-op emitter paths (#2767) if/and/ortests that lower to native PHP comparisons splice directly into the condition, dropping the truthiness adapter (#2766)- Multi-arity fns emit fixed-arity
invokeArityNmethods; build-mode call sites with a known arity skip the variadic__invokedispatch (roughly 1.5-2x faster per call) (#2760)
🐛 Fixed
- Build cache writes and deletes route through Gacela's
FileCacheprimitives (writeContentsAtomically,delete), which lock, treat a short-byte write as failure, and invalidate opcache for the final path; a plainfile_put_contents(...) === falsemissed truncated disk-full writes and a stale opcode entry could serve old content after a rewrite phel runon an ad-hoc script fails immediately with the extractor's error when a required sibling.phelfile cannot be read or parsed, instead of silently skipping it; namespaces that resolve elsewhere remain tolerated#instliterals inside macro-expanded forms no longer crash compilation in the location-enrichment walk (#2778)- Multi-arity
fnreturned from another fn no longer emits invalid PHP; per-arity closures are analyzed in expression context (#2776) - String concatenation no longer specializes over
php/json_encode,php/hex2bin, orphp/str_replace, whosestring|false/string|arrayreturns could emit""wherestrrenders"false"; return-type tables consolidated intoPhpFunctionReturnTypes(#2768) phel test --coveragefails fast with a hint when xdebug'scoveragemode is inactive, instead of PHP warnings and an empty report (#2764)- Self-recursion inside a wrapped def init (e.g.
(def f (wrap (fn ...)))) routes through the registry instead of the invalid$thisshortcut; direct(def f (fn ...))inits keep the fast path - Lexer deprecation warnings for bare
#and#| |#comments no longer promise removal "in Phel v0.33", a version that shipped 15 releases ago; they now read "in a future release", matching the backslash-separator warning (#2783) - Sorted sets carry the same NaN guard as hash sets, so a distinct sorted set holding
NANis never=to another set; previously this held only by accident of the default comparator and broke under a user comparator that ordersNANequal to itself (#2782) phel list:modulesandphel cache:warmno longer fatal in projects whose test suite holds a class that cannot be loaded standalone; the newapp-module-pathsconfig key (PhelConfig::withAppModulePaths()) scopes Gacela's module discovery, defaulting to today's whole-root walk (#2787)- The REPL reports a mistyped closing bracket instead of hanging: typing
(php/+ 1and then]left it buffering for input that could never arrive, with no feedback and no way out but Ctrl-D. A closer with no matching opener is now handed to the parser, which raises the located error it always had ready - Sorted sets and sorted maps no longer store duplicate
NANunder the default comparator: PHP'sNAN <=> NANis1, so the binary search never relocated an existingNANand every insert appended a fresh one. The default comparator now matches JavaDouble.compare/ Clojurecompare(NaN equals itself, orders after every number), so(count (sorted-set NAN NAN))is1and(contains? (sorted-set NAN) NAN)istrue(#2789)
👥 Contributors
Full Changelog: v0.48.0...v0.49.0
Downloads
v0.49.0
- phel.phar (2.08 MB)