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. MISTAKES AT THE HEART OF
    RUBY
    Tom Stuart
    Monday, April 8, 13
    ...want to ask a question

    View full-size slide

  2. What makes software hard to
    work with?
    Monday, April 8, 13

    View full-size slide

  3. MUTABLE SHARED STATE
    Monday, April 8, 13

    View full-size slide

  4. MUTABLE SHARED STATE
    Used by more than one component
    Monday, April 8, 13

    View full-size slide

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

    View full-size slide

  6. MUTABLE SHARED STATE
    Used by more than one component
    Can change in value
    Using components depend on value
    Monday, April 8, 13

    View full-size slide

  7. MUTABLE SHARED STATE
    Global variables
    Assigned variables
    Procedural programming
    Monday, April 8, 13

    View full-size slide

  8. 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?

    View full-size slide

  9. 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

    View full-size slide

  10. 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

    View full-size slide

  11. FUNCTIONAL PROGRAMMING
    Monday, April 8, 13
    solves ALL the problems
    ok, so it’s an approach which claims to answer this problem. How?

    View full-size slide

  12. 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

    View full-size slide

  13. 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

    View full-size slide

  14. OBJECT-ORIENTED PROGRAMMING
    Monday, April 8, 13

    View full-size slide

  15. 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

    View full-size slide

  16. 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:

    View full-size slide

  17. WHAT IS RUBY?
    Monday, April 8, 13
    Vote - object/functional/other

    View full-size slide

  18. 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

    View full-size slide

  19. 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

    View full-size slide

  20. 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!

    View full-size slide

  21. 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

    View full-size slide

  22. 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

    View full-size slide

  23. 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?

    View full-size slide

  24. 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

    View full-size slide

  25. 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

    View full-size slide

  26. 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

    View full-size slide

  27. 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

    View full-size slide

  28. 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)

    View full-size slide

  29. 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

    View full-size slide

  30. 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

    View full-size slide

  31. 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

    View full-size slide

  32. 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

    View full-size slide

  33. 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?

    View full-size slide

  34. ARE OUR PROBLEMS REALLY JUST CULTURAL?
    So, there’s this cool web
    framework...
    Monday, April 8, 13

    View full-size slide

  35. 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)

    View full-size slide

  36. ARE OUR PROBLEMS REALLY JUST CULTURAL?
    How do Smalltalkers approach the web?
    Monday, April 8, 13

    View full-size slide

  37. 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

    View full-size slide

  38. 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?

    View full-size slide

  39. SOLVING THE PROBLEM OF MUTABLE SHARED STATE
    OK, what now?
    Monday, April 8, 13

    View full-size slide

  40. 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

    View full-size slide

  41. SOLVING THE PROBLEM OF MUTABLE SHARED STATE
    But this is the same as dynamic typing
    We chose a powerful language
    Monday, April 8, 13

    View full-size slide

  42. 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

    View full-size slide

  43. THANKS - QUESTIONS?
    http://www.tomstuart.co.uk
    Monday, April 8, 13

    View full-size slide