ruby 46 lines · 8 steps

Deep-freezing config with recursive immutability

A recursive freeze-and-copy pair backs an immutable Config whose updates return new instances.

Explained by highlit
1module DeepImmutable
2 module_function
3 
4 def deep_freeze(obj)
5 case obj
6 when Hash
7 obj.each_value { |v| deep_freeze(v) }
8 when Array
9 obj.each { |v| deep_freeze(v) }
10 end
11 obj.freeze
12 end
13 
14 def deep_dup(obj)
15 case obj
16 when Hash
17 obj.each_with_object({}) { |(k, v), acc| acc[k] = deep_dup(v) }
18 when Array
19 obj.map { |v| deep_dup(v) }
20 when Numeric, Symbol, NilClass, TrueClass, FalseClass
21 obj
22 else
23 obj.dup
24 end
25 end
26end
27 
28class Config
29 def initialize(settings)
30 @settings = DeepImmutable.deep_freeze(DeepImmutable.deep_dup(settings))
31 end
32 
33 def [](key)
34 @settings[key]
35 end
36 
37 def with(overrides)
38 merged = DeepImmutable.deep_dup(@settings)
39 overrides.each { |k, v| merged[k] = v }
40 Config.new(merged)
41 end
42 
43 def to_h
44 DeepImmutable.deep_dup(@settings)
45 end
46end
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Recursion is the natural fit for making nested structures fully immutable or fully copied.
  2. 2Freezing a duplicated input protects internal state from outside mutation of the original object.
  3. 3Returning a new instance from update methods keeps a value object genuinely immutable.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Deep-freezing config with recursive immutability — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code