Skip to main content

Release: 0.49 - Arity Lane

Latest update

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 of some-fn; true only when every predicate passes for every argument (#2738)
  • mapv and filterv: eager vector variants of map and filter (#2739)
  • unsigned-bit-shift-right: logical right shift that zero-fills the high bits (#2742)
  • bit-and-not: bitwise and with 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: like println but returns the string (#2748)
  • distinct?: true when no two arguments are = (#2749)
  • bounded-count: counts a collection, walking at most n elements of a non-counted? sequence (#2750)
  • splitv-at: eager vector variant of split-at (#2751)
  • map-invert: returns the map with keys and values swapped (#2753)
  • infinite?: alias for inf? (#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)
  • subseq and rsubseq: lazy ascending/descending range queries over sorted maps and sets, honoring their comparator (#2757)
  • random-sample: keeps each item with probability prob; lazy, with a transducer arity (#2759)
  • pr, prn, pr-str, prn-str: readable-print family (strings quoted and escaped); char literals like \A are one-char strings and print as "A" (#2743)
  • phel\trace namespace in the spirit of clojure.tools.trace: trace, trace-fn, deftrace, dotrace, reset-trace-state!; prints to stderr
  • The REPL registers phel.repl/print-tap on 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-all accepts Clojure's [n step coll] arity (#2758)
  • partition accepts Clojure's [n step coll] and [n step pad coll] arities; [n coll] unchanged (#2752)

Performance

  • Opt-in strip-symbol-meta build 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/or tests that lower to native PHP comparisons splice directly into the condition, dropping the truthiness adapter (#2766)
  • Multi-arity fns emit fixed-arity invokeArityN methods; build-mode call sites with a known arity skip the variadic __invoke dispatch (roughly 1.5-2x faster per call) (#2760)

🐛 Fixed

  • Build cache writes and deletes route through Gacela's FileCache primitives (writeContentsAtomically, delete), which lock, treat a short-byte write as failure, and invalidate opcache for the final path; a plain file_put_contents(...) === false missed truncated disk-full writes and a stale opcode entry could serve old content after a rewrite
  • phel run on an ad-hoc script fails immediately with the extractor's error when a required sibling .phel file cannot be read or parsed, instead of silently skipping it; namespaces that resolve elsewhere remain tolerated
  • #inst literals inside macro-expanded forms no longer crash compilation in the location-enrichment walk (#2778)
  • Multi-arity fn returned 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, or php/str_replace, whose string|false/string|array returns could emit "" where str renders "false"; return-type tables consolidated into PhpFunctionReturnTypes (#2768)
  • phel test --coverage fails fast with a hint when xdebug's coverage mode 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 $this shortcut; 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 NAN is never = to another set; previously this held only by accident of the default comparator and broke under a user comparator that orders NAN equal to itself (#2782)
  • phel list:modules and phel cache:warm no longer fatal in projects whose test suite holds a class that cannot be loaded standalone; the new app-module-paths config 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/+ 1 and 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 NAN under the default comparator: PHP's NAN <=> NAN is 1, so the binary search never relocated an existing NAN and every insert appended a fresh one. The default comparator now matches Java Double.compare / Clojure compare (NaN equals itself, orders after every number), so (count (sorted-set NAN NAN)) is 1 and (contains? (sorted-set NAN) NAN) is true (#2789)

👥 Contributors

@chemaclass @JesusValeraDev

Full Changelog: v0.48.0...v0.49.0

Downloads

v0.49.0


View release on GitHub