The disaster of mutable state

1,941 views

Published on

Explores the disastrous problems that mutable state and side effects entail, and discusses how to overcome them.

Published in: Software, Technology
0 Comments
10 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,941
On SlideShare
0
From Embeds
0
Number of Embeds
47
Actions
Shares
0
Downloads
41
Comments
0
Likes
10
Embeds 0
No embeds

No notes for slide

The disaster of mutable state

  1. 1. The disaster of mutable state
  2. 2. What’s the big deal 1. Referential transparency is super important 2. Effects and mutability do immense damage 3. How to write a pure core with a thin effectful crust
  3. 3. 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
  4. 4. Benefits • Easier to reason about • Easier to test • Easier to compose & recombine • More modular • Safe caching • Share data with impunity • Thread-safe
  5. 5. 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)
  6. 6. 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
  7. 7. 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
  8. 8. Pure example def add(a,b) a + b end def mult(a,b) a * b end # Interchangeable # Evaluation order doesn’t matter add(4, mult(3,2)) add(4, 6) 10
  9. 9. Impure example mutavar = 0 def mult(num) mutavar *= num end def add(num) mutavar += num end # Cannot change order # Cannot substitute actions for results add(5) mult(2) add(-2)
  10. 10. Composition pure style! • Give input, check output • output = foo(bar(baz(input)))
  11. 11. Composition pure style! • Give input, check output • output = foo(bar(baz(input)))
  12. 12. Composition pure style! • Give input, check output • output = foo(bar(baz(input)))
  13. 13. Composition pure style! • Put something inside another thing • output = foo(bar(baz(input)))
  14. 14. Testing pure style! • Give input, check output • output == foo(bar(baz(input))) Give it one of these …and we get this?
  15. 15. Composition mutable style
  16. 16. Composition mutable style
  17. 17. Composition mutable style
  18. 18. Composition mutable style
  19. 19. Composition mutable style
  20. 20. Composition mutable style
  21. 21. Composition mutable style
  22. 22. Composition mutable style
  23. 23. Composition mutable style
  24. 24. Composition mutable style
  25. 25. Composition mutable style
  26. 26. Composition mutable style
  27. 27. Composition mutable style
  28. 28. Composition mutable style
  29. 29. 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
  30. 30. 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.
  31. 31. Modularity (pure) What does this do? Elitist academic Ivory Tower genius
  32. 32. Modularity (pure) What does this do? Elitist academic Ivory Tower genius
  33. 33. Modularity (with side-effects) What does this do? Pragmatic real world coder who gets shit done
  34. 34. Modularity (with side-effects) What does this do? Pragmatic real world coder who gets shit done
  35. 35. Modularity (with side-effects) What does this do? Pragmatic real world coder who gets shit done
  36. 36. Modularity (with side-effects) What does this do? Pragmatic real world coder who gets shit done
  37. 37. Modularity (with side-effects) What does this do? Pragmatic real world coder who gets shit done Expected order 1) This 2) That 3) Depends 4) ??? 5) Touches here 6) Widdles the widget 7) Diddles the doobilacky Gets a bit hairy here Best we don’t modify this
  38. 38. “I’m not smart enough to use mutable state!” - Functional programmers
  39. 39. State, time & identity “But the real world is mutable!”
  40. 40. State, time & identity samuel_l_jackson.hairstyle samuel_l_jackson.occupation
  41. 41. State, time & identity samuel_l_jackson.hairstyle samuel_l_jackson.occupation => “bald” => “actor”
  42. 42. State, time & identity samuel_l_jackson.hairstyle samuel_l_jackson.occupation => “bald” => “actor” ... in 2014
  43. 43. State, time & identity samuel_l_jackson.hairstyle samuel_l_jackson.occupatio n => “afro” => “student” 1960s:
  44. 44. State, time & identity “Samuel L Jackson” 1960 1970 1980 1990 2000 2010 H: “bald” O: “actor” H: “afro” O: “student” Identity
  45. 45. 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
  46. 46. Arguably OK identity class ToyRobot attr_accessor :facing, :pos end
  47. 47. Arguably OK identity • ToyRobot will hold many facing/position states over time • Robot’s identity is at least a meaningful concept
  48. 48. Totally wrong and broken identity class Position attr_accessor :x, :y end
  49. 49. 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?
  50. 50. Consider… class Integer attr_accessor :value def plus(n); value += n.value; end end three = Integer.new 3 three.value = 4 three.plus(three) 8
  51. 51. Better: separate the identity class ToyRobot attr_reader :facing, :pos end current_robots = { :r2d2 => ToyRobot.new ... :c3p0 => ... :dexter => ... :rdolivaw => ... }
  52. 52. Emphasis on transitions • No: “Take this robot and change it” • Yes: “Specify the transformation from one robot to another”
  53. 53. 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() }
  54. 54. 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) }
  55. 55. 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
  56. 56. 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”) end end
  57. 57. 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”) end end
  58. 58. 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”) end end
  59. 59. 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”) end end
  60. 60. 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”) end end
  61. 61. Explicit decision class Decision; end class SendEmail < Decision attr_reader :email, :content end class Log < Decision attr_reader :message end class DoNothing < Decision; end
  62. 62. 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”) end end
  63. 63. Party at the end of the world def 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 end end
  64. 64. 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

×