Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Mistakes at the Heart of Ruby

Mistakes at the Heart of Ruby

Slides from my talk at Ruby Manor 4. You should probably wait for the video. I'll link to it when it exists.

Tom Stuart

April 06, 2013
Tweet

More Decks by Tom Stuart

Other Decks in Programming

Transcript

  1. MUTABLE SHARED STATE Used by more than one component Can

    change in value Monday, April 8, 13
  2. MUTABLE SHARED STATE Used by more than one component Can

    change in value Using components depend on value Monday, April 8, 13
  3. PROCEDURAL PROGRAMMING $lives = 10 $progress = “” $word =

    some_random_word while lives_left && $progress != $word guess = take_guess unless guess_in_word(guess) $lives -= 1 end show_progress(guess) end Monday, April 8, 13 What are the problems?
  4. MUTABLE SHARED STATE Can’t introduce new versions of components without

    understanding the whole program Changes to one function can affect all others Can’t parallelize Yes, testability Monday, April 8, 13
  5. MUTABLE SHARED STATE how do we avoid you? Monday, April

    8, 13 Don’t say “two solutions” - joke is to flash up “Functional programming” directly
  6. FUNCTIONAL PROGRAMMING Monday, April 8, 13 solves ALL the problems

    ok, so it’s an approach which claims to answer this problem. How?
  7. FUNCTIONAL PROGRAMMING Eliminate mutation Return new state if there are

    changes Pass all needed state to functions Buzzword: idempotency Monday, April 8, 13 My perspective on it - talking about approach, claim no expertise
  8. FUNCTIONAL PROGRAMMING game_state = {lives: 10, secret: "foo", guessed: [],

    last_guess: "" }.freeze def lose_life_if_needed(state) return state if state[:secret].include?(state[:last_guess]) state.clone.merge!(lives: state[:lives] - 1) end #... while should_continue(game_state) game_state = show_progress( lose_life_if_needed( take_guess(game_state))) end Monday, April 8, 13 that’s one approach, we’ll look at it again later
  9. OBJECT-ORIENTED PROGRAMMING Eliminate sharing Objects are told what to do

    Objects don’t depend directly on others’ state Buzzword: encapsulation Monday, April 8, 13 i.e. objects don’t retrieve state of others and make decisions based on it
  10. OBJECT-ORIENTED PROGRAMMING class Hangman::Game #... def main_loop @progress = Progress.new(game:

    self, lives: 10, secret: "foo") while continuing @input.take_next_guess(@progress) end end end class Hangman::Progress def add_letter(l) unless @secret.include?(l) @lives -= 1 end #... @display.show(progress_mask) end end Monday, April 8, 13 take_next_guess calls add_letter only switching is based on local state ok, two approaches. Next question:
  11. IS RUBY FUNCTIONAL? Functions are (almost) first-class citizens Mutator methods

    on primitives have bangs Enumerable#map, #select, etc. Code blocks return last expression by default Lisp-inspired Monday, April 8, 13
  12. IS RUBY OO? Everything is an object Every method call

    has a receiver Even ‘primitives’ have methods Simple to make instance variables Smalltalk-inspired Monday, April 8, 13
  13. IS RUBY PROCEDURAL? Unbound procedures can have side effects Easy

    to make global variables require mutates global shared state Bash-inspired Monday, April 8, 13 “What does require do?” -unscoped import + runs code OK, so clearly it’s multi-paradigmatic. We all know this, tell us something new!
  14. TRYING TO WRITE OO CODE IN RUBY Methods return last

    expression This is a Lispish feature Smalltalkish: return self by default What happens when we combine them? Monday, April 8, 13
  15. TRYING TO WRITE OO CODE IN RUBY class Hangman::Game #...

    def main_loop @progress = Progress.new(lives: 10, secret: "foo") while @input.take_next_guess(@progress) end end end class Hangman::Progress def add_letter(l) unless @secret.include?(l) @lives -= 1 end #... @display.show(@progress) end end Monday, April 8, 13
  16. TRYING TO WRITE OO CODE IN RUBY class Hangman::Game #...

    def main_loop @progress = ::Progress.new(lives: 10, secret: "foo") while @input.take_next_guess(@progress) end end end class Hangman::Progress def add_letter(l) unless @secret.include?(l) @lives -= 1 end #... @display.show(@progress) end end Monday, April 8, 13 What does this return?
  17. TRYING TO WRITE OO CODE IN RUBY class Hangman::Display def

    show(progress) #massage puts progress end end class Hangman::WebDisplay def show(progress) #massage erb :hangman_display, :progress => progress end end Monday, April 8, 13 Some nefarious developer writes code expecting html retval
  18. THE PERILS OF BEING EXPRESSION-ORIENTED Can leak implementation details Makes

    it hard to swap in a different version Can lead to lazy tests Monday, April 8, 13 Leak impl details -> we start caring about state in other objects
  19. THE PERILS OF BEING EXPRESSION-ORIENTED Kernel#puts returns nil String#upcase! returns

    new value, or nil Kernel#require returns true or false Monday, April 8, 13
  20. THE PERILS OF BEING EXPRESSION-ORIENTED If we know that methods

    return, we ask: “What should I return?” instead of: “Should I return?” Monday, April 8, 13 And this leads us back toward functional programming, where we care about retvals, but without removing mutation. So we’re in the procedural morass
  21. TRYING TO WRITE FUNCTIONAL CODE IN RUBY Ruby strings are

    mutable Anything else can be mutable What happens when we combine with FP style? Monday, April 8, 13 (via monkey-patching and lack of protection against it in the lang)
  22. game_state = {lives: 10, secret: "foo", guessed: [], last_guess: ""

    } def lose_life_if_needed(state) return state if state[:secret].include?(state[:last_guess]) state.clone.merge!(lives: state[:lives] - 1) end #... while should_continue(game_state) game_state = show_progress( lose_life_if_needed( take_guess(game_state))) end TRYING TO WRITE FUNCTIONAL CODE IN RUBY Monday, April 8, 13 Everything is side-effect free
  23. def lose_life_if_needed(state) state[:secret].upcase! return state if state[:secret].include?(state[:last_guess]) state.clone.merge!(lives: state[:lives] -

    1) end #... while should_continue(game_state) game_state = show_progress( lose_life_if_needed( take_guess(game_state))) end TRYING TO WRITE FUNCTIONAL CODE IN RUBY Monday, April 8, 13 Until now - I hope you had integration tests
  24. THE PERILS OF MUTABILITY Can’t guarantee idempotency Can’t compose functions

    safely Can’t safely use functions in different contexts Monday, April 8, 13
  25. THE PERILS OF MUTABILITY If we know that everything is

    mutable, we ask: “What should I change?” instead of: “Should I change something?” Monday, April 8, 13
  26. All right, hold on there Tom. Monday, April 8, 13

    We accept that Ruby isn’t the purest language. That doesn’t automatically mean you can’t write good code. OK, but we seem to have problems - code not as good as we want it to be, arguments still raging about how to write good Ruby code. Maybe those problems aren’t the language’s fault. Maybe they’re cultural?
  27. ARE OUR PROBLEMS REALLY JUST CULTURAL? HTTP is inherently stateless

    We use application servers that run our code from scratch on each request And we apply OOP to this problem Monday, April 8, 13 (some version of OOP)
  28. THE WEB: A SMALLTALKER’S PERSPECTIVE Seaside rejects the statelessness of

    the web Session key sent with every request/response - used to pick up the session where it left off REST? Whassat? Requires a persistently running app server Monday, April 8, 13 And this seems to be a better way to write ‘telling’ code in a web context. Not sure how practical it is, but interesting to see the way a purer OO lang has attempted it
  29. ARE OUR PROBLEMS REALLY JUST CULTURAL? Is all of Rails

    just a category error? Monday, April 8, 13 Have we applied the wrong paradigm to the problem space?
  30. SOLVING THE PROBLEM OF MUTABLE SHARED STATE Either functional or

    OO Ruby makes it easy to defer the decision But you have to choose And you’ll lose some purity - inconvenient Monday, April 8, 13
  31. SOLVING THE PROBLEM OF MUTABLE SHARED STATE But this is

    the same as dynamic typing We chose a powerful language Monday, April 8, 13
  32. LET’S END WITH A PLATITUDE Monday, April 8, 13 Perhaps

    some problems lend themselves to functional solutions, others to OO solutions. It’s our responsibility to pick the right paradigm and the right tool for the job. And that’s the end. Advance to next during applause