Release: 0.32 - Async Awakens
0.32.0 - Async Awakens
Released 2026-04-12 Β· GitHub release
Async/concurrency module (AMPHP) + Clojure-compat aliases & reader syntax + defrecord/reify/sorted collections + transients & character literals.
π Added
Async & Concurrency
phel\asyncmodule withasync,await,delay,await-all,await-any,->closurefor AMPHP-based concurrency (#793)pmapfor fiber-based parallel map (IO-parallel via AMPHP), matching Clojure'spmap(#793)amphp/amppromoted to a runtime dependency sophel\asyncworks out of the box, including from the PHAR (#793)futuremacro returning aPhelFuturecompatible withderef,realized?, and the@reader shortcut;await/await-all/await-anyaccept bothPhelFuturewrappers and rawAmp\Futurevalues (#1287)- 3-arg
(deref future timeout-ms timeout-val)for time-bounded blocking on aPhelFuture(#1313) future-cancel,future-cancelled?,future-done?forPhelFuturelifecycle management (#1313)
Reader & Compiler
- Character literals:
\a,\space,\newline,\tab,\uNNNN,\oNNN, and punctuation forms; compile to single-char PHP strings. FQN parsing is preserved via lookahead (#1283) - Var-quote reader syntax
#'foo, read as the bare symbolfoosince Phel has no first-class Var type (#1317) - Symbolic number literals
##Inf,##-Inf,##NaNfor.cljcinterop (#1276) - Generic
#<tag>tagged-literal dispatch (#cpp,#uuid,#inst, β¦) read asTaggedLiteralNode; unknown tags in unselected#?branches parse without error (#1277) fnaccepts an optional name symbol for self-recursion:(fn my-name [x] ...), including multi-arity forms (#1279, #1299)- Radix literals
NrXXX(bases 2β36), e.g.2r1111,16rFF(#1281) N/M-suffixed numeric literals (1N,1.5M); suffix is stripped since Phel has no BigInt/BigDecimal, accepted for.cljccompat (#1325)- Dot namespace separator and Clojure aliasing for FQNs, enabling
phel.core/fn,clojure.core/fn(#1251) - Accept
.as alternate namespace separator inns,in-ns,:require,:use(#1177) - Vector entries in
:require, e.g.(:require [phel\str :as s :refer [upper-case]])(#1183) - Automatic
clojure.*βphel.*namespace remapping in:requirewhen target exists (#1207, #1210) ~and~@reader macros forunquote/unquote-splicing, alongside existing,/,@(#1201)name#auto-gensym suffix inside syntax-quote, alongside existingname$(#1195)&formand&envimplicit symbols in everydefmacrobody, enabling dialect detection via(:ns &env)(#1185)
Core Language
defrecordanddeftypemacros expanding to adefstructplus->Namefactory (defrecordalso generatesmap->Name); optional protocol tail is spliced intoextend-type(#1324)reifyfor anonymous protocol/interface implementation (#1226)sorted-map,sorted-map-by,sorted-set,sorted-set-byfor sorted persistent collections (#1228)sorted?predicate for sorted-map/sorted-set (#1274)- 0-arg
(range)returns an infinite lazy sequence starting at 0 (#1259) letfnmacro for mutually recursive local functions (#1224)condpmacro for predicate-based dispatch, including:>>result threading (#1217)if-some,when-some,when-firstmacros for nil-aware binding (#1218)assertmacro for precondition checking with optional custom message (#1222)*assert*var (defaulttrue) read byassertat macroexpand time; when false,assertexpands tonil(#1315)- Nested
def/defninside anotherdef/defnbody is now permitted (#1316) dotimesmacro andrun!function for side-effecting iteration (#1252)fnilfor nil-safe function wrapping with default values (#1225)vary-metafor applying a function to an object's metadata (#1223)min-key,max-keyfor finding extremes by a derived value (#1221)rename-keysfor renaming map keys via a key map (#1220)seq?predicate forLazySeqInterface(#1231)(boolean x)coercion:falsefornil/false,trueotherwise (#1186)- 1-arg
(some? x)form alongside the existing 2-arg(some? pred coll)(#1184) (resolve sym)globally available inphel\core(no longer requiresphel\repl) (#1187):ordefaults and:strsstring-key support in map destructuring (#1219, #1227)disjvariadic function for removing keys from sets (#1285)floatanddoublecoercion functions returning a PHP float (#1282)object-arrayreturning a plain PHP indexed array accessible viaphp/aget/php/aset(#1318)array-map, aliased tohash-mapsince Phel has no distinct array-map type (#1319)to-arrayfor converting any collection to a plain PHP array (#1320)acloneproducing a shallow independent copy of a PHP array (#1321)bytecoercion truncating to a signed 8-bit range (-128..127) (#1327)charcoercion returning a single-character string from a code point or 1-char string (#1330)char?predicate (single-char string, UTF-8 counted), matching ClojureScript semantics (#1334)coll?predicate for persistent collections (vectors, lists, hash-maps, structs, sets, lazy-seqs) (#1336)conj!transient mutator with Clojure-compatible arities over transient vectors, sets, and maps (#1338)assoc!,dissoc!,disj!,pop!transient mutators, throwingInvalidArgumentExceptionon persistent or mismatched targets (#1353)some-fnhigher-order predicate combinator, short-circuiting on first logical-true result (#1339)counted?predicate for collections with constant-time length (lazy-seqs excluded) (#1340)- Single-arity
(drop-last coll)equivalent to(drop-last 1 coll)(#1343) NaN?as an alias fornan?(#1284)
Clojure-Compatible Aliases
atom,atom?,reset!as aliases forvar,var?,set!(#1252)persistent!as alias forpersistent(#1353)double?as alias forfloat?(#1353)identical?as alias forid(#1252)fn?as alias forfunction?(#1252)map?as alias forhash-map?(#1252)valsas alias forvalues(#1252)integer?as alias forint?(#1199)with-metamade public as replacement forset-meta!(#1252)
Testing
aremacro inphel\testfor template-based multiple assertions (#1255)testingmacro inphel\testfor grouping assertions with context strings (#1237)do-reportinphel\testfor custom assertion reporting (#1260)phel\test/assert-expris now an open multimethod, letting users extendiswith custom assertion forms (#1188)defmethodpreserves the namespace of the multimethod, enabling cross-namespace extension with fully qualified multi-names
REPL & Tooling
requireanduseaccept quoted symbols in the REPL (e.g.(require 'phel\str)) for nREPL compatibility (#1211)ProjectLayout::Rootfor single-file / scratch projects (srcDirs = ['.']), andPhelConfig::forProject(layout: β¦)named-argument style with auto-detected layout when called with zero arguments (#1355)phel init --minimalgenerates a root-layout project: singlemain.phel+ matchingmain_test.phel+ one-linephel-config.phpat the repo root (#1355)phel initnow generates a matching test file by default (opt out with--no-tests) and listsphel testin the next-steps output (#1355)
Documentation
- Clojure-to-Phel migration guide covering naming, interop, namespaces, and feature differences (#1229)
- README "Getting Started" section showing
composer require+phel initflow - Quickstart tutorial replaced hand-written
phel-config.phpwithphel init; kept the manual form as an appendix
βοΈ Changed
- Anonymous
fnemits native PHP closures instead ofAbstractFnsubclasses, making them compatible with libraries that type-hint\Closure(e.g. AMPHP) without->closureconversion (#793) QuoteNodepreserves the original reader-macro prefix (,vs~,',`,@) for faithful parser/printer round-trip (#1203)- Split
Phel\Lang\Generators\SequenceGeneratorinto focused sibling generators (TransformGenerator,SliceGenerator,CombineGenerator,DedupeGenerator) (#1197) - Migrate all Phel source and test files from
|(...)to#(...)short function syntax (#1179) - Migrate all in-repo Phel source, tests, and docs from
,/,@to~/~@forunquote/unquote-splicing(#1203) - Migrate
timemacro inphel\corefromname$toname#auto-gensym suffix (#1203) - Migrate internal
tests/phel/usage of#|...|#multiline comments to;;line comments (#1276)
β οΈ Deprecated
#|...|#multiline comments and bare#line comments (deprecated since v0.30, now announced for removal in v0.33); use;;and#_instead (#1276)varβatom,var?βatom?,set!βreset!(#1252)idβidentical?(#1252)function?βfn?,hash-map?βmap?(#1252)valuesβvals(#1252)set-meta!βwith-meta(#1252)|(...)short function syntax with$placeholders; use#(...)with%instead (#1179),/,@asunquote/unquote-splicingreader macros; use~/~@instead (#1203)name$auto-gensym suffix; usename#instead (#1203)
π Fixed
(meta x)on a local binding returns the value's metadata; only(meta 'sym)looks up definition metadata (#1352)(assoc nil k v)returns a fresh map, matching Clojure (#1352)assocon a non-associative value throwsInvalidArgumentExceptionnaming the received type (#1352)absthrowsInvalidArgumentExceptionon non-numeric input instead of silently returning0(#1352)drop-lastno longer errors onnil:(drop-last n nil)and(drop-last nil)return[], aligning withdrop/take(#1344)reset!,swap!,set!now return the newly-stored value instead ofnil, matching Clojure (#1304)associative?returnstruefor vectors and PHP indexed arrays, matching Clojure'sAssociativeprotocol (#1303)(vec map)returns entries as 2-element vectors (e.g.(vec {:a 1}) => [[:a 1]]) instead of just values (#1305)min-key/max-keyreturn the latest argument on ties, matching Clojure's reference implementation (#1306)- 1-arg
(thrown? body)defaults to catching any\Throwable, matching Clojure's portability convention (#1307) -0x8000000000000000(and-0b...,-0o...at 64-bit minimum) parses correctly toPHP_INT_MINinstead of crashing withParseError(#1278)aremacro no longer eagerly evaluates list literals in table cells; cells are substituted symbolically, matchingclojure.template/do-template(#1280)=on lazy sequences no longer realizes an infinite side:LazySeq/ChunkedSeq/AbstractPersistentVectorshort-circuit on identity and walk pairwise via lockstep iterators (#1286)(php/yield ...)in return position no longer emitsreturn yield ...;, which broke PHP generator semantics (#793)phel runno longer buffers output soprintln/printflush immediately, fixing silent output in long-running AMPHP servers (#793)- REPL
requiresupports dot namespace separator and Clojure aliasing, e.g.(require phel.str),(require clojure.str)(#1263) - REPL
(require 'foo)throwsRuntimeExceptionwhen the namespace cannot be found instead of silently succeeding (#1246) - PHP reserved keywords (e.g.
and,list,class) allowed in namespace names, matching PHP 8.0+ (#1230) macroexpand/macroexpand-1are now functions (were macros), so quoted forms expand correctly and unquoted forms evaluate eagerly (#1209)macroexpandno longer applies inline expansion to non-macro forms:(macroexpand '(+ 1 2))returns(+ 1 2)(#1208)- Lexer no longer swallows a reader conditional (
#?(...)) following a gensym-suffixed symbol (#1195) - Lexer accepts
'inside and at the end of symbol names (e.g.a',foo'',a'b); leading'is still the quote reader macro (#1275) php/...calls to namespaced PHP functions (e.g.php/Amp\File\write) emit fully qualified names so they resolve from compiled/cached files (#1180)phel.pharno longer emits duplicate-namespace warnings or fails to write the compiled-code cache when run from a directory withoutphel-config.php(#1354)
π₯ Contributors
@Chemaclass @JesusValeraDev @MattVanHorn @jasalt
Full Changelog: v0.31.0...v0.32.0
Downloads
v0.32.0
- phel.phar (1.14 MB)