|
| 1 | ++++ |
| 2 | +title = "Pattern Matching: Writing Cleaner Code with Less Conditional Logic" |
| 3 | +aliases = [ "/blog/pattern-matching" ] |
| 4 | ++++ |
| 5 | + |
| 6 | +Remember those giant `if/elseif/else` ladders we write in PHP? They start off harmless and suddenly fill half a file. Phel ships with friendlier tools so you can keep the logic flat and readable. We will look at the two big helpers - [`case`](/documentation/control-flow/#case) and [`cond`](/documentation/control-flow/#cond) - and how they feel when you are new to Lisp syntax. |
| 7 | + |
| 8 | +## When plain `if` gets messy |
| 9 | + |
| 10 | +Here is the classic situation: a small payload comes from an API and you branch on it. In PHP you might write a long `if/elseif`. In Phel it can look like this: |
| 11 | + |
| 12 | +```phel |
| 13 | +(defn classify [event] |
| 14 | + (if (= (:type event) :created) |
| 15 | + "Fresh!" |
| 16 | + (if (= (:type event) :updated) |
| 17 | + (if (:urgent? event) |
| 18 | + "Update (urgent)" |
| 19 | + "Update (normal)") |
| 20 | + (if (= (:type event) :deleted) |
| 21 | + "Gone." |
| 22 | + "Unknown...")))) |
| 23 | +``` |
| 24 | + |
| 25 | +It works, but the intent hides in the nesting. Pattern matching lets us tell the same story with fewer twists. |
| 26 | + |
| 27 | +## `case`: think `switch`, but without fall-through |
| 28 | + |
| 29 | +When you compare one value against known constants, reach for [`case`](/documentation/control-flow/#case). It feels like PHP's `switch`, minus the accidental fall-through. |
| 30 | + |
| 31 | +```phel |
| 32 | +(defn classify [event] |
| 33 | + (case (:type event) |
| 34 | + :created "Fresh!" |
| 35 | + :updated (if (:urgent? event) |
| 36 | + "Update (urgent)" |
| 37 | + "Update (normal)") |
| 38 | + :deleted "Gone." |
| 39 | + (str "Unknown: " (:type event)))) |
| 40 | +``` |
| 41 | + |
| 42 | +Every branch lives on a single level, and the final expression works as a default. No more scrolling to match up closing parentheses. |
| 43 | + |
| 44 | +## `cond`: guard clauses without the ladder |
| 45 | + |
| 46 | +Sometimes you check different conditions in order: heavy parcel, express flag, cancel flag, and so on. [`cond`](/documentation/control-flow/#cond) does exactly that. Give it pairs of condition and result; it returns the first match. |
| 47 | + |
| 48 | +```phel |
| 49 | +(defn shipping-label [order] |
| 50 | + (cond |
| 51 | + (:cancelled? order) "Skip shipping, order cancelled." |
| 52 | + (> (:weight order) 30) "Send with freight carrier." |
| 53 | + (:express? order) "Upgrade to express." |
| 54 | + :else "Regular parcel.")) |
| 55 | +``` |
| 56 | + |
| 57 | +Think of it as stacked `elseif` blocks with no braces to juggle. Drop in `:else` for the fallback and you are done. |
| 58 | + |
| 59 | +## Bonus: friendly destructuring |
| 60 | + |
| 61 | +Pattern matching gets even nicer when you unpack data while you branch. Vectors are like PHP arrays with numeric keys, and maps are like associative arrays. You can pull values out right where you need them. |
| 62 | + |
| 63 | +```phel |
| 64 | +(defn handle-message [[kind payload]] |
| 65 | + (case kind |
| 66 | + :email (let [{:keys [to subject]} payload] |
| 67 | + (str "Email to " to " about " subject)) |
| 68 | + :sms (let [{:keys [to text]} payload] |
| 69 | + (str "SMS to " to ": " text)) |
| 70 | + :push (let [{:keys [device title]} payload] |
| 71 | + (str "Push notification for " device " -> " title)) |
| 72 | + (str "Unknown message: " kind))) |
| 73 | +``` |
| 74 | + |
| 75 | +`[kind payload]` pulls the first two items out of the vector, and `{:keys [...]}` plucks values from the map by name. No manual `get` calls, no index juggling. |
| 76 | + |
| 77 | +## When to pick what |
| 78 | + |
| 79 | +- **Use `case`** when you would normally reach for `switch` in PHP. One value, many constants. |
| 80 | +- **Use `cond`** when each branch has its own test, especially guard clauses. |
| 81 | +- **Stick with `if`** for single true/false checks or very hot loops. |
| 82 | +- **Layer in destructuring** whenever naming parts of the data makes each branch easier to read. |
| 83 | + |
| 84 | +Once you start matching patterns instead of stacking `if`s, your Phel code reads like a short list of rules. Try it on the next feature that touches conditional logic - you will not miss the ladder. |
0 commit comments