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

From Ruby to Scala and back again: Better living through type checking

From Ruby to Scala and back again: Better living through type checking

Presented at Ruby Manor 4.0 in April 2013. An intro to Scala and static type systems, for Rubyists.

Tim Cowlishaw

April 07, 2013
Tweet

Other Decks in Technology

Transcript

  1. From Ruby to Scala
    and back again:
    Better living through
    type-checking
    Tim Cowlishaw
    Ruby Manor 4.0

    View Slide

  2. Ruby, Scala and Me

    ~7 years working (mostly) with Ruby

    Increasingly using Scala whenever I can

    Why?
    – Ruby-like syntax and lack of ceremony
    – On the JVM
    – Best bits of Functional and OO paradigms
    – Powerful statically checked type system

    View Slide

  3. What I'll cover

    Introduction to Scala

    Types and Type systems
    How do we develop differently in Ruby and
    Scala?

    What are the pros and cons of each?

    What are the advantages and disadvantages of
    types and tests for proving correctness of our
    programs?

    How can types make us better at Ruby?

    View Slide

  4. Scala

    View Slide

  5. Ruby!
    def fibonacciSequence(n)
    (0..(n-2)).inject([0,1]) do |fibs, n|
    nxt = fibs[-1] + fibs[-2]
    fibs + [nxt]
    end
    end
    # fibonacciSequence(7)
    # # => [0,1,1,2,3,5,8,13]

    View Slide

  6. Scala
    def fibonacciSequence(n)
    (0..(n-2)).inject([0,1]) do |fibs, n|
    nxt = fibs[-1] + fibs[-2]
    fibs + [nxt]
    end
    end
    # fibonacciSequence(7)
    # # => [0,1,1,2,3,5,8,13]
    def fibonnaciSequence(n : Int) : List[Int] = {
    0.to(n-2).foldLeft(List(0,1)) { (fibs, n) =>
    val next : Int = fibs(fibs.length-1) +
    fibs(fibs.length-2)
    fibs ::: List(next)
    }
    }
    // fibonacciSequence(7)
    // => List[Int] = List(0,1,1,2,3,5,8,13)

    View Slide

  7. Scala
    def fibonacciSequence(n)
    (0..(n-2)).inject([0,1]) do |fibs, n|
    nxt = fibs[-1] + fibs[-2]
    fibs + [nxt]
    end
    end
    # fibonacciSequence(7)
    # # => [0,1,1,2,3,5,8,13]
    def fibonnaciSequence(n : Int) : List[Int] = {
    0.to(n-2).foldLeft(List(0,1)) { (fibs, n) =>
    val next : Int = fibs(fibs.length-1) +
    fibs(fibs.length-2)
    fibs ::: List(next)
    }
    }
    // fibonacciSequence(7)
    // => List[Int] = List(0,1,1,2,3,5,8,13)

    View Slide

  8. Scala
    def fibonacciSequence(n)
    (0..(n-2)).inject([0,1]) do |fibs, n|
    nxt = fibs[-1] + fibs[-2]
    fibs + [nxt]
    end
    end
    # fibonacciSequence(7)
    # # => [0,1,1,2,3,5,8,13]
    def fibonnaciSequence(n : Int) : List[Int] = {
    0.to(n-2).foldLeft(List(0,1)) { (fibs, n) =>
    val next : Int = fibs(fibs.length-1) +
    fibs(fibs.length-2)
    fibs ::: List(next)
    }
    }
    // fibonacciSequence(7)
    // => List[Int] = List(0,1,1,2,3,5,8,13)

    View Slide

  9. Scala
    def fibonacciSequence(n)
    (0..(n-2)).inject([0,1]) do |fibs, n|
    nxt = fibs[-1] + fibs[-2]
    fibs + [nxt]
    end
    end
    # fibonacciSequence(7)
    # # => [0,1,1,2,3,5,8,13]
    def fibonnaciSequence(n : Int) : List[Int] = {
    0.to(n-2).foldLeft(List(0,1)) { (fibs, n) =>
    val next : Int = fibs(fibs.length-1) +
    fibs(fibs.length-2)
    fibs ::: List(next)
    }
    }
    // fibonacciSequence(7)
    // => List[Int] = List(0,1,1,2,3,5,8,13)

    View Slide

  10. Scala
    def fibonacciSequence(n)
    (0..(n-2)).inject([0,1]) do |fibs, n|
    nxt = fibs[-1] + fibs[-2]
    fibs + [nxt]
    end
    end
    # fibonacciSequence(7)
    # # => [0,1,1,2,3,5,8,13]
    def fibonnaciSequence(n : Int) : List[Int] = {
    0.to(n-2).foldLeft(List(0,1)) { (fibs, n) =>
    val next : Int = fibs(fibs.length-1) +
    fibs(fibs.length-2)
    fibs ::: List(next)
    }
    }
    // fibonacciSequence(7)
    // => List[Int] = List(0,1,1,2,3,5,8,13)

    View Slide

  11. What is a type?

    Property of values:
    2 : Int, “Hello World” : String

    …variables… :
    x : Int, greeting : String

    …functions…:
    sumIsEven : (Int, Int) → Bool

    ...and expressions:
    sumIsEven(2,3) : Bool

    View Slide

  12. What is a type?

    A set of related values:
    Bool = {True, False}
    Int = {… -2, -1, 0, 1, 2 …}
    Char = {'a'..'z', 'A'..'Z' etc}
    List[X] = Empty or X + List[X]
    String = List[Char]

    View Slide

  13. What is a type?

    A set of related operations or
    behaviours:
    Bool - {!, &&, ||}
    Int - { +, -, *, div}
    String – {upcase, format} and:
    List – {join, map, filter, inject}

    View Slide

  14. A type-system bestiary

    Strong vs. weak

    Static vs. dynamic...
    – Static vs. dynamic checking
    – Static vs. dynamic dispatch

    View Slide

  15. A type-system bestiary

    Strong or weak?
    – Javascript:
    phantomjs>

    View Slide

  16. A type-system bestiary

    Strong or weak?
    – Javascript:
    phantomjs> 5 * “2”

    View Slide

  17. A type-system bestiary

    Strong or weak?
    – Javascript:
    phantomjs> 5 * “2”
    10
    phantomjs>

    View Slide

  18. A type-system bestiary

    Strong or weak?
    – Javascript:
    phantomjs> 5 * “2”
    10
    phantomjs> 5 + “2”

    View Slide

  19. A type-system bestiary

    Strong or weak?
    – Javascript:
    phantomjs> 5 * “2”
    10
    phantomjs> 5 + “2”
    52

    View Slide

  20. A type-system bestiary

    Strong or weak?
    – Javascript
    – Ruby:
    irb(main):001:0>

    View Slide

  21. A type-system bestiary

    Strong or weak?
    – Javascript
    – Ruby:
    irb(main):001:0> 5 * “2”

    View Slide

  22. A type system bestiary

    Strong or weak?
    – Javascript
    – Ruby:
    irb(main):001:0> 5 * “2”
    TypeError: String can't be coerced into Fixnum
    irb(main):002:0>

    View Slide

  23. A type-system bestiary

    Strong or weak?
    – Javascript
    – Ruby:
    irb(main):001:0> 5 * “2”
    TypeError: String can't be coerced into Fixnum
    irb(main):002:0> 5 + “2”

    View Slide

  24. A type-system bestiary

    Strong or weak?
    – Javascript
    – Ruby:
    irb(main):001:0> 5 * “2”
    TypeError: String can't be coerced into Fixnum
    irb(main):002:0> 5 + “2”
    TypeError: String can't be coerced into Fixnum

    View Slide

  25. A type-system bestiary

    Strong or weak?
    – Javascript
    – Ruby
    – Scala:
    scala>

    View Slide

  26. A type-system bestiary

    Strong or weak?
    – Javascript
    – Ruby
    – Scala:
    scala> 5 * “2”

    View Slide

  27. A type-system bestiary

    Strong or weak?
    – Javascript
    – Ruby
    – Scala:
    scala> 5 * “2”
    :8: error: overloaded method value * with alternatives:
    (x: Double)Double
    (x: Float)Float
    (x: Long)Long
    (x: Int)Int
    (x: Char)Int
    (x: Short)Int
    (x: Byte)Int
    cannot be applied to (java.lang.String)
    scala>

    View Slide

  28. A type-system bestiary

    Strong or weak?
    – Javascript
    – Ruby
    – Scala:
    scala> 5 * “2”
    :8: error: overloaded method value * with alternatives:
    (x: Double)Double
    (x: Float)Float
    (x: Long)Long
    (x: Int)Int
    (x: Char)Int
    (x: Short)Int
    (x: Byte)Int
    cannot be applied to (java.lang.String)
    scala> 5 + “2”

    View Slide

  29. A type-system bestiary

    Strong or weak?
    – Javascript
    – Ruby
    – Scala:
    scala> 5 * “2”
    :8: error: overloaded method value * with alternatives:
    (x: Double)Double
    (x: Float)Float
    (x: Long)Long
    (x: Int)Int
    (x: Char)Int
    (x: Short)Int
    (x: Byte)Int
    cannot be applied to (java.lang.String)
    scala> 5 + “2”
    res8: String = 52

    View Slide

  30. A type-system bestiary

    Strong or weak?
    – Presence of implicit coercion
    – A scale rather than a binary choice
    Javascript: Weak
    Ruby: Strong
    Scala: Strong(-ish)

    View Slide

  31. A type-system bestiary

    Static vs. dynamic type-checking
    – When do type-checks happen?

    Runtime (Dynamic)
    – Blows up in your user's face

    Compile time (Static)
    – Blows up in your own face
    – Much like your test suite

    View Slide

  32. A type-system bestiary

    Ruby: Dynamic type-checking
    def square(x)
    x * x
    end
    square("5")
    $ ruby square.rb
    TypeError: can't convert String into Integer
    * at org/jruby/RubyString.java:1135
    square at square.rb:2
    (root) at square.rb:4

    View Slide

  33. A type-system bestiary

    Scala: Static type-checking
    $ scalac test.scala
    test.scala:3: error: type mismatch;
    found : java.lang.String("5")
    required: Int
    square("5")
    ^
    one error found
    object Example extends App {
    def square(x : Int) : Int = x *
    x
    square("5")
    }

    View Slide

  34. A type-system bestiary

    Dispatch: when is the implementation of
    a method resolved?
    – Static: At compile type
    – Dynamic: At runtime

    “Dynamic” or “Late” binding

    Enables polymorphism

    View Slide

  35. A type-system bestiary

    Ruby: Dynamic dispatch (Obviously)

    View Slide

  36. A type-system bestiary

    Ruby: Dynamic dispatch
    module NumberAnnouncer
    def initialize(n)
    @n = n
    end
    def announcement
    raise NotImplementedError
    end
    end

    View Slide

  37. A type-system bestiary

    Ruby: Dynamic dispatch
    class EvenAnnouncer
    include NumberAnnouncer
    def announcement
    "#{@n} is even!"
    end
    end

    View Slide

  38. A type-system bestiary

    Ruby: Dynamic dispatch
    class OddAnnouncer
    include NumberAnnouncer
    def announcement
    "#{@n} is odd!"
    end
    end

    View Slide

  39. A type-system bestiary

    Ruby: Dynamic dispatch
    while line = gets
    n = line.to_i
    announcer = if n % 2 == 0
    EvenAnnouncer.new(n)
    else
    OddAnnouncer.new(n)
    end
    puts announcer.announcement
    end

    View Slide

  40. A type-system bestiary

    Ruby: Dynamic dispatch
    $ ruby number_wang.rb
    1
    1 is odd!
    2
    2 is even!

    View Slide

  41. A type-system bestiary

    Scala: Dynamic dispatch!

    View Slide

  42. A type-system bestiary

    Scala: Dynamic dispatch!
    trait NumberAnnouncer {
    def announcement : String
    }
    class EvenAnnouncer(n : Int) extends NumberAnnouncer {
    def announcement : String = n + " is even!"
    }
    class OddAnnouncer(n : Int) extends NumberAnnouncer {
    def announcement : String = n + " is odd!"
    }

    View Slide

  43. A type-system bestiary
    Scala: Dynamic dispatch!
    import io.Source
    object NumberWang extends App {
    val input = Source.fromInputStream(System.in).getLines
    for(line <- input) {
    val n = line.toInt
    val announcer : _ <: NumberAnnouncer = n % 2 == 0 match {
    case true => new EvenAnnouncer(n)
    case false => new OddAnnouncer(n)
    }
    println(announcer.announcement)
    }
    }

    View Slide

  44. A type-system bestiary
    Scala: Dynamic dispatch!
    import io.Source
    object NumberWang extends App {
    val input = Source.fromInputStream(System.in).getLines
    for(line <- input) {
    val n = line.toInt
    val announcer : _ <: NumberAnnouncer = n % 2 == 0 match {
    case true => new EvenAnnouncer(n)
    case false => new OddAnnouncer(n)
    }
    println(announcer.announcement)
    }
    }

    View Slide

  45. A type-system bestiary
    Scala: Dynamic dispatch!
    $ scalac number_wang.scala
    $ scala NumberWang
    1
    1 is odd!
    2
    2 is even!

    View Slide

  46. A type-system bestiary

    Ruby
    – Strong typing
    – Dynamic checking
    – Dynamic dispatch

    Scala
    – Strong typing
    – Static checking
    – Dynamic dispatch

    View Slide

  47. Why do we care?

    Static type checks
    – Ensure that some classes of errors can't
    happen at runtime.
    – Give us tests for free.

    View Slide

  48. Tests for free!
    object Example extends App {
    def square(x : Int) : Int = x * x
    square("5")
    }

    View Slide

  49. Tests for free!
    class Transaction < Struct(:amount); end
    class BankAccount
    def initialize(opening_balance)
    @opening_balance = opening_balance
    @transactions = []
    end
    def add_transaction(transaction)
    @transactions << transaction
    end
    def balance
    @transactions.inject(opening_balance) {|b,t| b + t.amount }
    end
    end

    View Slide

  50. Tests for free!
    class Transaction(val amount : Int)
    class Account(val opening_balance : Int) {
    val transactions = new MutableList
    def add_transaction(transaction : Transaction) : Unit = {
    transactions += transaction
    }
    def balance : Int = {
    transactions.foldLeft(opening_balance) {(b,t) => b + t.amount}
    }
    }

    View Slide

  51. Tests for free!
    class Transaction(val amount : Int)
    class Account(val opening_balance : Int) {
    val transactions = new MutableList
    def add_transaction(transaction : Transaction) : Unit = {
    transactions += transaction
    }
    def balance : Int = {
    transactions.foldLeft(opening_balance) {(b,t) => b + t.amount}
    }
    }

    View Slide

  52. Tests for free!
    class Transaction < Struct(:amount); end
    class BankAccount
    def initialize(opening_balance)
    @opening_balance = opening_balance
    @transactions = []
    end
    def add_transaction(transaction)
    @transactions << transaction
    end
    def balance
    @transactions.inject(opening_balance) {|b,t| b + t.amount }
    end
    end

    View Slide

  53. Tests for free!
    class Transaction < Struct(:amount, :currency); end
    class BankAccount
    def initialize(opening_balance)
    @opening_balance = opening_balance
    @transactions = []
    end
    def add_transaction(transaction)
    @transactions << transaction
    end
    def balance
    @transactions.inject(opening_balance) {|b,t| b + t.amount }
    end
    end

    View Slide

  54. Tests for free!
    class Transaction < Struct(:amount, :currency); end
    class BankAccount
    def initialize(opening_balance, currency)
    @opening_balance = opening_balance
    @currency = currency
    @transactions = []
    end
    def add_transaction(transaction)
    @transactions << transaction
    end
    def balance
    @transactions.inject(opening_balance) {|b,t| b + t.amount }
    end
    end

    View Slide

  55. Tests for free!
    class Transaction < Struct(:amount, :currency); end
    class BankAccount
    def initialize(opening_balance, currency)
    @opening_balance = opening_balance
    @currency = currency
    @transactions = []
    end
    def add_transaction(transaction)
    raise “Wrong currency!” unless transaction.currency = @currency
    @transactions << transaction
    end
    def balance
    @transactions.inject(opening_balance) {|b,t| b + t.amount }
    end
    end

    View Slide

  56. Tests for free!
    object Currencies {
    type GBP
    type USD
    }
    class Transaction[C](val amount : Int)
    class Account[C](val opening_balance : Int) {
    val transactions = new MutableList
    def add_transaction(transaction : Transaction[C]) : Unit = {
    transactions += transaction
    }
    def balance : Int = {
    transactions.foldLeft(opening_balance) {(b,t) => b + t.amount}
    }
    }
    val account = new Account[Currencies.USD]

    View Slide

  57. Tests for free!
    object Currencies {
    type GBP
    type USD
    }
    class Transaction[C](val amount : Int)
    class Account[C](val opening_balance : Int) {
    val transactions = new MutableList
    def add_transaction(transaction : Transaction[C]) : Unit = {
    transactions += transaction
    }
    def balance : Int = {
    transactions.foldLeft(opening_balance) {(b,t) => b + t.amount}
    }
    }
    val account = new Account[Currencies.USD]

    View Slide

  58. Tests for free!
    object Currencies {
    type GBP
    type USD
    }
    class Transaction[C](val amount : Int)
    class Account[C](val opening_balance : Int) {
    val transactions = new MutableList
    def add_transaction(transaction : Transaction[C]) : Unit = {
    transactions += transaction
    }
    def balance : Int = {
    transactions.foldLeft(opening_balance) {(b,t) => b + t.amount}
    }
    }
    val account = new Account[Currencies.USD]

    View Slide

  59. Tests for free!
    object Currencies {
    type GBP
    type USD
    }
    class Transaction[C](val amount : Int)
    class Account[C](val opening_balance : Int) {
    val transactions = new MutableList
    def add_transaction(transaction : Transaction[C]) : Unit = {
    transactions += transaction
    }
    def balance : Int = {
    transactions.foldLeft(opening_balance) {(b,t) => b + t.amount}
    }
    }
    val account = new Account[Currencies.USD]

    View Slide

  60. Tests for free – Generically

    The “Option” type: “type-safe nulls”







    View Slide

  61. Tests for free – Generically

    The “Option” type: “type-safe nulls”
    – scala> var x : Option[Int] = Some(6)
    – x : Option[Int] = Some(6)













    View Slide

  62. Tests for free – Generically

    The “Option” type: “type-safe nulls”
    – scala> var x : Option[Int] = Some(6)
    – x : Option[Int] = Some(6)
    – scala> x.map {(n) => n * 2 }
    – Some(12) : Option[Int]











    View Slide

  63. Tests for free – Generically

    The “Option” type: “type-safe nulls”
    – scala> var x : Option[Int] = Some(6)
    – x : Option[Int] = Some(6)
    – scala> x.map {(n) => n * 2 }
    – Some(12) : Option[Int]
    – scala> x.map {(n) => n * 2 }.getOrElse(14)
    – 12 : Int









    View Slide

  64. Tests for free – Generically

    The “Option” type: “type-safe nulls”
    – scala> var x : Option[Int] = Some(6)
    – x : Option[Int] = Some(6)
    – scala> x.map {(n) => n * 2 }
    – Some(12) : Option[Int]
    – scala> x.map {(n) => n * 2 }.getOrElse(14)
    – 12 : Int
    – scala> x.map {(n) => n * 2 }.getOrElse(14)
    – 12 : Int







    View Slide

  65. Tests for free – Generically

    The “Option” type: “type-safe nulls”
    – scala> var x : Option[Int] = Some(6)
    – x : Option[Int] = Some(6)
    – scala> x.map {(n) => n * 2 }
    – Some(12) : Option[Int]
    – scala> x.map {(n) => n * 2 }.getOrElse(14)
    – 12 : Int
    – scala> x.map {(n) => n * 2 }.getOrElse(14)
    – 12 : Int
    – scala> x = None
    – x : Option[Int] = None





    View Slide

  66. Tests for free – Generically

    The “Option” type: “type-safe nulls”
    – scala> var x : Option[Int] = Some(6)
    – x : Option[Int] = Some(6)
    – scala> x.map {(n) => n * 2 }
    – Some(12) : Option[Int]
    – scala> x.map {(n) => n * 2 }.getOrElse(14)
    – 12 : Int
    – scala> x.map {(n) => n * 2 }.getOrElse(14)
    – 12 : Int
    – scala> x = None
    – x : Option[Int] = None
    – scala> x.map {(n) => n * 2 }.getOrElse(14)
    – 14 : Int

    View Slide

  67. But....

    Verbosity!

    Duck Typing!

    View Slide

  68. Verbosity?

    Type Inference!

    View Slide

  69. Verbosity?

    Type Inference!

    def fibonnaciSequence(n : Int) : List[Int] = {
    0.to(n-2).foldLeft(List(0,1)) { (fibs, n) =>
    val next : Int = fibs(fibs.length-1) +
    fibs(fibs.length-2)
    fibs ::: List(next)
    }
    }
    // fibonacciSequence(7)
    // => List[Int] = List(0,1,1,2,3,5,8,13)

    View Slide

  70. Verbosity?

    Type Inference!

    def fibonnaciSequence(n : Int) : List[Int] = {
    0.to(n-2).foldLeft(List(0,1)) { (fibs, n) =>
    val next : Int = fibs(fibs.length-1) +
    fibs(fibs.length-2)
    fibs ::: List(next)
    }
    }
    // fibonacciSequence(7)
    // => List[Int] = List(0,1,1,2,3,5,8,13)

    View Slide

  71. Verbosity?

    Type Inference!

    def fibonnaciSequence(n : Int) = {
    0.to(n-2).foldLeft(List(0,1)) { (fibs, n) =>
    val next = fibs(fibs.length-1) +
    fibs(fibs.length-2)
    fibs ::: List(next)
    }
    }
    // fibonacciSequence(7)
    // => List[Int] = List(0,1,1,2,3,5,8,13)

    View Slide

  72. Duck-typing?

    Structural types!

    View Slide

  73. Duck-typing?

    Structural types!
    trait SerializableAsJSON {
    def toJSON : String
    }
    def sendToTheInternet(thing : SerializableAsJSON) =
    {
    var serializedThing = thing.toJSON
    internet.send(serializedThing)
    }

    View Slide

  74. Duck-typing?

    Structural types!
    trait SerializableAsJSON {
    def toJSON : String
    }
    def sendToTheInternet(thing : SerializableAsJSON) =
    {
    var serializedThing = thing.toJSON
    internet.send(serializedThing)
    }

    View Slide

  75. Duck-typing?

    Structural types!
    def sendToTheInternet(thing : {def toJSON : String})
    = {
    var serializedThing = thing.toJSON
    internet.send(serializedThing)
    }

    View Slide

  76. But why?

    Types do the same job as tests
    – Tests are statements in logic
    – Types are also statements in logic (Curry-
    Howard correspondence, 1934)
    – So why choose one over the other?

    View Slide

  77. Type vs. Tests

    Tests are necessarily existentially
    qualified logical statements:
    – “For this specific input, we observe this
    output”

    Types are universally qualified logical
    statements:
    – “For all possible inputs, we observe this
    (type of) output”
    (modulo non-termination)
    – Types can make assertions about the call
    -site of your methods!

    View Slide

  78. Type vs. Tests

    However...
    – Types don't encode our test criteria
    Can drive design in the small
    – (But not in the large)
    – Can document contracts and interfaces
    – (But not the overall behaviour of a system)
    The type system complements,
    rather than replaces your test suite!

    View Slide

  79. This is all moot

    ...as Ruby doesn't do static type-
    checking.

    :-(

    But...

    View Slide

  80. Cool Ruby tools
    DiamondBack (by Michael Furr)
    – Optional, static type checker for Ruby
    Rantly (by Howard Yeh)
    – Quickcheck style fuzz-testing

    View Slide

  81. Cool Ruby tools
    DiamondBack (by Michael Furr)
    – Optional, static type checker for Ruby
    Rantly (By Howard Yeh)
    – Quickcheck style fuzz-testing
    describe "Squaring a number" do
    context "For all integer inputs" do
    it "returns a positive integer output" do
    Rantly {
    (integer ** 2).should be_greater_than_or_equal_to(0)
    }
    end
    end
    end

    View Slide

  82. Cool Ruby tools
    Ourselves!
    – Encapsulate everything, well-defined
    interfaces between components
    – Think carefully about how well tests cover
    possible inputs and states
    – Fail fast with clear error messages

    View Slide

  83. Encapsulation
    class Address < Struct.new(:line_1, :line_2,
    :postcode); end
    class LatLongService
    Class << self
    def lat_long_for_postcode(postcode)
    end
    end
    end
    LatLongService.lat_long_for_postcode(address.postcode)

    View Slide

  84. Encapsulation
    class Address < Struct.new(:line_1, :line_2, :postcode)
    def lat_long
    LatLongService.lat_long_for_postcode(postcode)
    end
    end
    address.lat_long

    View Slide

  85. Encapsulation

    Provides well-defined contract

    Halves validation tests needed

    Increases confidence in our code

    View Slide

  86. Thanks!




    Tim Cowlishaw / @mistertim

    View Slide