SlideShare a Scribd company logo
Exploring type-directed, test-driven development
A case study using FizzBuzz
Franklin Chen
http://franklinchen.com/
June 7, 2014
Pittsburgh TechFest 2014
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 1 / 76
Outline
1 Introduction
2 Original FizzBuzz problem
3 FizzBuzz 2: user configuration
4 FizzBuzz 3: FizzBuzzPop and beyond
5 Parallel FizzBuzz
6 Conclusion
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 2 / 76
Outline
1 Introduction
2 Original FizzBuzz problem
3 FizzBuzz 2: user configuration
4 FizzBuzz 3: FizzBuzzPop and beyond
5 Parallel FizzBuzz
6 Conclusion
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 3 / 76
Goals of this presentation
Give a taste of a practical software development process that is:
test-driven
type-directed
Show everything for real (using Scala):
project build process
testing frameworks
all the code
Use FizzBuzz because:
problem: easy to understand
modifications: easy to understand
fun!
Encourage you to explore a modern typed language
This Monday, Apple ditched Objective C for its new language Swift!
Complete Swift code will shown at the end; looks a lot like Scala.
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 4 / 76
Test-driven development (TDD)
Think.
Write a test that fails.
Write code until test succeeds.
Repeat, and refactor as needed.
Is TDD dead?
Short answer: No.
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 5 / 76
Type systems
What is a type system?
A syntactic method to prove that bad things can’t happen.
“Debating” types “versus” tests?
Let’s use both types and tests!
But: use a good type system, not a bad one.
Some decent practical typed languages
OCaml: 20 years old
Haskell: 20 years old
Scala: 10 years old
Swift: 5 days old
Rust (still in progress)
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 6 / 76
Outline
1 Introduction
2 Original FizzBuzz problem
3 FizzBuzz 2: user configuration
4 FizzBuzz 3: FizzBuzzPop and beyond
5 Parallel FizzBuzz
6 Conclusion
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 8 / 76
Original FizzBuzz problem
FizzBuzz defined
Write a program that prints the numbers from 1 to 100.
But for multiples of three, print “Fizz” instead of the number.
And for the multiples of five, print “Buzz”.
For numbers which are multiples of both three and five, print “FizzBuzz”.
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 9 / 76
Starter code: main driver
Scala: a modern object-oriented and functional language.
object Main extends App {
// Will not compile yet!
runToSeq(1, 100).foreach(println)
}
Type-directed design: separate out effects (such as printing to
terminal) from the real work.
Type-directed feedback: compilation fails when something is not
implemented yet.
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 10 / 76
The joys of continuous compilation and testing
SBT: build tool supporting Scala, Java. . .
Winning features
Source file changes trigger smart recompilation!
Source file changes trigger rerun of the tests that depend on changed
code!
$ sbt
> ~testQuick
[info] Compiling 1 Scala source to ...
[error] ...Main.scala:16: not found: value runToSeq
[error] runToSeq(1, 100) foreach println
[error] ^
[error] one error found
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 11 / 76
Write type-directed stub
object Main extends App {
runToSeq(1, 100).foreach(println)
def runToSeq(start: Int, end: Int): Seq[String] = {
???
}
}
Write wanted type signature
??? is convenient for stubbing.
In Scala standard library
Just performs: throw new NotImplementedError
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 12 / 76
Write acceptance test (simplified)
Specs2: a fine testing framework for Scala, Java. . .
class MainSpec extends Specification { def is = s2"""
${Main.runToSeq(1, 16) ==== ‘strings for 1 to 16‘}
"""
val ‘strings for 1 to 16‘ = Seq(
"1", "2", "Fizz", "4", "Buzz", "Fizz",
"7", "8", "Fizz", "Buzz", "11", "Fizz",
"13", "14", "FizzBuzz", "16"
)
}
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 13 / 76
Test passes type check, but fails
Incremental compilation/testing kicks in:
Waiting for source changes... (press enter to interrupt)
[info] MainSpec
[info] x Main.runToSeq(1, 16) ==== ‘strings for 1 to 16
[error] an implementation is missing (Main.scala:19)
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 15 / 76
Outside-in: for a FizzBuzz unit
Types are shapes to assemble logically.
def runToSeq(start: Int, end: Int): Seq[String] = {
start.to(end).map(FizzBuzz.evaluate)
}
start.to(end): Seq[Int], where Seq[_] is a type constructor
that, given a type A, returns a type of Seq[A].
For any value of type Seq[A], map: (A => B) => Seq[B].
Therefore: need to implement function
FizzBuzz.evaluate: Int => String.
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 16 / 76
Implement new FizzBuzz module
A failing acceptance test drives discovery of
A unit, FizzBuzz
A function with a particular type, Int => String
object FizzBuzz {
type Evaluator = Int => String
val evaluate: Evaluator = { i =>
???
}
}
Types are better than comments as documentation!
Comments are not checkable, unlike types and tests.
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 18 / 76
First part of unit test: example-based
Manually write some examples.
class FizzBuzzSpec extends Specification { def is = s2"""
${FizzBuzz.evaluate(15) ==== "FizzBuzz"}
${FizzBuzz.evaluate(20) ==== "Buzz"}
${FizzBuzz.evaluate(6) ==== "Fizz"}
${FizzBuzz.evaluate(17) ==== "17"}
"""
}
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 19 / 76
The joy of property-based tests
ScalaCheck: a framework for writing property-based tests.
class FizzBuzzSpec extends Specification
with ScalaCheck { def is = s2"""
${‘Multiple of both 3 and 5 => "FizzBuzz"‘} """
def ‘Multiple of both 3 and 5 => "FizzBuzz"‘ =
prop { i: Int => (i % 3 == 0 && i % 5 == 0) ==>
{ FizzBuzz.evaluate(i) ==== "FizzBuzz" }
}
}
Winning features
Auto-generates random tests for each property (100 by default).
Type-driven: here, generates random Int values.
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 20 / 76
Property-based tests (continued)
The other three properties of interest:
def ‘Multiple of only 3 => "Fizz"‘ =
prop { i: Int => (i % 3 == 0 && i % 5 != 0) ==>
{ FizzBuzz.evaluate(i) ==== "Fizz" }
}
def ‘Multiple of only 5 => "Buzz"‘ =
prop { i: Int => (i % 3 != 0 && i % 5 == 0) ==>
{ FizzBuzz.evaluate(i) ==== "Buzz" }
}
def ‘Not a multiple of either 3 or 5 => number‘ =
prop { i: Int => (i % 3 != 0 && i % 5 != 0) ==>
{ FizzBuzz.evaluate(i) ==== i.toString }
}
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 22 / 76
A buggy and ugly solution
// Buggy and ugly!
val evaluate: Evaluator = { i =>
if (i % 3 == 0)
"Fizz"
else if (i % 5 == 0)
"Buzz"
else if (i % 3 == 0 && i % 5 == 0)
"FizzBuzz"
else
i.toString
}
[info] FizzBuzzSpec
[info] x FizzBuzz.evaluate(15) ==== "FizzBuzz"
[error] ’Fizz’ is not equal to ’FizzBuzz’
(FizzBuzzSpec.scala:14)
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 23 / 76
Booleans are evil!
Maze of twisty little conditionals, all different
Too easy to write incorrect sequences of nested, combined
conditionals.
Overuse of Booleans is a type smell.
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 24 / 76
Pattern matching organizes information
val evaluate: Evaluator = { i =>
(i % 3 == 0, i % 5 == 0) match {
case (true, false) => "Fizz"
case (false, true) => "Buzz"
case (true, true) => "FizzBuzz"
case (false, false) => i.toString
}
}
Winning features
Visual beauty and clarity.
No duplicated conditionals.
No ordering dependency.
Type checker verifies full coverage of cases.
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 26 / 76
Example of non-exhaustive pattern matching
val evaluate: Evaluator = { i =>
(i % 3 == 0, i % 5 == 0) match {
case (true, false) => "Fizz"
case (false, true) => "Buzz"
case (true, true) => "FizzBuzz"
// case (false, false) => ???
}
}
[warn] ...FizzBuzz.scala:46: match may not be exhaustive.
[warn] It would fail on the following input: (false, false)
[warn] (i % 3 == 0, i % 5 == 0) match {
[warn] ^
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 27 / 76
Acceptance test passes
[info] MainSpec
[info] + Main.runToSeq(1, 16) ==== ‘strings for 1 to 16
Done?
No. Client wants more features.
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 29 / 76
Outline
1 Introduction
2 Original FizzBuzz problem
3 FizzBuzz 2: user configuration
4 FizzBuzz 3: FizzBuzzPop and beyond
5 Parallel FizzBuzz
6 Conclusion
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 30 / 76
Adding new features
Client wants to:
Choose two arbitrary divisors in place of 3 and 5
such as 4 and 7
Choose other arbitrary words in place of "Fizz" and "Buzz"
such as "Moo" and "Quack"
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 31 / 76
Type-driven refactoring
Types mean: refactoring is much more fun!
Add new tests.
Change types and code: to make new tests type check.
Refactor original code and tests: use new APIs.
Keep passing the old tests.
Delay writing code for new features.
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 32 / 76
More features means more types
Change FizzBuzz.evaluate to Defaults.fizzBuzzer:
def runToSeq(start: Int, end: Int): Seq[String] = {
start.to(end).map(Defaults.fizzBuzzer)
}
Add new types to FizzBuzz module:
type Evaluator = Int => String
case class Config(pair1: (Int, String),
pair2: (Int, String))
type Compiler = Config => Evaluator
val compile: Compiler = {
case Config((d1, w1), (d2, w2)) =>
{ i =>
???
}
}
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 33 / 76
Extract original default configuration
object Defaults {
val fizzBuzzerConfig: Config =
Config(3 -> "Fizz", 5 -> "Buzz")
val fizzBuzzer: Evaluator =
FizzBuzz.compile(fizzBuzzerConfig)
// Useful to keep old implementation
val oldFizzBuzzer: Evaluator = { i =>
(i % 3 == 0, i % 5 == 0) match {
case (true, false) => "Fizz"
case (false, true) => "Buzz"
case (true, true) => "FizzBuzz"
case (false, false) => i.toString
}
}
}
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 34 / 76
More types means more tests
Write new property-based test over arbitrary user configurations:
val arbitraryConfig: Arbitrary[Config] = Arbitrary {
for {
(d1, d2, w1, w2) <-
arbitrary[(Int, Int, String, String)]
} yield Config(d1 -> w1, d2 -> w2)
}
def ‘Arbitrary pair of divisors: divisible by first‘ =
arbitraryConfig { config: Config =>
val evaluate = FizzBuzz.compile(config)
val Config((d1, w1), (d2, _)) = config
prop { i: Int => (i % d1 == 0 && i % d2 != 0) ==>
{ evaluate(i) ==== w1 }
}
}
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 35 / 76
Problem: coarse Config type
[info] ! Arbitrary divisor/word pair fizzBuzzers
[error] ArithmeticException: :
A counter-example is ’Config((0,),(0,))’:
java.lang.ArithmeticException: / by zero
(after 0 try) (FizzBuzzSpec.scala:58)
0 as a divisor crashes!
We discovered client’s underspecification.
Client says: meant to allow only divisors within 2 and 100.
We need to:
Add runtime validation when constructing Config.
Refine Config random generator.
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 36 / 76
Add (runtime) validation
Runtime precondition contract: Scala’s require (throws exception on
failure):
val DIVISOR_MIN = 2; val DIVISOR_MAX = 100
def validatePair(pair: (Int, String)) = pair match {
case (d, _) =>
require(d >= DIVISOR_MIN,
s"divisor $d must be >= $DIVISOR_MIN")
require(d <= DIVISOR_MAX,
s"divisor $d must be <= $DIVISOR_MAX")
}
case class Config(pair1: (Int, String),
pair2: (Int, String)) {
validatePair(pair1); validatePair(pair2)
}
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 37 / 76
A note on exceptions and types
Exceptions are evil because they escape the type system.
In real life, I prefer to use a principled type-based validation system,
such as Scalaz.
Note: Swift does not have exceptions, so expect some type-based
libraries to emerge!
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 38 / 76
Improve Config random generator
val arbitraryConfig: Arbitrary[Config] =
Arbitrary {
for {
d1 <- choose(DIVISOR_MIN, DIVISOR_MAX)
d2 <- choose(DIVISOR_MIN, DIVISOR_MAX)
w1 <- arbitrary[String]
w2 <- arbitrary[String]
} yield Config(d1 -> w1, d2 -> w2)
}
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 40 / 76
New test runs further, stills fails
Refactor old code to FizzBuzz.compile, to pass old tests and new test.
val compile: Compiler = {
case Config((d1, w1), (d2, w2)) =>
// Precompute, hence "compiler".
val w = w1 + w2
// Return an Evaluator.
{ i =>
(i % d1 == 0, i % d2 == 0) match {
case (true, false) => w1
case (false, true) => w2
case (true, true) => w
case (false, false) => i.toString
}
}
}
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 41 / 76
Outline
1 Introduction
2 Original FizzBuzz problem
3 FizzBuzz 2: user configuration
4 FizzBuzz 3: FizzBuzzPop and beyond
5 Parallel FizzBuzz
6 Conclusion
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 42 / 76
Generalizing to more than two divisors
Client wants FizzBuzzPop!
Given three divisors (such as 3, 5, 7).
Given three words (such as "Fizz", "Buzz", "Pop").
Example: 21 should output "FizzPop".
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 43 / 76
More features means more tests
Write new tests for new Defaults.fizzBuzzPopper:
def is = s2"""
${Defaults.fizzBuzzPopper(2) ==== "2"}
${Defaults.fizzBuzzPopper(21) ==== "FizzPop"}
${Defaults.fizzBuzzPopper(9) ==== "Fizz"}
${Defaults.fizzBuzzPopper(7) ==== "Pop"}
${Defaults.fizzBuzzPopper(35) ==== "BuzzPop"}
"""
Change configuration: to Seq of pairs, instead of just two:
val fizzBuzzPopperConfig: Config =
Config(Seq(
3 -> "Fizz", 5 -> "Buzz", 7 -> "Pop"
))
val fizzBuzzPopper: Evaluator =
FizzBuzz.compile(fizzBuzzPopperConfig)
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 45 / 76
More tests means more (or changed) types
[error] ...Defaults.scala:29: not enough arguments for
method apply:
(pair1: (Int, String), pair2: (Int, String))
com.franklinchen.FizzBuzz.Config in object Config
[error] Unspecified value parameter pair2.
[error] Config(Seq(
[error] ^
Change type Config to allow a sequence of pairs:
case class Config(pairs: Seq[(Int, String)]) {
pairs.foreach(validatePair)
}
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 46 / 76
Fix remaining type errors
Refactoring reveals need to implement case of more than two divisors.
val compile: Compiler = {
case Config(Seq((d1, w1), (d2, w2))) =>
val w = w1 + w2
{ i =>
(i % d1 == 0, i % d2 == 0) match {
case (true, false) => w1
case (false, true) => w2
case (true, true) => w
case (false, false) => i.toString
}
}
case _ => // TODO handle more than 2
{ i => ??? }
}
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 47 / 76
More computation means more types
Compile each divisor to a “rule” that awaits input.
type Rule = Int => String
val buildRule: ((Int, String)) => Rule = {
case (n, word) => { i =>
if (i % n == 0) word else ""
}
}
FizzBuzz demo time!
Two volunteers: each to play role of Rule.
One “manager” to combine two sub-results.
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 49 / 76
Assemble the types
type Evaluator = Int => String
type Compiler = Config => Evaluator
type Rule = Int => String
val compile: Compiler = { case Config(pairs) =>
val rules: Seq[Rule] = pairs.map(buildRule) // compile
// Return an Evaluator.
{ i =>
val words: Seq[String] = rules.map { rule => rule(i) }
val combined: String = words.reduce { (x, y) => x + y }
if (combined == "") i.toString else combined
}
}
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 51 / 76
A note on reduce
For any value of type Seq[A], reduce: ((A, A) => B) => B.
Example: for Seq[String], reduction with string concatenation + returns
the concatenation of all the strings in the sequence.
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 52 / 76
Test failure: coarse types again
nfo] x Arbitrary pair of divisors: divisible by first
rror] A counter-example is ’Config(List((8,), (32,)))’
(after 0 try)
rror] A counter-example is ’32405464’
(after 0 try - shrinked (’1150076488’ -> ’32405464’))
rror] ’32405464’ is not equal to ’’
(FizzBuzzSpec.scala:71)
Demo time!
Configuration: Seq(3 -> "", 5 -> "Buzz")
Input: 9 (note: divisible by 3)
Output: should be "" (because of the part divisible by 3)
Output was: "9"
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 53 / 76
Assemble the types
type Evaluator = Int => String
type Compiler = Config => Evaluator
type Rule = Int => String
val compile: Compiler = { case Config(pairs) =>
val rules: Seq[Rule] = pairs.map(buildRule) // compile
// Return an Evaluator.
{ i =>
val words: Seq[String] = rules.map { rule => rule(i) }
val combined: String = words.reduce { (x, y) => x + y }
if (combined == "") i.toString else combined
}
}
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 54 / 76
Property-based testing rescued us again!
Be honest: would you have caught this bug manually?
I didn’t.
I never wrote FizzBuzzPop examples testing empty strings.
Property-based testing reveals unexpected corner cases.
(Empty “fizz” and “buzz” word strings).
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 55 / 76
An empty string is not equivalent to no string
Presence of something “empty” is not equivalent to no thing.
Sending someone an empty email versus not sending any email.
Many programming languages get this wrong.
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 56 / 76
Option[A] type
Option[A] is one of two possibilities:
None
Some(a) wraps a value a of type A.
For example, Some("") is not the same as None.
val fizzFor3 = Some("") // multiple of 3
val buzzFor3 = None // not multiple of 5
val fizzbuzzFor3 = Some("") // fizzed ""
val fizzFor2 = None // not multiple of 3
val buzzFor2 = None // not multiple of 5
val fizzbuzzFor2 = None // not multiple of any
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 57 / 76
Cleaning up the types
// was: type Rule = Int => String
type Rule = Int => Option[String]
Useful type errors:
und : String
quired: Option[String]
word
^
und : String("")
quired: Option[String]
""
^
und : Seq[Option[String]]
quired: Seq[String]
val words: Seq[String] = rules map { rule => rule(i) }
^
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 58 / 76
Fix the type errors: our rule builder
type Rule = Int => Option[String]
val buildRule: ((Int, String)) => Rule = {
case (n, word) => { i =>
(i % n == 0).option(word)
}
}
Demo time!
(Instructions: for result Some(s), hold up the string, else don’t hold
up anything)
Configuration: Seq(3 -> "", 5 -> "Buzz")
Input: 9
Output: now correctly is ""
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 59 / 76
Fix the type errors: our compiler
val compile: Compiler = { case Config(pairs) =>
val rules: Seq[Rule] = pairs map buildRule
{ i =>
val wordOptions: Seq[Option[String]] =
rules.map { rule => rule(i) }
val combinedOption: Option[String] =
wordOptions.reduce(addOption)
combinedOption.getOrElse(i.toString)
}
}
We need to write: addOption
Scala standard library provides: getOrElse
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 60 / 76
“Addition” for Option[String]
def addOption(a1: Option[String], a2: Option[String]):
Option[String] = (a1, a2) match {
case (Some(s1), None) => Some(s1)
case (None, Some(s2)) => Some(s2)
case (Some(s1), Some(s2)) => Some(s1 + s2)
case (None, None) => None
}
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 61 / 76
Getting A back out of Option[A]
Do not lose information!
getOrElse inspects the
and either
returns the value v inside a Some(v),
or else returns the specific default value.
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 62 / 76
The same thing, in Swift
func addOption(a1: String?, a2: String?) -> String? = {
switch (a1, a2) {
case (let .Some(s1), .None): return .Some(s1)
case (.None, let .Some(s2)): return .Some(s2)
case (let .Some(s1), let .Some(s2)): return .Some(s1+s2)
case (.None, .None): return .None
}
}
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 64 / 76
Transform information; don’t destroy it
Did you notice? Our new code no longer uses if.
Bug cause: destroying information, using if
Building a rule: if (i % n == 0) word else ""
Obtaining a final string answer:
if (combined == "") i.toString else combined
Transforming information
To Option[String]: (i % n == 0).option(word)
To String: combinedOption.getOrElse(i.toString)
Type-directed design tip
We could have saved trouble up front, by using precise types.
Avoid if, when possible.
Avoid String (but required at I/O boundaries of program).
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 65 / 76
Bonus: the final code in Swift
typealias Evaluator = Int -> String
typealias Config = [(Int, String)]
typealias Compiler = Config -> Evaluator
typealias Rule = Int -> String?
let buildRule: ((Int, String)) -> Rule = { n, word in
{ i in return (i % n == 0) ? word : nil } }
}
let compile: Compiler = { pairs in
let rules: [Rule] = pairs.map(buildRule)
return { i in
let wordOptions = rules.map { rule in rule(i) }
let combinedOption = wordOptions.reduce(nil, addOption)
if let combined = combinedOption { return combined }
else { return String(i) }
}
}Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 66 / 76
Outline
1 Introduction
2 Original FizzBuzz problem
3 FizzBuzz 2: user configuration
4 FizzBuzz 3: FizzBuzzPop and beyond
5 Parallel FizzBuzz
6 Conclusion
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 67 / 76
Parallelism
Use of map: parallelizable; there are high-performance parallel
collections for Scala.
Use of reduce: parallelizable because of the monoid property:
Option[String] is a Monoid
There is an identity element (None).
There is a binary associative operator (addOption).
Fantastically important in practice!
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 68 / 76
Final demo!
Demo time!
Configuration:
Seq(3 -> "Fizz", 5 -> "Buzz", 7 -> "Pop", 2 -> "Boom")
Tree of volunteers to simulate concurrency:
Four at leaves.
Two “middle managers” each handling two leaves.
One top-level manager handling two middle managers.
Input: 42
Output: "FizzPopBoom"
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 69 / 76
Final parallelized code
val parallelCompile: Compiler = { case Config(pairs) =>
val rules = pairs.
toArray.
toPar.
map(buildRule)
{ i: Int => rules.
map { rule => rule(i) }.
reduce(addOption).
getOrElse(i.toString)
}
}
Coding style tip
This level of conciseness is not always best: maybe too “clever”?
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 70 / 76
Parallelism summary
We discovered a theoretical speedup for generalized FizzBuzz:
Sequential: O(n)
Parallel: O(log n) (given log n processors, and omitting some
technical subtleties)
Also, driver outer loop can be sped up:
Sequential loop on 1 to m: O(m)
Parallel loop: O(1) (given m processors)
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 72 / 76
Future work
Asynchronous
Real-time
Interactive
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 73 / 76
Outline
1 Introduction
2 Original FizzBuzz problem
3 FizzBuzz 2: user configuration
4 FizzBuzz 3: FizzBuzzPop and beyond
5 Parallel FizzBuzz
6 Conclusion
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 74 / 76
Conclusion
Tests are great.
Types are great.
Tests and types work hand in hand, driving design and program
evolution.
Modern typed languages such as Scala promote fun, correct
programming!
It’s a great time to be learning and using a modern typed language:
Apple ditched Objective C for Swift.
Code, slides, article
Go to https://github.com/franklinchen/
talk-on-type-directed-tdd-using-fizzbuzz
The article has more detail omitted in the presentation.
The hyperlinks in all provided PDFs are clickable.
The Swift code:
https://github.com/franklinchen/fizzbuzz-swift
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 75 / 76
Some free online courses on modern typed functional
programming
Using Scala: Functional Programming Principles in Scala on
Coursera, taught by Martin Odersky (inventor of Scala)
Using Haskell: Introduction to Functional Programming on EdX,
taught by Erik Meijer (Haskell hero)
Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 76 / 76

