If#
(if test then else?)
A control flow structure. First evaluates test. If test evaluates to true, only the then form is evaluated and the result is returned. If test evaluates to false only the else form is evaluated and the result is returned. If no else form is given, nil will be returned.
The test evaluates to false if its value is false or equal to nil. Every other value evaluates to true. In sense of PHP this means (test != null && test !== false).
# Basic if examples
(if true 10) # Evaluates to 10
(if false 10) # Evaluates to nil
(if true (print 1) (print 2)) # Prints 1 but not 2
# Important: Only false and nil are falsy!
(if 0 (print 1) (print 2)) # Prints 1 (0 is truthy!)
(if nil (print 1) (print 2)) # Prints 2 (nil is falsy)
(if [] (print 1) (print 2)) # Prints 1 (empty vector is truthy!)
# Practical examples
(defn greet [name]
(if name
(str "Hello, " name)
"Hello, stranger"))
(greet "Alice") # => "Hello, Alice"
(greet nil) # => "Hello, stranger"
# Using if for validation
(defn divide [a b]
(if (= b 0)
nil
(/ a b)))
(divide 10 2) # => 5
(divide 10 0) # => nilCase#
(case test & pairs)
Evaluates the test expression. Then iterates over each pair. If the result of the test expression matches the first value of the pair, the second expression of the pair is evaluated and returned. If no match is found, returns nil.
# Basic case examples
(case (+ 7 5)
3 :small
12 :big) # Evaluates to :big
(case (+ 7 5)
3 :small
15 :big) # Evaluates to nil (no match)
(case (+ 7 5)) # Evaluates to nil (no pairs)
# Practical examples
(defn http-status-message [code]
(case code
200 "OK"
201 "Created"
400 "Bad Request"
404 "Not Found"
500 "Internal Server Error"))
(http-status-message 200) # => "OK"
(http-status-message 404) # => "Not Found"
(http-status-message 999) # => nil
# Using case with keywords
(defn animal-sound [animal]
(case animal
:dog "Woof!"
:cat "Meow!"
:cow "Moo!"
:duck "Quack!"))
(animal-sound :dog) # => "Woof!"
(animal-sound :fish) # => nilPHP Coming from PHP? ›
case is similar to PHP's switch but more concise:
// PHP
switch ($value) {
case 3:
$result = 'small';
break;
case 12:
$result = 'big';
break;
default:
$result = null;
}
// Phel
(case value
3 :small
12 :big)
No break needed-Phel's case doesn't fall through.
Clojure Coming from Clojure? ›
case works exactly like Clojure's case-evaluates to the matching value without fall-through.
Cond#
(cond & pairs)
Iterates over each pair. If the first expression of the pair evaluates to logical true, the second expression of the pair is evaluated and returned. If no match is found, returns nil.
# Basic cond examples
(cond
(neg? 5) :negative
(pos? 5) :positive) # Evaluates to :positive
(cond
(neg? 5) :negative
(neg? 3) :negative) # Evaluates to nil (no match)
(cond) # Evaluates to nil (no pairs)
# Practical examples
(defn classify-number [n]
(cond
(< n 0) "negative"
(= n 0) "zero"
(> n 0) "positive"))
(classify-number -5) # => "negative"
(classify-number 0) # => "zero"
(classify-number 10) # => "positive"
# Using cond for complex conditions
(defn ticket-price [age]
(cond
(< age 3) 0 # Free for toddlers
(< age 12) 5 # Child price
(< age 65) 10 # Adult price
:else 7)) # Senior discount
(ticket-price 2) # => 0
(ticket-price 10) # => 5
(ticket-price 30) # => 10
(ticket-price 70) # => 7
# Combining multiple conditions
(defn water-state [temp]
(cond
(<= temp 0) :ice
(and (> temp 0) (< temp 100)) :liquid
(>= temp 100) :steam))
(water-state -5) # => :ice
(water-state 25) # => :liquid
(water-state 105) # => :steamPHP Coming from PHP? ›
cond is like a chain of if/elseif in PHP:
// PHP
if ($value < 0) {
$result = 'negative';
} elseif ($value > 0) {
$result = 'positive';
} else {
$result = null;
}
// Phel
(cond
(neg? value) :negative
(pos? value) :positive)
More elegant for multiple conditions than nested if expressions. Use :else as the last condition for a default case.
Clojure Coming from Clojure? ›
cond works exactly like Clojure's cond-evaluates predicates in order and returns first match.
Loop#
(loop [bindings*] expr*)
Creates a new lexical context with variables defined in bindings and defines a recursion point at the top of the loop.
(recur expr*)
Evaluates the expressions in order and rebinds them to the recursion point. A recursion point can be either a fn or a loop. The recur expressions must match the arity of the recursion point exactly.
Internally recur is implemented as a PHP while loop and therefore prevents the Maximum function nesting level errors.
# Basic loop example - sum numbers from 1 to 10
(loop [sum 0
cnt 10]
(if (= cnt 0)
sum
(recur (+ cnt sum) (dec cnt)))) # => 55
# Recursion in a function
(defn factorial [n]
(loop [acc 1
n n]
(if (<= n 1)
acc
(recur (* acc n) (dec n)))))
(factorial 5) # => 120
# Finding an element in a vector
(defn find-index [pred coll]
(loop [idx 0
items coll]
(cond
(empty? items) nil
(pred (first items)) idx
:else (recur (inc idx) (rest items)))))
(find-index even? [1 3 5 8 9]) # => 3
(find-index neg? [1 2 3]) # => nil
# Building a result with loop
(defn reverse-vec [v]
(loop [result []
remaining v]
(if (empty? remaining)
result
(recur (conj result (last remaining))
(pop remaining)))))
(reverse-vec [1 2 3 4]) # => [4 3 2 1]PHP Coming from PHP? ›
loop/recur provides tail-call optimization, which PHP doesn't support natively:
// PHP - recursive functions can cause stack overflow
function countdown($n) {
if ($n === 0) return 0;
return countdown($n - 1); // Stack overflow for large n!
}
// Phel - recur compiles to a while loop (safe for any n)
(loop [n 1000000]
(if (= n 0)
0
(recur (dec n)))) # No stack overflow!
This is critical for functional programming patterns in PHP.
Clojure Coming from Clojure? ›
loop/recur works exactly like Clojure-provides tail-call optimization by compiling to iterative loops.
Foreach#
(foreach [value valueExpr] expr*)
(foreach [key value valueExpr] expr*)
The foreach special form can be used to iterate over all kind of PHP datastructures for side-effects. The return value of foreach is always nil. The loop special form should be preferred of the foreach special form whenever possible.
(foreach [v [1 2 3]]
(print v)) # Prints 1, 2 and 3
(foreach [k v {"a" 1 "b" 2}]
(print k)
(print v)) # Prints "a", 1, "b" and 2PHP Coming from PHP? ›
foreach mirrors PHP's foreach loop syntax:
// PHP
foreach ([1, 2, 3] as $v) {
print($v);
}
foreach (["a" => 1, "b" => 2] as $k => $v) {
print($k);
print($v);
}
// Phel
(foreach [v [1 2 3]]
(print v))
(foreach [k v {"a" 1 "b" 2}]
(print k)
(print v))
Note: Prefer for or loop when you need to return values. foreach is only for side-effects.
For#
A more powerful loop functionality is provided by the for loop. The for loop is an elegant way to define and create arrays based on existing collections. It combines the functionality of foreach, let, if and reduce in one call.
(for head body+)
The head of the loop is a vector that contains a
sequence of bindings and modifiers. A binding is a sequence of three
values binding :verb expr. Where binding is a binding as
in let and :verb is one of the following keywords:
:rangeloop over a range, by using the range function.:inloops over all values of a collection.:keysloops over all keys/indexes of a collection.:pairsloops over all key value pairs of a collection.
After each loop binding additional modifiers can be applied. Modifiers
have the form :modifier argument. The following modifiers are supported:
:whilebreaks the loop if the expression is falsy.:letdefines additional bindings.:whenonly evaluates the loop body if the condition is true.:reduce [accumulator initial-value]Instead of returning a list, it reduces the values intoaccumulator. Initiallyaccumulatoris bound toinitial-value. Normally withwhenmacro insidereducefunction the accumulator becomesnilwhen the condition is not met. However withfor,:whencan be used for conditional logic with:reducewithout this issue.
(for [x :range [0 3]] x) # Evaluates to [0 1 2]
(for [x :range [3 0 -1]] x) # Evaluates to [3 2 1]
(for [x :in [1 2 3]] (inc x)) # Evaluates to [2 3 4]
(for [x :in {:a 1 :b 2 :c 3}] x) # Evaluates to [1 2 3]
(for [x :keys [1 2 3]] x) # Evaluates to [0 1 2]
(for [x :keys {:a 1 :b 2 :c 3}] x) # Evaluates to [:a :b :c]
(for [[k v] :pairs {:a 1 :b 2 :c 3}] [v k]) # Evaluates to [[1 :a] [2 :b] [3 :c]]
(for [[k v] :pairs [1 2 3]] [k v]) # Evaluates to [[0 1] [1 2] [2 3]]
(for [[k v] :pairs {:a 1 :b 2 :c 3} :reduce [m {}]]
(assoc m k (inc v))) # Evaluates to {:a 2 :b 3 :c 4}
(for [[k v] :pairs {:a 1 :b 2 :c 3} :reduce [m {}] :let [x (inc v)]]
(assoc m k x)) # Evaluates to {:a 2 :b 3 :c 4}
(for [[k v] :pairs {:a 1 :b 2 :c 3} :when (contains-value? [:a :c] k) :reduce [acc {}]]
(assoc acc k v)) # Evaluates to {:a 1 :c 3}
(for [x :in [2 2 2 3 3 4 5 6 6] :while (even? x)] x) # Evaluates to [2 2 2]
(for [x :in [2 2 2 3 3 4 5 6 6] :when (even? x)] x) # Evaluates to [2 2 2 4 6 6]
(for [x :in [1 2 3] :let [y (inc x)]] [x y]) # Evaluates to [[1 2] [2 3] [3 4]]
(for [x :range [0 4] y :range [0 x]] [x y]) # Evaluates to [[1 0] [2 0] [2 1] [3 0] [3 1] [3 2]]PHP Coming from PHP? ›
Phel's for is a powerful list comprehension, not like PHP's for loop:
// PHP - manual array building
$result = [];
foreach (range(1, 3) as $x) {
$result[] = $x + 1;
}
// Phel - declarative comprehension
(for [x :in [1 2 3]] (inc x)) # [2 3 4]
Phel's for combines iteration, filtering (:when), early termination (:while), reduction (:reduce), and nested loops in one elegant expression-much more powerful than PHP's for/foreach.
Clojure Coming from Clojure? ›
for works similarly to Clojure's for-list comprehensions with :let, :when, and nested bindings. The :reduce modifier is a Phel extension.
Do#
(do expr*)
Evaluates the expressions in order and returns the value of the last expression. If no expression is given, nil is returned.
(do 1 2 3 4) # Evaluates to 4
(do (print 1) (print 2) (print 3)) # Print 1, 2, and 3Dofor#
(dofor [x :in [1 2 3]] (print x)) # Prints 1, 2, 3 and returns nil
(dofor [x :in [2 3 4 5] :when (even? x)] (print x)) # Prints 1, 2 and returns nil
Iterating over collections for side-effects is also possible with dofor which has similar behavior to for otherwise but returns nil as foreach does.
Conditional Threading#
cond->#
(cond-> expr & clauses)
Takes an expression and a set of test/form pairs. Threads the expression through each form where the corresponding test is truthy (thread-first style). Forms where the test is falsy are skipped.
(cond-> 1
true inc
false (* 42)
true (* 3)) # => 6
# Only applies inc (true) and (* 3) (true), skips (* 42) (false)
# 1 -> (inc 1) -> 2 -> (* 2 3) -> 6
(defn maybe-transform [data opts]
(cond-> data
(:uppercase opts) (str/upper-case)
(:trim opts) (str/trim)
(:prefix opts) (str (:prefix opts))))cond->>#
(cond->> expr & clauses)
Like cond-> but threads as the last argument (thread-last style).
(cond->> [1 2 3 4 5]
true (map inc)
false (filter odd?)
true (take 3)) # => (2 3 4)
# Only applies (map inc) and (take 3), skips (filter odd?)Clojure Coming from Clojure? ›
cond-> and cond->> work exactly like their Clojure counterparts.
Exceptions#
(throw expr)
The expr is evaluated and thrown, therefore expr must return a value that implements PHP's Throwable interface.
Try, Catch and Finally#
(try expr* catch-clause* finally-clause?)
All expressions are evaluated and if no exception is thrown the value of the last expression is returned. If an exception occurs and a matching catch-clause is provided, its expression is evaluated and the value is returned. If no matching catch-clause can be found the exception is propagated out of the function. Before returning normally or abnormally the optionally finally-clause is evaluated.
(try) ; Evaluates to nil
(try
(throw (php/new \Exception))
(catch \Exception e "error")) ; Evaluates to "error"
(try
(+ 1 1)
(finally (print "test"))) ; Evaluates to 2 and prints "test"
(try
(throw (php/new \Exception))
(catch \Exception e "error")
(finally (print "test"))) ; Evaluates to "error" and prints "test"Structured Exceptions#
Phel provides functions for creating and inspecting structured exceptions that carry data maps, inspired by Clojure's ex-info system. This is useful when you need to attach context to errors beyond just a message string.
Creating structured exceptions with ex-info#
(ex-info message data)
(ex-info message data cause)
Creates an exception with a message string, an associated data map, and an optional cause (a previous exception). The data map can contain any information relevant to the error:
(throw (ex-info "User not found" {:user-id 42 :status 404}))
; With a cause (chaining exceptions)
(try
(do-something-risky)
(catch \Exception e
(throw (ex-info "Operation failed" {:step "processing"} e))))Inspecting structured exceptions#
Use ex-data, ex-message, and ex-cause to extract information from structured exceptions:
(def err (ex-info "Validation failed" {:field :email :reason "invalid format"}))
(ex-message err) ; => "Validation failed"
(ex-data err) ; => {:field :email :reason "invalid format"}
(ex-cause err) ; => nil (no cause provided)Practical example: error handling with data#
(defn find-user [id]
(let [user (db-lookup id)]
(if (nil? user)
(throw (ex-info "User not found" {:user-id id :status 404}))
user)))
(try
(find-user 42)
(catch \Exception e
(let [data (ex-data e)]
(case (:status data)
404 (println "Not found:" (ex-message e))
403 (println "Forbidden:" (ex-message e))))))Clojure Coming from Clojure? ›
ex-info, ex-data, ex-message, and ex-cause work exactly like their Clojure counterparts.