Skip to content

Commit 8bc5e87

Browse files
Merge pull request #168 from phel-lang/blog/immutability
2 parents 9709ac8 + 63101db commit 8bc5e87

File tree

1 file changed

+69
-0
lines changed

1 file changed

+69
-0
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
+++
2+
title = "Immutability in Phel: Why Your Data Should Never Change"
3+
aliases = [ "/blog/immutability" ]
4+
+++
5+
6+
Phel lives and breathes persistent data structures. Instead of rewriting values in place, every operation hands you a fresh value that still shares most of the old data. It is the easiest way to keep code honest in a functional world.
7+
8+
If you're coming from PHP's mutable arrays and objects, that default can feel strange at first. Stick with it: the pay-off is code that is easier to reason about, simpler to test, and naturally safe when things happen in parallel.
9+
10+
## Goodbye in-place updates
11+
12+
[Vectors](/documentation/data-structures/#vectors), [maps](/documentation/data-structures/#maps), [sets](/documentation/data-structures/#sets), [lists](/documentation/data-structures/#lists), and [structs](/documentation/data-structures/#structs) in Phel never mutate. Helpers such as `push` and `put` hand you the updated collection while the original stays exactly the same.
13+
14+
```phel
15+
(def groceries [:milk :bread])
16+
(def extended (push groceries :apples))
17+
18+
groceries
19+
=> [:milk :bread]
20+
21+
extended
22+
=> [:milk :bread :apples]
23+
```
24+
25+
Because `groceries` never changes, any function that already received it can keep using it without worrying about sneaky side effects. Maps behave the same way:
26+
27+
```phel
28+
(def customer {:id 42 :name "Ada"})
29+
(let [with-email (put customer :email "[email protected]")]
30+
[customer with-email])
31+
=> [{:id 42 :name "Ada"}
32+
{:id 42 :name "Ada" :email "[email protected]"}]
33+
```
34+
35+
`put` returns a new map that shares everything it can with the original. The copy is cheap thanks to structural sharing, Phel only allocates the path that actually changed.
36+
37+
## Benefits of data that never changes
38+
39+
- **Predictable functions**: With immutable inputs, the only variable is the arguments themselves. This guarantees referential transparency and eliminates hidden, hard-to-trace bugs.
40+
- **Stress-free tests**: Testing pure functions is effortless, just pass in data, check the result, and forget about mocking or side effects.
41+
- **Simpler debugging**: Log it once, and it stays true, no sneaky mutations hiding under your traces.
42+
- **Effortless concurrency**: Pass the same data between async jobs without race conditions or surprises.
43+
44+
## Transforming data in steps
45+
46+
Immutability pairs nicely with Phel's pipeline-friendly tools. Each step receives a value, returns a fresh one, and the original remains available for whatever comes next.
47+
48+
```phel
49+
(def scores [10 18 21 7])
50+
51+
(->> scores
52+
(filter |(>= $ 15))
53+
(map |(- $ 10))
54+
(reduce + 0))
55+
=> 19
56+
57+
scores
58+
=> [10 18 21 7]
59+
```
60+
61+
The vector `scores` is still the same after the reduction, ready to reuse later. That makes it trivial to layer different views over the same base data.
62+
63+
## Managing change at the edges
64+
65+
Real programs still need to talk to the outside world; databases, APIs, the filesystem. Immutability doesn’t stop that; it just asks you to keep those side effects in their own little corner.
66+
67+
Do your updates at clear entry points, turn any external data into immutable Phel values, and let the rest of your code run safely on pure data. When you need a new version, use helpers like `put-in`, `unset`, or `push`, and pass the new value forward instead of mutating it.
68+
69+
Once you stop changing data in place, life gets simpler: there’s the value you got, and the value you return. **That's it**. Everything else becomes easier to trust, and that’s why, in Phel, your data never changes.

0 commit comments

Comments
 (0)