More Related Content

Viewers also liked

Physical Science: Chapter 4, sec 2
Physical Science: Chapter 4, sec 2Physical Science: Chapter 4, sec 2
Physical Science: Chapter 4, sec 2
mshenry
 
The present simple
The present simpleThe present simple
The present simple
Alexandra Canal
 
Flanders Open Data Day II - KeyNote - Erik Mannens
Flanders Open Data Day II - KeyNote - Erik MannensFlanders Open Data Day II - KeyNote - Erik Mannens
Flanders Open Data Day II - KeyNote - Erik MannensErik Mannens
 
PA EI Leadership Meeting: Technology to Suppoty EI Leaders
PA EI Leadership Meeting: Technology to Suppoty EI LeadersPA EI Leadership Meeting: Technology to Suppoty EI Leaders
PA EI Leadership Meeting: Technology to Suppoty EI LeadersJenny Brown
 
Effective and Efficient Entity Search in RDF data
Effective and Efficient Entity Search in RDF dataEffective and Efficient Entity Search in RDF data
Effective and Efficient Entity Search in RDF data
Roi Blanco
 
General guidelines for the selection of the best sections and category of sch...
General guidelines for the selection of the best sections and category of sch...General guidelines for the selection of the best sections and category of sch...
General guidelines for the selection of the best sections and category of sch...
Mernalyn Magbilang
 
