Tim Cowlishaw
April 07, 2013
6.2k

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

April 07, 2013

## Transcript

1. ### From Ruby to Scala and back again: Better living through

type-checking Tim Cowlishaw Ruby Manor 4.0
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
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?

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]
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)
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)
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)
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)
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)
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
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]
13. ### What is a type? • A set of related operations

or behaviours: Bool - {!, &&, ||} Int - { +, -, *, div} String – {upcase, format} and: List – {join, map, filter, inject}
14. ### A type-system bestiary • Strong vs. weak • Static vs.

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

5 * “2”
17. ### A type-system bestiary • Strong or weak? – Javascript: phantomjs>

5 * “2” 10 phantomjs>
18. ### A type-system bestiary • Strong or weak? – Javascript: phantomjs>

5 * “2” 10 phantomjs> 5 + “2”
19. ### A type-system bestiary • Strong or weak? – Javascript: phantomjs>

5 * “2” 10 phantomjs> 5 + “2” 52
20. ### A type-system bestiary • Strong or weak? – Javascript –

Ruby: irb(main):001:0>
21. ### A type-system bestiary • Strong or weak? – Javascript –

Ruby: irb(main):001:0> 5 * “2”
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>
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”
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
25. ### A type-system bestiary • Strong or weak? – Javascript –

Ruby – Scala: scala>
26. ### A type-system bestiary • Strong or weak? – Javascript –

Ruby – Scala: scala> 5 * “2”
27. ### A type-system bestiary • Strong or weak? – Javascript –

Ruby – Scala: scala> 5 * “2” <console>:8: error: overloaded method value * with alternatives: (x: Double)Double <and> (x: Float)Float <and> (x: Long)Long <and> (x: Int)Int <and> (x: Char)Int <and> (x: Short)Int <and> (x: Byte)Int cannot be applied to (java.lang.String) scala>
28. ### A type-system bestiary • Strong or weak? – Javascript –

Ruby – Scala: scala> 5 * “2” <console>:8: error: overloaded method value * with alternatives: (x: Double)Double <and> (x: Float)Float <and> (x: Long)Long <and> (x: Int)Int <and> (x: Char)Int <and> (x: Short)Int <and> (x: Byte)Int cannot be applied to (java.lang.String) scala> 5 + “2”
29. ### A type-system bestiary • Strong or weak? – Javascript –

Ruby – Scala: scala> 5 * “2” <console>:8: error: overloaded method value * with alternatives: (x: Double)Double <and> (x: Float)Float <and> (x: Long)Long <and> (x: Int)Int <and> (x: Char)Int <and> (x: Short)Int <and> (x: Byte)Int cannot be applied to (java.lang.String) scala> 5 + “2” res8: String = 52
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)
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
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
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") }
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

36. ### A type-system bestiary • Ruby: Dynamic dispatch module NumberAnnouncer def

initialize(n) @n = n end def announcement raise NotImplementedError end end
37. ### A type-system bestiary • Ruby: Dynamic dispatch class EvenAnnouncer include

NumberAnnouncer def announcement "#{@n} is even!" end end
38. ### A type-system bestiary • Ruby: Dynamic dispatch class OddAnnouncer include

NumberAnnouncer def announcement "#{@n} is odd!" end end
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
40. ### A type-system bestiary • Ruby: Dynamic dispatch \$ ruby number_wang.rb

1 1 is odd! 2 2 is even!

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!" }
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) } }
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) } }
45. ### A type-system bestiary Scala: Dynamic dispatch! \$ scalac number_wang.scala \$

scala NumberWang 1 1 is odd! 2 2 is even!
46. ### A type-system bestiary • Ruby – Strong typing – Dynamic

checking – Dynamic dispatch • Scala – Strong typing – Static checking – Dynamic dispatch
47. ### Why do we care? • Static type checks – Ensure

that some classes of errors can't happen at runtime. – Give us tests for free.
48. ### Tests for free! object Example extends App { def square(x

: Int) : Int = x * x square("5") }
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
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} } }
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} } }
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
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
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
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
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]
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]
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]
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]
60. ### Tests for free – Generically • The “Option” type: “type-safe

nulls” – – – – – – –
61. ### Tests for free – Generically • The “Option” type: “type-safe

nulls” – scala> var x : Option[Int] = Some(6) – x : Option[Int] = Some(6) – – – – – – – – – – – – –
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] – – – – – – – – – – –
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 – – – – – – – – –
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 – – – – – – –
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 – – – – –
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 •

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

73. ### Duck-typing? • Structural types! trait SerializableAsJSON { def toJSON :

String } def sendToTheInternet(thing : SerializableAsJSON) = { var serializedThing = thing.toJSON internet.send(serializedThing) }
74. ### Duck-typing? • Structural types! trait SerializableAsJSON { def toJSON :

String } def sendToTheInternet(thing : SerializableAsJSON) = { var serializedThing = thing.toJSON internet.send(serializedThing) }
75. ### Duck-typing? • Structural types! def sendToTheInternet(thing : {def toJSON :

String}) = { var serializedThing = thing.toJSON internet.send(serializedThing) }
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?
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! –
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!
79. ### This is all moot • ...as Ruby doesn't do static

type- checking. • :-( • But...
80. ### Cool Ruby tools DiamondBack (by Michael Furr) – Optional, static

type checker for Ruby Rantly (by Howard Yeh) – Quickcheck style fuzz-testing
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
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 –
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)