the disaster of mutable state
DESCRIPTION
Explores the disastrous problems that mutable state and side effects entail, and discusses how to overcome them.TRANSCRIPT
The disaster of mutable state
What’s the big deal
1. Referential transparency is super important2. Effects and mutability do immense damage3. How to write a pure core with a thin effectful
crust
Referential transparency
• For the given inputs, we will always get the same outputs
• Like a mathematical function• You can substitute an expression with its
results
Benefits
• Easier to reason about• Easier to test• Easier to compose & recombine• More modular• Safe caching• Share data with impunity• Thread-safe
Drawbacks?
• Requires some discipline to maintain• If you break RT in one place, it ruins
everything• Updating data requires copying data
structures, which might be expensive (or not)
What’s off the menu?
• Network/DB/File system I/O• Mutating state• Reading/observing state that can be mutated• Creating a mutable object• Reference equality• Time.now()• random()• Exceptions
Equational reasoning
• Our starting point is no more complex than the final result, and can be replaced without changing the meaning
• Order doesn’t matter
2 + (25 * 3) – 7= 2 + 75 – 7= 77 – 7= 70
Pure exampledef add(a,b) a + bend
def mult(a,b) a * bend
# Interchangeable# Evaluation order doesn’t matteradd(4, mult(3,2))add(4, 6)10
Impure examplemutavar = 0
def mult(num) mutavar *= numend
def add(num) mutavar += numend
# Cannot change order# Cannot substitute actions for resultsadd(5)mult(2)add(-2)
Composition pure style!
• Give input, check output• output = foo(bar(baz(input)))
Composition pure style!
• Give input, check output• output = foo(bar(baz(input)))
Composition pure style!
• Give input, check output• output = foo(bar(baz(input)))
Composition pure style!
• Put something inside another thing• output = foo(bar(baz(input)))
Testing pure style!
• Give input, check output• output == foo(bar(baz(input)))
Give it one of these …and we get this?
Composition mutable style
Composition mutable style
Composition mutable style
Composition mutable style
Composition mutable style
Composition mutable style
Composition mutable style
Composition mutable style
Composition mutable style
Composition mutable style
Composition mutable style
Composition mutable style
Composition mutable style
Composition mutable style
Composition mutable style
• We need to fit every level of detail in our head! We can’t ignore anything.
• Any level could fiddle with something that changes the whole• Non-modular• Not really “composition” in a true sense
Testing mutable style! AKA “web of lies”
MOCK – HANDLE WITH CARE
MOCK – HANDLE WITH CARE
If I say “Gerbil”, then you say “Party time!”
And then you call my thing with a baked potato!
The 3rd time I knock, then you put on a Darth Vader mask and breath heavily
Let’s totally alter the fabric of space-time.
Modularity (pure)
What does this do?Elitist academic Ivory Tower genius
Modularity (pure)
What does this do?Elitist academic Ivory Tower genius
Modularity (with side-effects)
What does this do? Pragmatic real world coder who gets shit done
Modularity (with side-effects)
What does this do? Pragmatic real world coder who gets shit done
Modularity (with side-effects)
What does this do? Pragmatic real world coder who gets shit done
Modularity (with side-effects)
What does this do? Pragmatic real world coder who gets shit done
Modularity (with side-effects)
What does this do? Pragmatic real world coder who gets shit done
Expected order1) This2) That3) Depends4) ???5) Touches here6) Widdles the widget7) Diddles the
doobilacky
Gets a bit hairy here
Best we don’t modify this
“I’m not smart enough to use mutable state!”
- Functional programmers
State, time & identity
“But the real world is mutable!”
State, time & identity
samuel_l_jackson.hairstylesamuel_l_jackson.occupation
State, time & identity
samuel_l_jackson.hairstylesamuel_l_jackson.occupation
=> “bald”=> “actor”
State, time & identity
samuel_l_jackson.hairstylesamuel_l_jackson.occupation
=> “bald”=> “actor”... in 2014
State, time & identity
samuel_l_jackson.hairstylesamuel_l_jackson.occupation
=> “afro”=> “student”
1960s:
State, time & identity
“Samuel L Jackson”
1960 1970 1980 1990 2000 2010
H: “bald”O: “actor”
H: “afro”O: “student”
Identity
State, time & identity
• Mutable state implies identity• This totally changes the nature of the object• Identities refer to different states over time• Each state is eternal and immutable
Arguably OK identity
class ToyRobot attr_accessor :facing, :posend
Arguably OK identity
• ToyRobot will hold many facing/position states over time
• Robot’s identity is at least a meaningful concept
Totally wrong and broken identity
class Position attr_accessor :x, :yend
Totally wrong and broken identity
• A position can change under your feet!• What can it possibly mean??• What use is an identity that strings together
different Position states over time?
Consider…
class Integer attr_accessor :value def plus(n); value += n.value; endend
three = Integer.new 3three.value = 4three.plus(three) 8
Better: separate the identity
class ToyRobot attr_reader :facing, :posend
current_robots = { :r2d2 => ToyRobot.new ... :c3p0 => ... :dexter => ... :rdolivaw => ...}
Emphasis on transitions
• No: “Take this robot and change it”• Yes: “Specify the transformation from one
robot to another”
But this map is still mutable!
• At least we have shunted the mutability out a layer!
current_robots = { :r2d2 => ...}
current_robots.each { |id, bot| current_robots[:id] = bot.update()}
One more level: immutable hashes
• https://github.com/hamstergem/hamster
current_robots = Hamster.hash( :r2d2 => ToyRobot.new ... ...)
current_robots.fold(Hamster.hash) { |next_robots, id, bot| next_robots.put(id, bot.update)}
One more level: immutable world
class World attr_reader :current_robots
def update # return updated world end end
def whole_program(current_world) whole_program(current_world.update)end
Separating decisions from actions
• Often side-effecting code mixes decisions with actions
def send_welcome_email email, pwd if valid(email, pwd) content = create_content(email) send_email(email, content) else log(“[info] Couldn’t send email”) endend
Separating decisions from actions
1) Validity checking (pure)
def send_welcome_email email, pwd if valid(email, pwd) content = create_content(email) send_email(email, content) else log(“[info] Couldn’t send email”) endend
Separating decisions from actions
2) Generate email content (pure)
def send_welcome_email email, pwd if valid(email, pwd) content = create_content(email) send_email(email, content) else log(“[info] Couldn’t send email”) endend
Separating decisions from actions
3) Decide to send (pure), and act on it (I/O)
def send_welcome_email email, pwd if valid(email, pwd) content = create_content(email) send_email(email, content) else log(“[info] Couldn’t send email”) endend
Separating decisions from actions
4) Decide to log the failure (pure), and do it (I/O)
def send_welcome_email email, pwd if valid(email, pwd) content = create_content(email) send_email(email, content) else log(“[info] Couldn’t send email”) endend
Explicit decision
class Decision; end
class SendEmail < Decision attr_reader :email, :contentend
class Log < Decision attr_reader :messageend
class DoNothing < Decision; end
Pure version
def send_welcome_email email, pwd if valid(email, pwd) content = create_content(email) SendEmail.new(email, content) else Log.new( “[info] Couldn’t send email”) endend
Party at the end of the worlddef run_pure # Return decisions [ ... ]end
def interpret_decision d case d when Log puts d.message when SendEmail actually_send d.email, d.content when DoNothing endend
Big problems; huge wins
1. Mutable state causes immense harm to our software
2. Referential transparency and purity bring corresponding benefits
3. It’s not hard to start reaping the benefits