A quick reference for Phel syntax and core functions. Press / to filter sections.
Basic Syntax#
# This is a comment
; This is also a comment
#| This is a
multiline comment |#
nil # null value
true false # booleans (only false and nil are falsy)
42 -3 1.5 3.14e2 # numbers
0xFF 0b1010 0o17 # hex, binary, octal
"hello" "line\nbreak" # strings
:keyword :status # keywords (interned constants)
my-var my-module/fn # symbols
See Basic Types, Truth and Boolean Operations.
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)
(set 1 2 3) # same thing
'(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}
(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
(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
See Global and Local Bindings.
Functions#
(fn [x] (* x 2)) # anonymous function
|(* $ 2) # short form (single param)
|(+ $1 $2) # short form (multiple params)
|(sum $&) # 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
(memoize expensive-fn) # => cached version of fn
(memoize-lru expensive-fn 100) # => cached with max 100 entries
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
See Functions and Recursion, Control Flow.
Collections#
(map inc [1 2 3]) # => (2 3 4)
(filter even? [1 2 3 4]) # => (2 4)
(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
(into #{} [1 2 1 3]) # => #{1 2 3}
(distinct [1 2 1 3 2]) # => (1 2 3)
(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.
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))
(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)
(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
Lazy sequences were added in v0.25.0. map, filter, take, drop, concat, mapcat, interleave, and partition all return lazy sequences.
Threading Macros#
(-> {:name "Alice" :age 30} # thread-first
(assoc :role "admin")
(dissoc :age))
(->> [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)) # => 4Strings#
(str "Hello" " " "World") # => "Hello World"
(str "n=" 42) # => "n=42"
(format "Hi %s, age %d" "Jo" 25) # => "Hi Jo, age 25"
(php/strtolower "HELLO") # => "hello"
(php/strtoupper "hello") # => "HELLO"
(php/str_replace "o" "0" "foo") # => "f00"
(php/substr "hello" 1 3) # => "ell"
(php/explode "," "a,b,c") # => PHP array ["a" "b" "c"]Mutable State#
(def counter (var 0)) # create a mutable variable
(deref counter) # => 0
(set! counter 42) # direct reset
(deref counter) # => 42
(swap! counter inc) # apply function, counter is now 43
(swap! counter + 10) # counter is now 53
# Vars are the only mutable primitive in Phel
# (no atoms, agents, or refs)
See Global and Local Bindings.
Error Handling#
(try
(/ 1 0)
(catch \DivisionByZeroError e
(str "Error: " (php/-> e (getMessage)))))
(try
(do-risky-thing)
(catch \Exception e
(println (str "Failed: " (php/-> e (getMessage)))))
(finally
(cleanup)))
(throw (php/new \InvalidArgumentException "bad input"))
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.
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
(php/new \DateTime "now") # new DateTime("now")
# 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()
# Static methods & properties
(php/:: MyClass CONST) # MyClass::CONST
(php/:: MyClass (create "x")) # MyClass::create("x")
# 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
(php/new DateTimeImmutable) # use imported class
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 (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
See Testing.