Quick reference for Phel syntax and core functions.
AI agents: load Agentic Coding first for the truncation-safe rules and PHP-interop gotchas. This sheet is the wide surface; that one is the must-know.
Basic syntax#
;; This is a standalone comment
; inline comment (after an expression)
nil ; null value
true false ; booleans (only false and nil are falsy)
42 -3 1.5 3.14e2 ; numbers
0xFF 0b1010 017 ; hex, binary, octal
"hello" "line\nbreak" ; strings
:keyword :status ; keywords (interned constants)
my-var my-module/fn ; symbols
#"[a-z]+" ; regex literal (PCRE pattern)
Note:
#line and#| |#multiline comments are deprecated. Use;;for standalone comments and;for inline comments.
See Basic Types.
Reader syntax#
@my-var ; shorthand for (deref my-var)
#"pattern" ; regex literal (PCRE)
#(+ %1 %2) ; anonymous function shorthand
#(inc %) ; single-arg: % is the same as %1
#(apply + %&) ; variadic: %& captures rest args
#?(:phel expr1 :default expr2) ; reader conditional
#?@(:phel [a b] :default [c]) ; splicing reader conditional
;; Tagged literals
#inst "2026-01-15T12:00:00Z" ; => DateTimeImmutable
#uuid "550e8400-e29b-41d4-a716-446655440000" ; => UUID string
#regex "\\d+" ; => PCRE pattern string
;; First-class var handles
#'my-fn ; shorthand for (var my-fn)
(var my-fn) ; returns the Var object for my-fn
#(...) is the preferred shorthand. % or %1 first arg, %2 second, %& rest. Legacy |(...) with $ is deprecated.
Reader conditionals (#?(), #?@()) target platforms in .cljc via :phel and :default.
Tagged literals: #inst reads as DateTimeImmutable, #uuid as a UUID string, #regex as a PCRE pattern. Register custom tags with register-tag from phel.reader.
Data structures#
[1 2 3] ; vector (indexed)
(vector 1 2 3) ; same thing
{:a 1 :b 2} ; map (key-value pairs)
(hash-map :a 1 :b 2) ; same thing
#{1 2 3} ; set (unique values)
(hash-set 1 2 3) ; set from arguments
(set [1 2 3]) ; coerce collection to set
'(1 2 3) ; quoted list (data, not a call)
(list 1 2 3) ; same thing
See Data Structures.
Accessing data#
(get [1 2 3] 0) ; => 1
(get {:a 1} :a) ; => 1
(get {:a 1} :b "default") ; => "default"
(get-in {:a {:b 1}} [:a :b]) ; => 1
(first [1 2 3]) ; => 1
(second [1 2 3]) ; => 2
(peek [1 2 3]) ; => 3
(:name {:name "Alice"}) ; => "Alice" (keyword as function)
({:a 1 :b 2} :a) ; => 1 (map as function)
([10 20 30] 1) ; => 20 (vector as function)Modifying data#
(conj [1 2] 3) ; => [1 2 3]
(conj #{1 2} 3) ; => #{1 2 3}
(conj {:a 1} [:b 2]) ; => {:a 1, :b 2}
(assoc {:a 1} :b 2) ; => {:a 1, :b 2}
(assoc [1 2 3] 0 9) ; => [9 2 3]
(dissoc {:a 1 :b 2} :a) ; => {:b 2}
(update {:a 1} :a inc) ; => {:a 2}
(update-keys {:a 1 :b 2} name) ; => {"a" 1, "b" 2}
(update-vals {:a 1 :b 2} inc) ; => {:a 2, :b 3}
(assoc-in {} [:a :b] 1) ; => {:a {:b 1}}
(update-in {:a {:b 1}} [:a :b] inc) ; => {:a {:b 2}}
(merge {:a 1} {:b 2 :a 3}) ; => {:a 3, :b 2}
See Data Structures.
Destructuring#
;; Sequential destructuring
(let [[a b c] [1 2 3]]
(+ a b c)) ; => 6
(let [[a b & rest] [1 2 3 4 5]]
rest) ; => [3 4 5]
;; Associative destructuring
(let [{:name name :age age} {:name "Alice" :age 30}]
(str name " is " age)) ; => "Alice is 30"
;; Default values
(let [{:name name :role role :or {role "guest"}}
{:name "Bob"}]
role) ; => "guest"
;; Works in defn, fn, loop too
(defn greet [{:name name}]
(str "Hello, " name))
(greet {:name "Alice"}) ; => "Hello, Alice"
See Destructuring.
Defining things#
(def pi 3.14159) ; global binding
(def secret :private 42) ; private binding
(defonce conn (connect!)) ; bind once; skipped if already defined (survives REPL reloads)
(defn greet [name] ; public function
(str "Hello, " name))
(defn- helper [x] ; private function
(* x 2))
(defstruct point [x y]) ; struct (typed map)
(point 1 2) ; => (point 1 2)
(point? (point 1 2)) ; => true
(let [x 1 ; local bindings
y (+ x 2)]
(+ x y)) ; => 4
(defmulti area :shape) ; multimethod (dispatch on :shape)
(defmethod area :circle [{:radius r}]
(* 3.14 r r))
See Global and Local Bindings.
Functions#
(fn [x] (* x 2)) ; anonymous function
#(* % 2) ; short form (single param)
#(+ %1 %2) ; short form (multiple params)
#(apply + %&) ; short form (variadic)
(defn greet ; multi-arity
([] "Hi")
([name] (str "Hi " name)))
(defn sum [& nums] ; variadic
(apply + nums))
(apply + [1 2 3]) ; => 6
(partial + 10) ; => fn that adds 10
(comp inc inc) ; => fn that increments twice
(identity 42) ; => 42
(some-fn pos? even?) ; => fn: true if any predicate passes
(every-pred pos? even?) ; => fn: true only if every predicate passes
(memoize expensive-fn) ; => cached version of fn
(memoize-lru expensive-fn 100) ; => cached with max 100 entries
(defn ^:memoize fib [n] ...) ; defn metadata: auto-wraps in memoize
(defn ^{:memoize-lru 128} f [k] ...)
(defn ^:async fetch [url] ...) ; wraps body in (async ...) -> Amp\Future
(defn ^int add [^int a ^int b] ; :tag metadata -> PHP type decls
(+ a b))
Control flow#
(if (> x 0) "pos" "non-pos") ; if/else
(when (> x 0) (print "pos")) ; when (no else branch)
(cond
(< n 0) "negative"
(= n 0) "zero"
:else "positive")
(case status
200 "OK"
404 "Not Found")
(do (print "a") (print "b") 42) ; evaluate multiple exprs, return last
See Control Flow.
Loops & recursion#
(loop [acc 0 n 10] ; loop with recur
(if (= n 0)
acc
(recur (+ acc n) (dec n)))) ; => 55
(foreach [v [1 2 3]] ; side-effects only, returns nil
(print v))
(for [x :in [1 2 3]] (* x 2)) ; => [2 4 6] (list comprehension)
(for [x :range [0 5]] x) ; => [0 1 2 3 4]
(for [x :in [1 2 3 4]
:when (even? x)] x) ; => [2 4]
(dotimes [i 3] (print i)) ; prints 0, 1, 2
(def n (atom 0))
(while (< @n 3) (swap! n inc)) ; side-effects while test is truthy
@n ; => 3
See Functions and Recursion, Control Flow.
Collections#
(def users [{:role :admin} {:role :user} {:role :admin}])
(map inc [1 2 3]) ; => @[2 3 4]
(filter even? [1 2 3 4]) ; => @[2 4]
(mapv inc [1 2 3]) ; => [2 3 4] (eager, returns a vector)
(filterv even? [1 2 3 4]) ; => [2 4] (eager, returns a vector)
(reduce + 0 [1 2 3]) ; => 6
(sort [3 1 2]) ; => [1 2 3]
(sort-by :age [{:age 30} {:age 20}]) ; sort by key
(group-by :role users) ; map of role -> [users]
(frequencies [:a :b :a :a]) ; => {:a 3, :b 1}
(count [1 2 3]) ; => 3
(empty? []) ; => true
(contains? {:a 1} :a) ; => true
(some even? [1 3 4]) ; => true
(every? pos? [1 2 3]) ; => true
(bounded-count 3 [1 2 3 4 5]) ; => 5 (walks at most 3 of a non-counted? seq)
(into #{} [1 2 1 3]) ; => #{1 2 3}
(vec '(1 2 3)) ; => [1 2 3] (coerce to vector)
(subset? #{1 2} #{1 2 3}) ; => true
(superset? #{1 2 3} #{1 2}) ; => true
(distinct [1 2 1 3 2]) ; => @[1 2 3]
(distinct? 1 2 3) ; => true (no two arguments are =)
(splitv-at 2 [1 2 3 4 5]) ; => [[1 2] [3 4 5]] (eager split)
(map-invert {:a 1 :b 2}) ; => {1 :a, 2 :b} (swap keys and values)
(flatten [[1 2] [3 [4]]]) ; => @[1 2 3 4]
(reverse [1 2 3]) ; => [3 2 1]
(concat [1 2] [3 4]) ; => @[1 2 3 4]
(compact [1 nil 2 nil 3]) ; => @[1 2 3]
(remove neg? [1 -2 3 -4]) ; => @[1 3]
See Data Structures.
Sorted collections & set relations#
(def sm (sorted-map 1 :a 3 :b 5 :c))
(subseq sm >= 3) ; => @[[3 :b] [5 :c]] (ascending range query)
(rsubseq sm <= 3) ; => @[[3 :b] [1 :a]] (descending)
;; Relational helpers over sets of maps, in the spirit of clojure.set
(def rel #{{:id 1 :role :admin} {:id 2 :role :user}})
(select #(= (:role %) :admin) rel) ; => #{{:id 1 :role :admin}}
(project rel [:role]) ; => #{{:role :admin} {:role :user}}
(rename rel {:role :kind}) ; => rows with :role renamed to :kind
(index rel [:role]) ; => map of {:role X} -> set of matching rows
subseq and rsubseq are lazy and honor the collection's comparator, so they only walk the matching range.
Walking data structures#
Requires (:require phel.walk :refer [postwalk prewalk postwalk-replace keywordize-keys stringify-keys]).
(postwalk f nested) ; transform bottom-up
(prewalk f nested) ; transform top-down
(postwalk-replace {:a :x} [:a :b]) ; => [:x :b]
(keywordize-keys {"name" "Alice"}) ; => {:name "Alice"}
(stringify-keys {:name "Alice"}) ; => {"name" "Alice"}
See Data Structures.
Lazy sequences#
(take 5 (range)) ; => @[0 1 2 3 4]
(take 5 (iterate inc 0)) ; => @[0 1 2 3 4]
(take 7 (cycle [1 2 3])) ; => @[1 2 3 1 2 3 1]
(take 4 (repeat :x)) ; => @[:x :x :x :x]
(take 5 (repeatedly #(php/rand 1 100))) ; 5 random numbers
(drop 3 (range 10)) ; => @[3 4 5 6 7 8 9]
(take-while pos? [3 2 1 0 -1]) ; => @[3 2 1]
(drop-while pos? [3 2 1 0 -1]) ; => @[0 -1]
(partition 2 [1 2 3 4 5 6]) ; => @[[1 2] [3 4] [5 6]]
(partition 2 1 [1 2 3 4]) ; => @[[1 2] [2 3] [3 4]] (sliding window)
(partition-all 2 [1 2 3]) ; => @[[1 2] [3]] (keeps the short tail)
(random-sample 0.5 (range 100)) ; keeps each item with probability 0.5
(interleave [:a :b :c] [1 2 3]) ; => @[:a 1 :b 2 :c 3]
;; Lazy filtering + transformation
(->> (range)
(filter even?)
(take 5)) ; => @[0 2 4 6 8]
;; Custom lazy sequence
(defn fibs []
(lazy-seq (cons 0 (cons 1
(map + (fibs) (rest (fibs)))))))
(doall (take 8 (fibs))) ; => [0 1 1 2 3 5 8 13]
(dorun (map println [1 2 3])) ; => nil (realize for side effects only)
(realized? (lazy-seq [1 2 3])) ; => false
Lazy file I/O:
(line-seq (php/fopen "file.txt" "r")) ; lazy line-by-line reading
(file-seq "src/") ; lazy recursive directory listing
(csv-seq (php/fopen "data.csv" "r")) ; lazy CSV parsing
(read-file-lazy "big.txt" 4096) ; lazy chunked reading
map, filter, take, drop, concat, mapcat, interleave, partition return lazy sequences.
Threading macros#
(-> {:name "Alice" :age 30} ; thread-first
(assoc :role "admin")
(dissoc :age)) ; => {:name "Alice", :role "admin"}
(->> [1 2 3 4 5] ; thread-last
(filter odd?)
(map inc)) ; => @[2 4 6]
(as-> [1 2 3] v ; thread with named binding
(conj v 4)
(count v)) ; => 4
(cond-> 1 ; conditional thread-first
true inc
false (* 42)) ; => 2
(cond->> [1 2 3] ; conditional thread-last
true (map inc)
false (filter odd?)) ; => @[2 3 4]Strings#
(str "Hello" " " "World") ; => "Hello World"
(str "n=" 42) ; => "n=42"
(format "Hi %s, age %d" "Jo" 25) ; => "Hi Jo, age 25"
Requires (:require phel.string :as str):
(ns my-app.strings
(:require phel.string :as str))
(str/lower-case "HELLO") ; => "hello"
(str/upper-case "hello") ; => "HELLO"
(str/replace "foo" "o" "0") ; => "f00"
(str/subs "hello" 1 3) ; => "el"
(str/split "a,b,c" #",") ; => ["a" "b" "c"] (Phel vector)
(str/join ", " ["a" "b" "c"]) ; => "a, b, c"
(str/starts-with? "hello" "he") ; => true
(str/ends-with? "hello" "lo") ; => true
(str/trim " hi ") ; => "hi"
(str/capitalize "hello world") ; => "Hello world"
(str/reverse "hello") ; => "olleh"Regular expressions#
;; Regex literals use #"..." syntax (PCRE patterns)
(re-find #"\d+" "abc123def") ; => "123"
(re-find #"(\w+)@(\w+)" "user@host")
; => ["user@host" "user" "host"]
(re-matches #"\d+" "123") ; => "123"
(re-matches #"\d+" "abc123") ; => nil (must match entire string)
;; re-seq: all matches as a vector
(re-seq #"\d+" "a1b2c3") ; => ["1" "2" "3"]
;; Use regex for validation
(defn valid-email? [s]
(some? (re-matches #".+@.+\..+" s)))
(valid-email? "alice@example.com") ; => true
(valid-email? "not-an-email") ; => falseMutable state#
(def counter (atom 0)) ; create an atom (mutable container)
(deref counter) ; => 0
@counter ; => 0 (shorthand for deref)
(reset! counter 42) ; direct reset
@counter ; => 42
(swap! counter inc) ; apply function, counter is now 43
(swap! counter + 10) ; counter is now 53
(compare-and-set! counter 53 100) ; => true (set only if current value matches)
(swap-vals! counter inc) ; => [100 101] (returns [old new])
(reset-vals! counter 0) ; => [101 0] (returns [old new])
;; Watchers: react to state changes
(add-watch counter :logger
(fn [key ref old-val new-val]
(println (str "Changed from " old-val " to " new-val))))
(remove-watch counter :logger)
;; Validators: constrain allowed values
(set-validator! counter #(>= % 0)) ; only non-negative values
(get-validator counter) ; => the validator fn
See Global and Local Bindings.
Error handling#
(try
(/ 1 0)
(catch DivisionByZeroError e
(str "Error: " (.getMessage e))))
(try
(do-risky-thing)
(catch Exception e
(println (str "Failed: " (.getMessage e))))
(finally
(cleanup)))
(throw (InvalidArgumentException. "bad input"))
;; Structured exceptions with ex-info
(throw (ex-info "User not found" {:id 42 :type :not-found}))
(try
(throw (ex-info "Validation failed" {:field :email} nil))
(catch Exception e
(ex-message e) ; => "Validation failed"
(ex-data e) ; => {:field :email}
(ex-cause e))) ; => nil
See PHP Interop.
Interfaces & structs#
(definterface Greetable
(greet [this]))
(definterface HasArea
(area [this]))
(defstruct circle [radius]
HasArea
(area [this] (* 3.14159 radius radius)))
(defstruct person [name age]
Greetable
(greet [this] (str "Hello, I'm " name)))
(greet (person "Alice" 30)) ; => "Hello, I'm Alice"
(area (circle 5)) ; => 78.53975
(person? (person "Alice" 30)) ; => true
See Interfaces.
Protocols#
Polymorphic dispatch on the first argument's type. More flexible than interfaces, extendable to existing types.
;; Define a protocol
(defprotocol Stringable
(to-string [this]))
(defstruct dog [name breed])
(extend-type dog
Stringable
(to-string [this] (str (:name this) " the " (:breed this))))
(to-string (dog "Rex" "Labrador")) ; => "Rex the Labrador"
;; Extend multiple types at once with extend-protocol
(extend-protocol Stringable
:string (to-string [this] this)
:int (to-string [this] (str this)))
;; Check protocol support
(satisfies? Stringable (dog "Rex" "Labrador")) ; => true
(extends? Stringable :string) ; => true
(extends? Stringable :array) ; => falseHierarchy system#
Ad-hoc hierarchies for multimethods and isa?.
(derive :shape/square :shape/poly)
(derive :shape/circle :shape/poly)
(derive :shape/filled-square :shape/square)
(isa? :shape/square :shape/poly) ; => true
(isa? :shape/filled-square :shape/poly) ; => true
(parents :shape/square) ; => #{:shape/poly}
(ancestors :shape/filled-square) ; => #{:shape/square :shape/poly}
(descendants :shape/poly) ; => #{:shape/square :shape/circle :shape/filled-square}
(make-hierarchy) ; => {:parents {}, :descendants {}, :ancestors {}}Transducers#
Composable transformations independent of the data source. Avoid intermediate collections.
;; Basic transducer usage with transduce
(transduce (map inc) + 0 [1 2 3]) ; => 9
(transduce (filter even?) + 0 [1 2 3 4]) ; => 6
;; Compose transducers (left-to-right order)
(def xf (comp (filter even?) (map inc)))
(transduce xf conj [] [1 2 3 4 5 6]) ; => [3 5 7]
;; into with a transducer (3-arg form)
(into [] (map inc) [1 2 3]) ; => [2 3 4]
(into #{} (filter odd?) [1 2 3 2 1]) ; => #{1 3}
;; sequence: lazy transducer application
(sequence (map inc) [1 2 3]) ; => [2 3 4]
;; cat: concatenating transducer for nested collections
(into [] cat [[1 2] [3 4] [5]]) ; => [1 2 3 4 5]
;; completing: supply a final step to a reducing function
(transduce (map inc) (completing + str) 0 [1 2 3]) ; => 9
;; Many core fns have transducer arities (called with no collection):
;; (map f), (filter pred), (take n), (drop n), (partition-all n), etc.PHP interop#
;; Calling PHP functions
(php/strlen "test") ; => 4
(php/date "Y-m-d") ; => "2026-02-07"
(php/array_merge arr1 arr2) ; call any PHP function
;; Instantiation - all three forms are equivalent
(php/new DateTime "now")
(new DateTime "now")
(DateTime. "now") ; ClassName. shorthand (preferred)
;; Instance methods & properties
(php/-> obj (method arg)) ; $obj->method($arg)
(php/-> obj property) ; $obj->property
(php/-> obj (a) (b) (c)) ; chained: $obj->a()->b()->c()
(.method obj arg) ; shorthand
(.-property obj) ; property shorthand
;; Static methods & properties
(php/:: MyClass CONST) ; MyClass::CONST
(php/:: MyClass (create "x")) ; MyClass::create("x")
(MyClass/create "x") ; static shorthand
Ns.MyClass/CONST ; static member shorthand
;; PHP arrays
(php/aget arr 0) ; $arr[0] ?? null
(php/aset arr "k" "v") ; $arr["k"] = "v"
(php/apush arr "v") ; $arr[] = "v"
See PHP Interop.
Namespaces#
(ns my-app.handlers
(:require my-app.db) ; import Phel module
(:require my-app.utils :as u) ; with alias
(:require my-app.auth :refer [login logout]) ; import symbols
(:use DateTimeImmutable) ; import PHP class
(:use Some.Long.Name :as Short)) ; PHP class with alias
(db/query "SELECT 1") ; use module prefix
(u/format-date date) ; use alias
(login credentials) ; use referred symbol
(DateTimeImmutable.) ; use imported class (ClassName. shorthand)
See Namespaces.
Testing#
(ns my-app.tests
(:require phel.test :refer [deftest is are]))
(deftest addition-test
(is (= 4 (+ 2 2)))
(is (= 4 (+ 2 2)) "optional description"))
(deftest multiple-assertions
(are [expected input] (= expected (inc input))
2 1
3 2
4 3))
(deftest exception-test
(is (thrown? Exception
(throw (php/new Exception "boom")))))./vendor/bin/phel test # run all tests
./vendor/bin/phel test tests/main.phel # run specific file
./vendor/bin/phel test --filter my-test # filter by name
./vendor/bin/phel test --fail-fast # stop on first failure
See Testing.
Async & concurrency#
async, await, await-all, await-any, ->closure are in phel.core (AMPHP-backed fibers).
;; Run body in a new fiber, returns an Amp\Future
(def f (async (+ 1 2)))
(await f) ; => 3 (blocks until resolved)
;; Await multiple futures concurrently
(await-all [(async 1) (async 2)]) ; => [1 2]
(await-any [(async 1) (async 2)]) ; => 1 (first to resolve)
;; Convert Phel fn to PHP Closure (for AMPHP and other libraries)
(->closure (fn [x] (* x 2)))
;; pmap: parallel map via fibers
(pmap inc [1 2 3]) ; => [2 3 4]Delay & force#
delay, delay?, and force are in phel.core (auto-imported, no require needed). phel.async/delay is a different function that suspends a fiber for N seconds.
;; Delay defers evaluation until first access
(def d (delay (do (println "computing...") 42)))
(delay? d) ; => true
(force d) ; prints "computing...", => 42
(force d) ; => 42 (cached, no recomputation)Iteration#
;; iteration: produce a lazy sequence from a step function
;; Useful for paginated APIs or stateful producers
(defn fetch-page [token]
{:items [1 2 3] :next-token (when (nil? token) "page2")})
(iteration fetch-page
{:kf :next-token
:vf :items
:initk nil})Arithmetic#
(+ 1 2 3) ; => 6
(- 10 3) ; => 7
(* 2 3 4) ; => 24
(/ 10 2) ; => 5
(/ 10 3) ; => 10/3 (Ratio, exact)
(/ 10.0 3) ; => 3.333... (float)
(float (/ 10 3)) ; => 3.333... (coerce Ratio to float)
(quot 10 3) ; => 3 (integer quotient)
(rem 10 3) ; => 1 (remainder)
(mod -10 3) ; => 2 (modulo, always non-negative)
(** 2 10) ; => 1024
(bit-and 2r1100 2r1010) ; => 8
(bit-and-not 2r1111 2r0101) ; => 10 (and with the complement)
(bit-shift-right -8 1) ; => -4 (arithmetic, sign-preserving)
(unsigned-bit-shift-right -1 60) ; => 15 (logical, zero-fills)
Integer division (/) returns a Ratio when not evenly divisible. Use float or (/ 10.0 3) if you need a float.
Utility functions#
(parse-long "42") ; => 42
(parse-double "3.14") ; => 3.14
(parse-boolean "true") ; => true
(abs -5) ; => 5
(inf? php/INF) ; => true
(infinite? php/INF) ; => true (alias for inf?)
(nan? (php/log -1)) ; => true
(rational? 1/2) ; => true (integers, Ratio, BigDecimal)
(rational? 1.5) ; => false
(random-uuid) ; => "550e8400-e29b-..." (random UUID string)Printing#
print/println render values for humans; the pr family renders them so the reader can read them back (strings quoted and escaped).
(pr-str "hi") ; => "\"hi\""
(prn-str [1 "a"]) ; => "[1 \"a\"]\n" (pr-str plus newline)
(println-str 1 2) ; => "1 2\n"
(str "hi") ; => "hi" (no quoting)
pr and prn write the same output to stdout instead of returning it.
Serialization (EDN & Transit)#
(ns my-app.serialize
(:require phel.edn :as edn)
(:require phel.transit :as transit))
;; phel.edn: eval-free EDN read/write (data only, no code execution)
(edn/read-string "{:a 1 :b [2 3]}") ; => {:a 1, :b [2 3]}
(edn/write-string {:a 1 :b [2 3]}) ; => "{:a 1, :b [2 3]}"
(edn/read-string-all "1 2 3") ; => [1 2 3] (every top-level form)
;; phel.transit: Transit + JSON-Verbose read/write
(transit/write-string {:a 1}) ; => "[\"~#cmap\",[\"~:a\",1]]"
(transit/read-string "[\"~:foo\",1]") ; => [:foo 1]Reflection#
(ns my-app.introspect
(:require phel.reflect :as reflect))
;; phel.reflect: introspect PHP classes via reflection
(reflect/class-info \DateTime) ; => map of name, methods, properties, ...
(reflect/methods \DateInterval) ; => vector of method-info maps
(reflect/properties \DateInterval) ; => vector of property-info maps
(reflect/supers \RuntimeException) ; => parent classes + interfacesREPL utilities#
(source my-fn) ; print source code of a function
(phel.repl/find-fn "map") ; search for functions by name
(symbol-info 'map) ; detailed info about a symbol
(ns-publics 'phel.core) ; all public vars in a namespace
(ns-aliases 'my-app.core) ; namespace aliases
(ns-refers 'my-app.core) ; referred symbols
(ns-list) ; list all loaded namespaces
(macroexpand-1 '(when true 1)) ; expand one level of macro
(macroexpand '(when true 1)) ; fully expand macro
(eval-str "(+ 1 2)") ; evaluate a string of Phel code
(load-file "src/my-module.phel") ; load and evaluate a file
(test-ns "my-app.tests") ; run tests in a namespace (name as string)Debugging#
(dbg (* w h)) ; print [file:line] form => value to stderr, return value
(dbg) ; "reached here" marker, returns nil
(inspect x) ; print a structural view of any value
(break) ; pause and open a sub-REPL over the local bindings
(add-tap println) ; attach an inspector
(tap> {:event :login}) ; send a value to every tap (printed in the REPL by default)
;; phel.trace: log every call, including recursive ones, to stderr
(ns my-app.core (:require phel.trace :refer [deftrace dotrace]))
(deftrace fact [n] (if (< n 2) 1 (* n (fact (dec n)))))
(dotrace [parse-row normalize] (process-file "in.csv"))
See Debugging.
Next steps#
- Getting Started: set up a project and a REPL.
- Language section: the full reference behind each form here.
- Agentic Coding: truncation-safe rules for AI pairing.