My name is
My name isMy name is
My name isSaRiCo
 
Physical Science: Chapter 5, sec3
Physical Science: Chapter 5, sec3Physical Science: Chapter 5, sec3
Physical Science: Chapter 5, sec3
mshenry
 
Pattern seeking across pictures
Pattern seeking across picturesPattern seeking across pictures
Pattern seeking across picturesRachel Bear
 
Sm250rink
Sm250rinkSm250rink
Sm250rinkRegina
 
Physical Science: Chapter 5, sec 1
Physical Science: Chapter 5, sec 1Physical Science: Chapter 5, sec 1
Physical Science: Chapter 5, sec 1
mshenry
 
Harry potter and the deathly hallows review.
Harry potter and the deathly hallows review.Harry potter and the deathly hallows review.
Harry potter and the deathly hallows review.Becca McPartland
 
Hire Immigrants Halifax Allies Report 2010
Hire Immigrants Halifax Allies Report 2010Hire Immigrants Halifax Allies Report 2010
Hire Immigrants Halifax Allies Report 2010
Halifax Partnership
 
Espire baques
Espire baquesEspire baques
Espire baques
Akilan Chockalingam
 
本当は怖いオープンデータ・ビッグデータ
本当は怖いオープンデータ・ビッグデータ本当は怖いオープンデータ・ビッグデータ
本当は怖いオープンデータ・ビッグデータpgcafe
 

