Slide 1

Slide 1 text

MISTAKES AT THE HEART OF RUBY Tom Stuart Monday, April 8, 13 ...want to ask a question

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

MUTABLE SHARED STATE Monday, April 8, 13

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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?

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

OBJECT-ORIENTED PROGRAMMING Monday, April 8, 13

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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:

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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!

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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?

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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)

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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?

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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)

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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?

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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