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?

types and tests for proving correctness of our
programs?

How can types make us better at Ruby?

4. Scala

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

15. A type-system bestiary

Strong or weak?
– Javascript:
phantomjs>

16. A type-system bestiary

Strong or weak?
– Javascript:
phantomjs> 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”
: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>

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”

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

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

35. A type-system bestiary

Ruby: Dynamic dispatch (Obviously)

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!

41. A type-system bestiary

Scala: Dynamic dispatch!

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.

object Example extends App {
def square(x : Int) : Int = x * x
square("5")
}

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

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

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

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

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

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

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

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]

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]

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]

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]

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

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

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]

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

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

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

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

67. But....

Verbosity!

Duck Typing!

68. Verbosity?

Type Inference!

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)

72. Duck-typing?

Structural types!

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

78. Type vs. Tests

However...
– Types don't encode our test criteria
– (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
:postcode); end
class LatLongService
Class << self
def lat_long_for_postcode(postcode)
end
end
end

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

85. Encapsulation

Provides well-defined contract

Halves validation tests needed

Increases confidence in our code

86. Thanks!

Tim Cowlishaw / @mistertim