Viewers also liked (20)

Physical Science: Chapter 4, sec 2
Physical Science: Chapter 4, sec 2Physical Science: Chapter 4, sec 2
Physical Science: Chapter 4, sec 2
 
The present simple
The present simpleThe present simple
The present simple
 
Flanders Open Data Day II - KeyNote - Erik Mannens
Flanders Open Data Day II - KeyNote - Erik MannensFlanders Open Data Day II - KeyNote - Erik Mannens
Flanders Open Data Day II - KeyNote - Erik Mannens
 
PA EI Leadership Meeting: Technology to Suppoty EI Leaders
PA EI Leadership Meeting: Technology to Suppoty EI LeadersPA EI Leadership Meeting: Technology to Suppoty EI Leaders
PA EI Leadership Meeting: Technology to Suppoty EI Leaders
 
Flat plans
Flat plansFlat plans
Flat plans
 
Effective and Efficient Entity Search in RDF data
Effective and Efficient Entity Search in RDF dataEffective and Efficient Entity Search in RDF data
Effective and Efficient Entity Search in RDF data
 
General guidelines for the selection of the best sections and category of sch...
General guidelines for the selection of the best sections and category of sch...General guidelines for the selection of the best sections and category of sch...
General guidelines for the selection of the best sections and category of sch...
 
My name is
My name isMy name is
My name is
 
Physical Science: Chapter 5, sec3
Physical Science: Chapter 5, sec3Physical Science: Chapter 5, sec3
Physical Science: Chapter 5, sec3
 
Pattern seeking across pictures
Pattern seeking across picturesPattern seeking across pictures
Pattern seeking across pictures
 
Sm250rink
Sm250rinkSm250rink
Sm250rink
 
Physical Science: Chapter 5, sec 1
Physical Science: Chapter 5, sec 1Physical Science: Chapter 5, sec 1
Physical Science: Chapter 5, sec 1
 
Harry potter and the deathly hallows review.
Harry potter and the deathly hallows review.Harry potter and the deathly hallows review.
Harry potter and the deathly hallows review.
 
Gic2011 aula9-ingles
Gic2011 aula9-inglesGic2011 aula9-ingles
Gic2011 aula9-ingles
 
Hire Immigrants Halifax Allies Report 2010
Hire Immigrants Halifax Allies Report 2010Hire Immigrants Halifax Allies Report 2010
Hire Immigrants Halifax Allies Report 2010
 
Espire baques
Espire baquesEspire baques
Espire baques
 
Gic2011 aula8-ingles
Gic2011 aula8-inglesGic2011 aula8-ingles
Gic2011 aula8-ingles
 
本当は怖いオープンデータ・ビッグデータ
本当は怖いオープンデータ・ビッグデータ本当は怖いオープンデータ・ビッグデータ
本当は怖いオープンデータ・ビッグデータ
 
2014 Halifax Index Presentation
2014 Halifax Index Presentation2014 Halifax Index Presentation
2014 Halifax Index Presentation
 
Pres
PresPres
Pres
 

Similar to Exploring type-directed, test-driven development: a case study using FizzBuzz

TDD - Test Driven Development - Java JUnit FizzBuzz
TDD - Test Driven Development - Java JUnit FizzBuzzTDD - Test Driven Development - Java JUnit FizzBuzz
TDD - Test Driven Development - Java JUnit FizzBuzz
Alan Richardson
 
Basic TDD moves
Basic TDD movesBasic TDD moves
Basic TDD moves
Fernando Cuenca
 
The Rule of Three
The Rule of ThreeThe Rule of Three
The Rule of Three
Kevlin Henney
 
C# to python
C# to pythonC# to python
C# to python
Tess Ferrandez
 
Introducción práctica a Test-Driven Development (TDD)
Introducción práctica a Test-Driven Development (TDD)Introducción práctica a Test-Driven Development (TDD)
Introducción práctica a Test-Driven Development (TDD)
Software Craftsmanship Alicante
 
Introducción práctica a TDD
Introducción práctica a TDDIntroducción práctica a TDD
Introducción práctica a TDD
rubocoptero
 
MozillaPH Rust Hack & Learn Session 2
MozillaPH Rust Hack & Learn Session 2MozillaPH Rust Hack & Learn Session 2
MozillaPH Rust Hack & Learn Session 2
Robert 'Bob' Reyes
 
Fuzz-testing: A hacker's approach to making your code more secure | Pascal Ze...
Fuzz-testing: A hacker's approach to making your code more secure | Pascal Ze...Fuzz-testing: A hacker's approach to making your code more secure | Pascal Ze...
Fuzz-testing: A hacker's approach to making your code more secure | Pascal Ze...
Codemotion
 
Where do Rubyists go?
 Where do Rubyists go?  Where do Rubyists go?
Where do Rubyists go?
Tobias Pfeiffer
 
COSCUP2012: How to write a bash script like the python?
COSCUP2012: How to write a bash script like the python?COSCUP2012: How to write a bash script like the python?
COSCUP2012: How to write a bash script like the python?Lloyd Huang
 
Python and test
Python and testPython and test
Python and test
Micron Technology
 
Js fwdays unit tesing javascript(by Anna Khabibullina)
Js fwdays unit tesing javascript(by Anna Khabibullina)Js fwdays unit tesing javascript(by Anna Khabibullina)
Js fwdays unit tesing javascript(by Anna Khabibullina)
Anna Khabibullina
 
JS Frameworks Day April,26 of 2014
JS Frameworks Day April,26 of 2014JS Frameworks Day April,26 of 2014
JS Frameworks Day April,26 of 2014
DA-14
 
Static types on javascript?! Type checking approaches to ensure healthy appli...
Static types on javascript?! Type checking approaches to ensure healthy appli...Static types on javascript?! Type checking approaches to ensure healthy appli...
Static types on javascript?! Type checking approaches to ensure healthy appli...
Arthur Puthin
 
Bdd: Tdd and beyond the infinite
Bdd: Tdd and beyond the infiniteBdd: Tdd and beyond the infinite
Bdd: Tdd and beyond the infiniteGiordano Scalzo
 
O CPAN tem as ferramentas que você precisa para fazer TDD em Perl, o Coding D...
O CPAN tem as ferramentas que você precisa para fazer TDD em Perl, o Coding D...O CPAN tem as ferramentas que você precisa para fazer TDD em Perl, o Coding D...
O CPAN tem as ferramentas que você precisa para fazer TDD em Perl, o Coding D...
Rodolfo Carvalho
 
Guildford Coding Dojo1
Guildford Coding Dojo1Guildford Coding Dojo1
Guildford Coding Dojo1
thirstybear
 
Introducing Swift - and the Sunset of Our Culture?
Introducing Swift - and the Sunset of Our Culture?Introducing Swift - and the Sunset of Our Culture?
Introducing Swift - and the Sunset of Our Culture?
dankogai
 
The Search for the Perfect Program
The Search for the Perfect ProgramThe Search for the Perfect Program
The Search for the Perfect ProgramNatallie Baikevich
 
Missing objects: ?. and ?? in JavaScript (BrazilJS 2018)
Missing objects: ?. and ?? in JavaScript (BrazilJS 2018)Missing objects: ?. and ?? in JavaScript (BrazilJS 2018)
Missing objects: ?. and ?? in JavaScript (BrazilJS 2018)
Igalia
 

Similar to Exploring type-directed, test-driven development: a case study using FizzBuzz (20)

TDD - Test Driven Development - Java JUnit FizzBuzz
TDD - Test Driven Development - Java JUnit FizzBuzzTDD - Test Driven Development - Java JUnit FizzBuzz
TDD - Test Driven Development - Java JUnit FizzBuzz
 
Basic TDD moves
Basic TDD movesBasic TDD moves
Basic TDD moves
 
The Rule of Three
The Rule of ThreeThe Rule of Three
The Rule of Three
 
C# to python
C# to pythonC# to python
C# to python
 
Introducción práctica a Test-Driven Development (TDD)
Introducción práctica a Test-Driven Development (TDD)Introducción práctica a Test-Driven Development (TDD)
Introducción práctica a Test-Driven Development (TDD)
 
Introducción práctica a TDD
Introducción práctica a TDDIntroducción práctica a TDD
Introducción práctica a TDD
 
MozillaPH Rust Hack & Learn Session 2
MozillaPH Rust Hack & Learn Session 2MozillaPH Rust Hack & Learn Session 2
MozillaPH Rust Hack & Learn Session 2
 
Fuzz-testing: A hacker's approach to making your code more secure | Pascal Ze...
Fuzz-testing: A hacker's approach to making your code more secure | Pascal Ze...Fuzz-testing: A hacker's approach to making your code more secure | Pascal Ze...
Fuzz-testing: A hacker's approach to making your code more secure | Pascal Ze...
 
Where do Rubyists go?
 Where do Rubyists go?  Where do Rubyists go?
Where do Rubyists go?
 
COSCUP2012: How to write a bash script like the python?
COSCUP2012: How to write a bash script like the python?COSCUP2012: How to write a bash script like the python?
COSCUP2012: How to write a bash script like the python?
 
Python and test
Python and testPython and test
Python and test
 
Js fwdays unit tesing javascript(by Anna Khabibullina)
Js fwdays unit tesing javascript(by Anna Khabibullina)Js fwdays unit tesing javascript(by Anna Khabibullina)
Js fwdays unit tesing javascript(by Anna Khabibullina)
 
JS Frameworks Day April,26 of 2014
JS Frameworks Day April,26 of 2014JS Frameworks Day April,26 of 2014
JS Frameworks Day April,26 of 2014
 
Static types on javascript?! Type checking approaches to ensure healthy appli...
Static types on javascript?! Type checking approaches to ensure healthy appli...Static types on javascript?! Type checking approaches to ensure healthy appli...
Static types on javascript?! Type checking approaches to ensure healthy appli...
 
Bdd: Tdd and beyond the infinite
Bdd: Tdd and beyond the infiniteBdd: Tdd and beyond the infinite
Bdd: Tdd and beyond the infinite
 
O CPAN tem as ferramentas que você precisa para fazer TDD em Perl, o Coding D...
O CPAN tem as ferramentas que você precisa para fazer TDD em Perl, o Coding D...O CPAN tem as ferramentas que você precisa para fazer TDD em Perl, o Coding D...
O CPAN tem as ferramentas que você precisa para fazer TDD em Perl, o Coding D...
 
Guildford Coding Dojo1
Guildford Coding Dojo1Guildford Coding Dojo1
Guildford Coding Dojo1
 
Introducing Swift - and the Sunset of Our Culture?
Introducing Swift - and the Sunset of Our Culture?Introducing Swift - and the Sunset of Our Culture?
Introducing Swift - and the Sunset of Our Culture?
 
The Search for the Perfect Program
The Search for the Perfect ProgramThe Search for the Perfect Program
The Search for the Perfect Program
 
Missing objects: ?. and ?? in JavaScript (BrazilJS 2018)
Missing objects: ?. and ?? in JavaScript (BrazilJS 2018)Missing objects: ?. and ?? in JavaScript (BrazilJS 2018)
Missing objects: ?. and ?? in JavaScript (BrazilJS 2018)
 

Recently uploaded

Hidden Gems of Europe - DISCOVERING THE CONTINENT'S BEST-KEPT SECRETS
Hidden Gems of Europe - DISCOVERING THE CONTINENT'S BEST-KEPT SECRETSHidden Gems of Europe - DISCOVERING THE CONTINENT'S BEST-KEPT SECRETS
Hidden Gems of Europe - DISCOVERING THE CONTINENT'S BEST-KEPT SECRETS
Kamil Uğraş TÜRKOĞLU
 
How To Talk To a Live Person at American Airlines
How To Talk To a Live Person at American AirlinesHow To Talk To a Live Person at American Airlines
How To Talk To a Live Person at American Airlines
flyn goo
 
LUXURY TRAVEL THE ULTIMATE TOKYO EXPERIENCE FROM SINGAPORE.pdf
LUXURY TRAVEL THE ULTIMATE TOKYO EXPERIENCE FROM SINGAPORE.pdfLUXURY TRAVEL THE ULTIMATE TOKYO EXPERIENCE FROM SINGAPORE.pdf
LUXURY TRAVEL THE ULTIMATE TOKYO EXPERIENCE FROM SINGAPORE.pdf
Diper Tour
 
Uk Visa Complete Guide and application process
Uk Visa Complete Guide and application processUk Visa Complete Guide and application process
Uk Visa Complete Guide and application process
pandeypratikwgblindi
 
Understanding the Running Costs of Electric Scooters.pptx
Understanding the Running Costs of Electric Scooters.pptxUnderstanding the Running Costs of Electric Scooters.pptx
Understanding the Running Costs of Electric Scooters.pptx
Zivah ElectriVa Private Limited
 
欧洲杯开户-信誉的欧洲杯开户-正规欧洲杯开户|【​网址​🎉ac123.net🎉​】
欧洲杯开户-信誉的欧洲杯开户-正规欧洲杯开户|【​网址​🎉ac123.net🎉​】欧洲杯开户-信誉的欧洲杯开户-正规欧洲杯开户|【​网址​🎉ac123.net🎉​】
欧洲杯开户-信誉的欧洲杯开户-正规欧洲杯开户|【​网址​🎉ac123.net🎉​】
bljeremy734
 
The Power of a Glamping Go-To-Market Accelerator Plan.pptx
The Power of a Glamping Go-To-Market Accelerator Plan.pptxThe Power of a Glamping Go-To-Market Accelerator Plan.pptx
The Power of a Glamping Go-To-Market Accelerator Plan.pptx
RezStream
 
Winter Festivities in Italy
Winter Festivities in ItalyWinter Festivities in Italy
Winter Festivities in Italy
Time for Sicily
 
Jose RIZAL History and his travel Paris to berlin
Jose RIZAL History and his travel Paris to berlinJose RIZAL History and his travel Paris to berlin
Jose RIZAL History and his travel Paris to berlin
MaryGraceArdalesLope
 
4 DAYS MASAI MARA WILDEBEEST MIGRATION SAFARI TOUR PACKAGE KENYA
4 DAYS MASAI MARA WILDEBEEST MIGRATION SAFARI TOUR PACKAGE KENYA4 DAYS MASAI MARA WILDEBEEST MIGRATION SAFARI TOUR PACKAGE KENYA
4 DAYS MASAI MARA WILDEBEEST MIGRATION SAFARI TOUR PACKAGE KENYA
Bush Troop Safari
 
Antarctica- Icy wilderness of extremes and wonder
Antarctica- Icy wilderness of extremes and wonderAntarctica- Icy wilderness of extremes and wonder
Antarctica- Icy wilderness of extremes and wonder
tahreemzahra82
 
Ooty Honeymoon Package from Chennai.docx
Ooty Honeymoon Package from Chennai.docxOoty Honeymoon Package from Chennai.docx
Ooty Honeymoon Package from Chennai.docx
Ooty Heritage Tours and Travels
 
Agence Régionale du Tourisme Grand Est - brochure MICE 2024.pdf
Agence Régionale du Tourisme Grand Est - brochure MICE 2024.pdfAgence Régionale du Tourisme Grand Est - brochure MICE 2024.pdf
Agence Régionale du Tourisme Grand Est - brochure MICE 2024.pdf
MICEboard
 
TOP 10 Historic Places To See in Kuruskhetra.
TOP 10 Historic Places To See in Kuruskhetra.TOP 10 Historic Places To See in Kuruskhetra.
TOP 10 Historic Places To See in Kuruskhetra.
ujjwalsethi113
 

Recently uploaded (14)

Hidden Gems of Europe - DISCOVERING THE CONTINENT'S BEST-KEPT SECRETS
Hidden Gems of Europe - DISCOVERING THE CONTINENT'S BEST-KEPT SECRETSHidden Gems of Europe - DISCOVERING THE CONTINENT'S BEST-KEPT SECRETS
Hidden Gems of Europe - DISCOVERING THE CONTINENT'S BEST-KEPT SECRETS
 
How To Talk To a Live Person at American Airlines
How To Talk To a Live Person at American AirlinesHow To Talk To a Live Person at American Airlines
How To Talk To a Live Person at American Airlines
 
LUXURY TRAVEL THE ULTIMATE TOKYO EXPERIENCE FROM SINGAPORE.pdf
LUXURY TRAVEL THE ULTIMATE TOKYO EXPERIENCE FROM SINGAPORE.pdfLUXURY TRAVEL THE ULTIMATE TOKYO EXPERIENCE FROM SINGAPORE.pdf
LUXURY TRAVEL THE ULTIMATE TOKYO EXPERIENCE FROM SINGAPORE.pdf
 
Uk Visa Complete Guide and application process
Uk Visa Complete Guide and application processUk Visa Complete Guide and application process
Uk Visa Complete Guide and application process
 
Understanding the Running Costs of Electric Scooters.pptx
Understanding the Running Costs of Electric Scooters.pptxUnderstanding the Running Costs of Electric Scooters.pptx
Understanding the Running Costs of Electric Scooters.pptx
 
欧洲杯开户-信誉的欧洲杯开户-正规欧洲杯开户|【​网址​🎉ac123.net🎉​】
欧洲杯开户-信誉的欧洲杯开户-正规欧洲杯开户|【​网址​🎉ac123.net🎉​】欧洲杯开户-信誉的欧洲杯开户-正规欧洲杯开户|【​网址​🎉ac123.net🎉​】
欧洲杯开户-信誉的欧洲杯开户-正规欧洲杯开户|【​网址​🎉ac123.net🎉​】
 
The Power of a Glamping Go-To-Market Accelerator Plan.pptx
The Power of a Glamping Go-To-Market Accelerator Plan.pptxThe Power of a Glamping Go-To-Market Accelerator Plan.pptx
The Power of a Glamping Go-To-Market Accelerator Plan.pptx
 
Winter Festivities in Italy
Winter Festivities in ItalyWinter Festivities in Italy
Winter Festivities in Italy
 
Jose RIZAL History and his travel Paris to berlin
Jose RIZAL History and his travel Paris to berlinJose RIZAL History and his travel Paris to berlin
Jose RIZAL History and his travel Paris to berlin
 
4 DAYS MASAI MARA WILDEBEEST MIGRATION SAFARI TOUR PACKAGE KENYA
4 DAYS MASAI MARA WILDEBEEST MIGRATION SAFARI TOUR PACKAGE KENYA4 DAYS MASAI MARA WILDEBEEST MIGRATION SAFARI TOUR PACKAGE KENYA
4 DAYS MASAI MARA WILDEBEEST MIGRATION SAFARI TOUR PACKAGE KENYA
 
Antarctica- Icy wilderness of extremes and wonder
Antarctica- Icy wilderness of extremes and wonderAntarctica- Icy wilderness of extremes and wonder
Antarctica- Icy wilderness of extremes and wonder
 
Ooty Honeymoon Package from Chennai.docx
Ooty Honeymoon Package from Chennai.docxOoty Honeymoon Package from Chennai.docx
Ooty Honeymoon Package from Chennai.docx
 
Agence Régionale du Tourisme Grand Est - brochure MICE 2024.pdf
Agence Régionale du Tourisme Grand Est - brochure MICE 2024.pdfAgence Régionale du Tourisme Grand Est - brochure MICE 2024.pdf
Agence Régionale du Tourisme Grand Est - brochure MICE 2024.pdf
 
TOP 10 Historic Places To See in Kuruskhetra.
TOP 10 Historic Places To See in Kuruskhetra.TOP 10 Historic Places To See in Kuruskhetra.
TOP 10 Historic Places To See in Kuruskhetra.
 

Exploring type-directed, test-driven development: a case study using FizzBuzz

  • 1. Exploring type-directed, test-driven development A case study using FizzBuzz Franklin Chen http://franklinchen.com/ June 7, 2014 Pittsburgh TechFest 2014 Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 1 / 76
  • 2. Outline 1 Introduction 2 Original FizzBuzz problem 3 FizzBuzz 2: user configuration 4 FizzBuzz 3: FizzBuzzPop and beyond 5 Parallel FizzBuzz 6 Conclusion Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 2 / 76
  • 3. Outline 1 Introduction 2 Original FizzBuzz problem 3 FizzBuzz 2: user configuration 4 FizzBuzz 3: FizzBuzzPop and beyond 5 Parallel FizzBuzz 6 Conclusion Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 3 / 76
  • 4. Goals of this presentation Give a taste of a practical software development process that is: test-driven type-directed Show everything for real (using Scala): project build process testing frameworks all the code Use FizzBuzz because: problem: easy to understand modifications: easy to understand fun! Encourage you to explore a modern typed language This Monday, Apple ditched Objective C for its new language Swift! Complete Swift code will shown at the end; looks a lot like Scala. Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 4 / 76
  • 5. Test-driven development (TDD) Think. Write a test that fails. Write code until test succeeds. Repeat, and refactor as needed. Is TDD dead? Short answer: No. Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 5 / 76
  • 6. Type systems What is a type system? A syntactic method to prove that bad things can’t happen. “Debating” types “versus” tests? Let’s use both types and tests! But: use a good type system, not a bad one. Some decent practical typed languages OCaml: 20 years old Haskell: 20 years old Scala: 10 years old Swift: 5 days old Rust (still in progress) Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 6 / 76
  • 7. Outline 1 Introduction 2 Original FizzBuzz problem 3 FizzBuzz 2: user configuration 4 FizzBuzz 3: FizzBuzzPop and beyond 5 Parallel FizzBuzz 6 Conclusion Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 8 / 76
  • 8. Original FizzBuzz problem FizzBuzz defined Write a program that prints the numbers from 1 to 100. But for multiples of three, print “Fizz” instead of the number. And for the multiples of five, print “Buzz”. For numbers which are multiples of both three and five, print “FizzBuzz”. Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 9 / 76
  • 9. Starter code: main driver Scala: a modern object-oriented and functional language. object Main extends App { // Will not compile yet! runToSeq(1, 100).foreach(println) } Type-directed design: separate out effects (such as printing to terminal) from the real work. Type-directed feedback: compilation fails when something is not implemented yet. Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 10 / 76
  • 10. The joys of continuous compilation and testing SBT: build tool supporting Scala, Java. . . Winning features Source file changes trigger smart recompilation! Source file changes trigger rerun of the tests that depend on changed code! $ sbt > ~testQuick [info] Compiling 1 Scala source to ... [error] ...Main.scala:16: not found: value runToSeq [error] runToSeq(1, 100) foreach println [error] ^ [error] one error found Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 11 / 76
  • 11. Write type-directed stub object Main extends App { runToSeq(1, 100).foreach(println) def runToSeq(start: Int, end: Int): Seq[String] = { ??? } } Write wanted type signature ??? is convenient for stubbing. In Scala standard library Just performs: throw new NotImplementedError Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 12 / 76
  • 12. Write acceptance test (simplified) Specs2: a fine testing framework for Scala, Java. . . class MainSpec extends Specification { def is = s2""" ${Main.runToSeq(1, 16) ==== ‘strings for 1 to 16‘} """ val ‘strings for 1 to 16‘ = Seq( "1", "2", "Fizz", "4", "Buzz", "Fizz", "7", "8", "Fizz", "Buzz", "11", "Fizz", "13", "14", "FizzBuzz", "16" ) } Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 13 / 76
  • 13. Test passes type check, but fails Incremental compilation/testing kicks in: Waiting for source changes... (press enter to interrupt) [info] MainSpec [info] x Main.runToSeq(1, 16) ==== ‘strings for 1 to 16 [error] an implementation is missing (Main.scala:19) Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 15 / 76
  • 14. Outside-in: for a FizzBuzz unit Types are shapes to assemble logically. def runToSeq(start: Int, end: Int): Seq[String] = { start.to(end).map(FizzBuzz.evaluate) } start.to(end): Seq[Int], where Seq[_] is a type constructor that, given a type A, returns a type of Seq[A]. For any value of type Seq[A], map: (A => B) => Seq[B]. Therefore: need to implement function FizzBuzz.evaluate: Int => String. Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 16 / 76
  • 15. Implement new FizzBuzz module A failing acceptance test drives discovery of A unit, FizzBuzz A function with a particular type, Int => String object FizzBuzz { type Evaluator = Int => String val evaluate: Evaluator = { i => ??? } } Types are better than comments as documentation! Comments are not checkable, unlike types and tests. Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 18 / 76
  • 16. First part of unit test: example-based Manually write some examples. class FizzBuzzSpec extends Specification { def is = s2""" ${FizzBuzz.evaluate(15) ==== "FizzBuzz"} ${FizzBuzz.evaluate(20) ==== "Buzz"} ${FizzBuzz.evaluate(6) ==== "Fizz"} ${FizzBuzz.evaluate(17) ==== "17"} """ } Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 19 / 76
  • 17. The joy of property-based tests ScalaCheck: a framework for writing property-based tests. class FizzBuzzSpec extends Specification with ScalaCheck { def is = s2""" ${‘Multiple of both 3 and 5 => "FizzBuzz"‘} """ def ‘Multiple of both 3 and 5 => "FizzBuzz"‘ = prop { i: Int => (i % 3 == 0 && i % 5 == 0) ==> { FizzBuzz.evaluate(i) ==== "FizzBuzz" } } } Winning features Auto-generates random tests for each property (100 by default). Type-driven: here, generates random Int values. Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 20 / 76
  • 18. Property-based tests (continued) The other three properties of interest: def ‘Multiple of only 3 => "Fizz"‘ = prop { i: Int => (i % 3 == 0 && i % 5 != 0) ==> { FizzBuzz.evaluate(i) ==== "Fizz" } } def ‘Multiple of only 5 => "Buzz"‘ = prop { i: Int => (i % 3 != 0 && i % 5 == 0) ==> { FizzBuzz.evaluate(i) ==== "Buzz" } } def ‘Not a multiple of either 3 or 5 => number‘ = prop { i: Int => (i % 3 != 0 && i % 5 != 0) ==> { FizzBuzz.evaluate(i) ==== i.toString } } Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 22 / 76
  • 19. A buggy and ugly solution // Buggy and ugly! val evaluate: Evaluator = { i => if (i % 3 == 0) "Fizz" else if (i % 5 == 0) "Buzz" else if (i % 3 == 0 && i % 5 == 0) "FizzBuzz" else i.toString } [info] FizzBuzzSpec [info] x FizzBuzz.evaluate(15) ==== "FizzBuzz" [error] ’Fizz’ is not equal to ’FizzBuzz’ (FizzBuzzSpec.scala:14) Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 23 / 76
  • 20. Booleans are evil! Maze of twisty little conditionals, all different Too easy to write incorrect sequences of nested, combined conditionals. Overuse of Booleans is a type smell. Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 24 / 76
  • 21. Pattern matching organizes information val evaluate: Evaluator = { i => (i % 3 == 0, i % 5 == 0) match { case (true, false) => "Fizz" case (false, true) => "Buzz" case (true, true) => "FizzBuzz" case (false, false) => i.toString } } Winning features Visual beauty and clarity. No duplicated conditionals. No ordering dependency. Type checker verifies full coverage of cases. Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 26 / 76
  • 22. Example of non-exhaustive pattern matching val evaluate: Evaluator = { i => (i % 3 == 0, i % 5 == 0) match { case (true, false) => "Fizz" case (false, true) => "Buzz" case (true, true) => "FizzBuzz" // case (false, false) => ??? } } [warn] ...FizzBuzz.scala:46: match may not be exhaustive. [warn] It would fail on the following input: (false, false) [warn] (i % 3 == 0, i % 5 == 0) match { [warn] ^ Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 27 / 76
  • 23. Acceptance test passes [info] MainSpec [info] + Main.runToSeq(1, 16) ==== ‘strings for 1 to 16 Done? No. Client wants more features. Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 29 / 76
  • 24. Outline 1 Introduction 2 Original FizzBuzz problem 3 FizzBuzz 2: user configuration 4 FizzBuzz 3: FizzBuzzPop and beyond 5 Parallel FizzBuzz 6 Conclusion Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 30 / 76
  • 25. Adding new features Client wants to: Choose two arbitrary divisors in place of 3 and 5 such as 4 and 7 Choose other arbitrary words in place of "Fizz" and "Buzz" such as "Moo" and "Quack" Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 31 / 76
  • 26. Type-driven refactoring Types mean: refactoring is much more fun! Add new tests. Change types and code: to make new tests type check. Refactor original code and tests: use new APIs. Keep passing the old tests. Delay writing code for new features. Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 32 / 76
  • 27. More features means more types Change FizzBuzz.evaluate to Defaults.fizzBuzzer: def runToSeq(start: Int, end: Int): Seq[String] = { start.to(end).map(Defaults.fizzBuzzer) } Add new types to FizzBuzz module: type Evaluator = Int => String case class Config(pair1: (Int, String), pair2: (Int, String)) type Compiler = Config => Evaluator val compile: Compiler = { case Config((d1, w1), (d2, w2)) => { i => ??? } } Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 33 / 76
  • 28. Extract original default configuration object Defaults { val fizzBuzzerConfig: Config = Config(3 -> "Fizz", 5 -> "Buzz") val fizzBuzzer: Evaluator = FizzBuzz.compile(fizzBuzzerConfig) // Useful to keep old implementation val oldFizzBuzzer: Evaluator = { i => (i % 3 == 0, i % 5 == 0) match { case (true, false) => "Fizz" case (false, true) => "Buzz" case (true, true) => "FizzBuzz" case (false, false) => i.toString } } } Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 34 / 76
  • 29. More types means more tests Write new property-based test over arbitrary user configurations: val arbitraryConfig: Arbitrary[Config] = Arbitrary { for { (d1, d2, w1, w2) <- arbitrary[(Int, Int, String, String)] } yield Config(d1 -> w1, d2 -> w2) } def ‘Arbitrary pair of divisors: divisible by first‘ = arbitraryConfig { config: Config => val evaluate = FizzBuzz.compile(config) val Config((d1, w1), (d2, _)) = config prop { i: Int => (i % d1 == 0 && i % d2 != 0) ==> { evaluate(i) ==== w1 } } } Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 35 / 76
  • 30. Problem: coarse Config type [info] ! Arbitrary divisor/word pair fizzBuzzers [error] ArithmeticException: : A counter-example is ’Config((0,),(0,))’: java.lang.ArithmeticException: / by zero (after 0 try) (FizzBuzzSpec.scala:58) 0 as a divisor crashes! We discovered client’s underspecification. Client says: meant to allow only divisors within 2 and 100. We need to: Add runtime validation when constructing Config. Refine Config random generator. Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 36 / 76
  • 31. Add (runtime) validation Runtime precondition contract: Scala’s require (throws exception on failure): val DIVISOR_MIN = 2; val DIVISOR_MAX = 100 def validatePair(pair: (Int, String)) = pair match { case (d, _) => require(d >= DIVISOR_MIN, s"divisor $d must be >= $DIVISOR_MIN") require(d <= DIVISOR_MAX, s"divisor $d must be <= $DIVISOR_MAX") } case class Config(pair1: (Int, String), pair2: (Int, String)) { validatePair(pair1); validatePair(pair2) } Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 37 / 76
  • 32. A note on exceptions and types Exceptions are evil because they escape the type system. In real life, I prefer to use a principled type-based validation system, such as Scalaz. Note: Swift does not have exceptions, so expect some type-based libraries to emerge! Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 38 / 76
  • 33. Improve Config random generator val arbitraryConfig: Arbitrary[Config] = Arbitrary { for { d1 <- choose(DIVISOR_MIN, DIVISOR_MAX) d2 <- choose(DIVISOR_MIN, DIVISOR_MAX) w1 <- arbitrary[String] w2 <- arbitrary[String] } yield Config(d1 -> w1, d2 -> w2) } Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 40 / 76
  • 34. New test runs further, stills fails Refactor old code to FizzBuzz.compile, to pass old tests and new test. val compile: Compiler = { case Config((d1, w1), (d2, w2)) => // Precompute, hence "compiler". val w = w1 + w2 // Return an Evaluator. { i => (i % d1 == 0, i % d2 == 0) match { case (true, false) => w1 case (false, true) => w2 case (true, true) => w case (false, false) => i.toString } } } Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 41 / 76
  • 35. Outline 1 Introduction 2 Original FizzBuzz problem 3 FizzBuzz 2: user configuration 4 FizzBuzz 3: FizzBuzzPop and beyond 5 Parallel FizzBuzz 6 Conclusion Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 42 / 76
  • 36. Generalizing to more than two divisors Client wants FizzBuzzPop! Given three divisors (such as 3, 5, 7). Given three words (such as "Fizz", "Buzz", "Pop"). Example: 21 should output "FizzPop". Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 43 / 76
  • 37. More features means more tests Write new tests for new Defaults.fizzBuzzPopper: def is = s2""" ${Defaults.fizzBuzzPopper(2) ==== "2"} ${Defaults.fizzBuzzPopper(21) ==== "FizzPop"} ${Defaults.fizzBuzzPopper(9) ==== "Fizz"} ${Defaults.fizzBuzzPopper(7) ==== "Pop"} ${Defaults.fizzBuzzPopper(35) ==== "BuzzPop"} """ Change configuration: to Seq of pairs, instead of just two: val fizzBuzzPopperConfig: Config = Config(Seq( 3 -> "Fizz", 5 -> "Buzz", 7 -> "Pop" )) val fizzBuzzPopper: Evaluator = FizzBuzz.compile(fizzBuzzPopperConfig) Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 45 / 76
  • 38. More tests means more (or changed) types [error] ...Defaults.scala:29: not enough arguments for method apply: (pair1: (Int, String), pair2: (Int, String)) com.franklinchen.FizzBuzz.Config in object Config [error] Unspecified value parameter pair2. [error] Config(Seq( [error] ^ Change type Config to allow a sequence of pairs: case class Config(pairs: Seq[(Int, String)]) { pairs.foreach(validatePair) } Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 46 / 76
  • 39. Fix remaining type errors Refactoring reveals need to implement case of more than two divisors. val compile: Compiler = { case Config(Seq((d1, w1), (d2, w2))) => val w = w1 + w2 { i => (i % d1 == 0, i % d2 == 0) match { case (true, false) => w1 case (false, true) => w2 case (true, true) => w case (false, false) => i.toString } } case _ => // TODO handle more than 2 { i => ??? } } Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 47 / 76
  • 40. More computation means more types Compile each divisor to a “rule” that awaits input. type Rule = Int => String val buildRule: ((Int, String)) => Rule = { case (n, word) => { i => if (i % n == 0) word else "" } } FizzBuzz demo time! Two volunteers: each to play role of Rule. One “manager” to combine two sub-results. Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 49 / 76
  • 41. Assemble the types type Evaluator = Int => String type Compiler = Config => Evaluator type Rule = Int => String val compile: Compiler = { case Config(pairs) => val rules: Seq[Rule] = pairs.map(buildRule) // compile // Return an Evaluator. { i => val words: Seq[String] = rules.map { rule => rule(i) } val combined: String = words.reduce { (x, y) => x + y } if (combined == "") i.toString else combined } } Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 51 / 76
  • 42. A note on reduce For any value of type Seq[A], reduce: ((A, A) => B) => B. Example: for Seq[String], reduction with string concatenation + returns the concatenation of all the strings in the sequence. Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 52 / 76
  • 43. Test failure: coarse types again nfo] x Arbitrary pair of divisors: divisible by first rror] A counter-example is ’Config(List((8,), (32,)))’ (after 0 try) rror] A counter-example is ’32405464’ (after 0 try - shrinked (’1150076488’ -> ’32405464’)) rror] ’32405464’ is not equal to ’’ (FizzBuzzSpec.scala:71) Demo time! Configuration: Seq(3 -> "", 5 -> "Buzz") Input: 9 (note: divisible by 3) Output: should be "" (because of the part divisible by 3) Output was: "9" Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 53 / 76
  • 44. Assemble the types type Evaluator = Int => String type Compiler = Config => Evaluator type Rule = Int => String val compile: Compiler = { case Config(pairs) => val rules: Seq[Rule] = pairs.map(buildRule) // compile // Return an Evaluator. { i => val words: Seq[String] = rules.map { rule => rule(i) } val combined: String = words.reduce { (x, y) => x + y } if (combined == "") i.toString else combined } } Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 54 / 76
  • 45. Property-based testing rescued us again! Be honest: would you have caught this bug manually? I didn’t. I never wrote FizzBuzzPop examples testing empty strings. Property-based testing reveals unexpected corner cases. (Empty “fizz” and “buzz” word strings). Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 55 / 76
  • 46. An empty string is not equivalent to no string Presence of something “empty” is not equivalent to no thing. Sending someone an empty email versus not sending any email. Many programming languages get this wrong. Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 56 / 76
  • 47. Option[A] type Option[A] is one of two possibilities: None Some(a) wraps a value a of type A. For example, Some("") is not the same as None. val fizzFor3 = Some("") // multiple of 3 val buzzFor3 = None // not multiple of 5 val fizzbuzzFor3 = Some("") // fizzed "" val fizzFor2 = None // not multiple of 3 val buzzFor2 = None // not multiple of 5 val fizzbuzzFor2 = None // not multiple of any Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 57 / 76
  • 48. Cleaning up the types // was: type Rule = Int => String type Rule = Int => Option[String] Useful type errors: und : String quired: Option[String] word ^ und : String("") quired: Option[String] "" ^ und : Seq[Option[String]] quired: Seq[String] val words: Seq[String] = rules map { rule => rule(i) } ^ Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 58 / 76
  • 49. Fix the type errors: our rule builder type Rule = Int => Option[String] val buildRule: ((Int, String)) => Rule = { case (n, word) => { i => (i % n == 0).option(word) } } Demo time! (Instructions: for result Some(s), hold up the string, else don’t hold up anything) Configuration: Seq(3 -> "", 5 -> "Buzz") Input: 9 Output: now correctly is "" Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 59 / 76
  • 50. Fix the type errors: our compiler val compile: Compiler = { case Config(pairs) => val rules: Seq[Rule] = pairs map buildRule { i => val wordOptions: Seq[Option[String]] = rules.map { rule => rule(i) } val combinedOption: Option[String] = wordOptions.reduce(addOption) combinedOption.getOrElse(i.toString) } } We need to write: addOption Scala standard library provides: getOrElse Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 60 / 76
  • 51. “Addition” for Option[String] def addOption(a1: Option[String], a2: Option[String]): Option[String] = (a1, a2) match { case (Some(s1), None) => Some(s1) case (None, Some(s2)) => Some(s2) case (Some(s1), Some(s2)) => Some(s1 + s2) case (None, None) => None } Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 61 / 76
  • 52. Getting A back out of Option[A] Do not lose information! getOrElse inspects the and either returns the value v inside a Some(v), or else returns the specific default value. Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 62 / 76
  • 53. The same thing, in Swift func addOption(a1: String?, a2: String?) -> String? = { switch (a1, a2) { case (let .Some(s1), .None): return .Some(s1) case (.None, let .Some(s2)): return .Some(s2) case (let .Some(s1), let .Some(s2)): return .Some(s1+s2) case (.None, .None): return .None } } Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 64 / 76
  • 54. Transform information; don’t destroy it Did you notice? Our new code no longer uses if. Bug cause: destroying information, using if Building a rule: if (i % n == 0) word else "" Obtaining a final string answer: if (combined == "") i.toString else combined Transforming information To Option[String]: (i % n == 0).option(word) To String: combinedOption.getOrElse(i.toString) Type-directed design tip We could have saved trouble up front, by using precise types. Avoid if, when possible. Avoid String (but required at I/O boundaries of program). Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 65 / 76
  • 55. Bonus: the final code in Swift typealias Evaluator = Int -> String typealias Config = [(Int, String)] typealias Compiler = Config -> Evaluator typealias Rule = Int -> String? let buildRule: ((Int, String)) -> Rule = { n, word in { i in return (i % n == 0) ? word : nil } } } let compile: Compiler = { pairs in let rules: [Rule] = pairs.map(buildRule) return { i in let wordOptions = rules.map { rule in rule(i) } let combinedOption = wordOptions.reduce(nil, addOption) if let combined = combinedOption { return combined } else { return String(i) } } }Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 66 / 76
  • 56. Outline 1 Introduction 2 Original FizzBuzz problem 3 FizzBuzz 2: user configuration 4 FizzBuzz 3: FizzBuzzPop and beyond 5 Parallel FizzBuzz 6 Conclusion Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 67 / 76
  • 57. Parallelism Use of map: parallelizable; there are high-performance parallel collections for Scala. Use of reduce: parallelizable because of the monoid property: Option[String] is a Monoid There is an identity element (None). There is a binary associative operator (addOption). Fantastically important in practice! Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 68 / 76
  • 58. Final demo! Demo time! Configuration: Seq(3 -> "Fizz", 5 -> "Buzz", 7 -> "Pop", 2 -> "Boom") Tree of volunteers to simulate concurrency: Four at leaves. Two “middle managers” each handling two leaves. One top-level manager handling two middle managers. Input: 42 Output: "FizzPopBoom" Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 69 / 76
  • 59. Final parallelized code val parallelCompile: Compiler = { case Config(pairs) => val rules = pairs. toArray. toPar. map(buildRule) { i: Int => rules. map { rule => rule(i) }. reduce(addOption). getOrElse(i.toString) } } Coding style tip This level of conciseness is not always best: maybe too “clever”? Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 70 / 76
  • 60. Parallelism summary We discovered a theoretical speedup for generalized FizzBuzz: Sequential: O(n) Parallel: O(log n) (given log n processors, and omitting some technical subtleties) Also, driver outer loop can be sped up: Sequential loop on 1 to m: O(m) Parallel loop: O(1) (given m processors) Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 72 / 76
  • 61. Future work Asynchronous Real-time Interactive Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 73 / 76
  • 62. Outline 1 Introduction 2 Original FizzBuzz problem 3 FizzBuzz 2: user configuration 4 FizzBuzz 3: FizzBuzzPop and beyond 5 Parallel FizzBuzz 6 Conclusion Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 74 / 76
  • 63. Conclusion Tests are great. Types are great. Tests and types work hand in hand, driving design and program evolution. Modern typed languages such as Scala promote fun, correct programming! It’s a great time to be learning and using a modern typed language: Apple ditched Objective C for Swift. Code, slides, article Go to https://github.com/franklinchen/ talk-on-type-directed-tdd-using-fizzbuzz The article has more detail omitted in the presentation. The hyperlinks in all provided PDFs are clickable. The Swift code: https://github.com/franklinchen/fizzbuzz-swift Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 75 / 76
  • 64. Some free online courses on modern typed functional programming Using Scala: Functional Programming Principles in Scala on Coursera, taught by Martin Odersky (inventor of Scala) Using Haskell: Introduction to Functional Programming on EdX, taught by Erik Meijer (Haskell hero) Franklin Chen http://franklinchen.com/ Exploring type-directed TDD w/FizzBuzz Pittsburgh TechFest 2014 76 / 76