Presentation Simplicity in Scala Design

This talk will focus on how to achieve simplicity in Scala library, DSL, and application design. It will highlight general principles that can be applied to any programming language, and show specific techniques that can be used in Scala to "implement" the general principles. This talk will give you a set of concrete guidelines that can help you manage complexity in your Scala projects.

Speakers


PDF: slides.pdf

Slides

Simplicity in Scala Design

Simplicity in Scala Design Bill Venners President Artima, Inc. @bvenners

Bill Venners

Bill Venners · Programming in Scala (book) · ScalaTest (test framework) · Escalate Software (training and consulting) http://www.computermanuals.co.uk/

Design to simplify tasks for your users.

Design to simplify tasks for your users.

Design for busy teams.

Design for busy teams.

21 should be (20 plusOrMinus 5)

21 should be (20 plusOrMinus 5) 21 should equal (20 plusOrMinus 5)

Demo

Make it obvious, guessable, or easy to remember.

Make it obvious, guessable, or easy to remember.

21 should be (20 plusOrMinus 5)

21 should be (20 plusOrMinus 5) 21 should equal (20 plusOrMinus 5)

Make it obvious, guessable, or easy to remember.

Make it obvious, guessable, or easy to remember.

a) 21 should be (20 plusOrMinus 5) b) 21 should be (20 +/- 5) c) 21 should be (20 +- 5) d) 21 should be (20 ± 5) e) 21 should be (20 ? 5)

a) 21 should be (20 plusOrMinus 5) b) 21 should be (20 +/- 5) c) 21 should be (20 +- 5) d) 21 should be (20 ± 5) e) 21 should be (20 ? 5)

Design for readers, then writers.

Design for readers, then writers.

a) 21 should be (20 plusOrMinus 5) b) 21 should be (20 +/- 5) c) 21 should be (20 +- 5) d) 21 should be (20 ± 5) e) 21 should be (20 ? 5)

a) 21 should be (20 plusOrMinus 5) b) 21 should be (20 +/- 5) c) 21 should be (20 +- 5) d) 21 should be (20 ± 5) e) 21 should be (20 ? 5)

a) obj invokePrivate decorate(1) b) obj~~>decorate(1) c) obj priv decorate(1)

a) obj invokePrivate decorate(1) b) obj~~>decorate(1) c) obj priv decorate(1)

Make errors impossible or difficult.

Make errors impossible or difficult.

class MutaSpec extends Spec with ParallelTestExecution { // ... }

class MutaSpec extends Spec with ParallelTestExecution { // ... }

Exploit familiarity.

Exploit familiarity.

class SetSuite extends FunSuite { test("An empty set should have size 0") { assert(Set.empty.size === 0) } test("Invoking head on an empty Set should" + "produce NoSuchElementException") { intercept[NoSuchElementException] { Set.empty.head } } }

class SetSuite extends FunSuite { test("An empty set should have size 0") { assert(Set.empty.size === 0) } test("Invoking head on an empty Set should" + "produce NoSuchElementException") { intercept[NoSuchElementException] { Set.empty.head } } }

def endWithExtension(ext: String) = endWith(ext) compose { (f: File) => f.getPath } val examples = Table( ("n", "d"), ( 1, 2), ( -1, 2), ( 1, -2) ) examples foreach println

def endWithExtension(ext: String) = endWith(ext) compose { (f: File) => f.getPath } val examples = Table( ("n", "d"), ( 1, 2), ( -1, 2), ( 1, -2) ) examples foreach println

Document with examples.

Document with examples.

Demo

Minimize redundancy. Minimize redundancy.

Minimize redundancy. Minimize redundancy.

Demo

Demo

Recommended Usage: For teams coming from xUnit, FunSuite feels comfortable and familiar while still giving some benefits of BDD: FunSuite makes it easy to write descriptive test names, natural to write focused tests, and generates specification-like outp

Recommended Usage: For teams coming from xUnit, FunSuite feels comfortable and familiar while still giving some benefits of BDD: FunSuite makes it easy to write descriptive test names, natural to write focused tests, and generates specification-like output that can facilitate communication among stakeholders.

Demo

Maximize consistency.

Maximize consistency.

class SetSuite extends FunSuite with GivenWhenThen { test("An element can be added to an empty mutable Set") { Given("an empty mutable Set") val set = mutable.Set.empty[String] When("an element is added") set += "clarity" Then("the set should have size 1

class SetSuite extends FunSuite with GivenWhenThen { test("An element can be added to an empty mutable Set") { Given("an empty mutable Set") val set = mutable.Set.empty[String] When("an element is added") set += "clarity" Then("the set should have size 1") assert(set.size === 1) And("the set should contain the added element") assert(set.contains("clarity")) } }

class SetSuite extends Suite with GivenWhenThen { def testCanAddAnnElementToAnEmptyMutableSet(implicit info: Informer) { Given("an empty mutable Set") val set = mutable.Set.empty[String] When("an element is added") set += "clarity" Then("the set should h

class SetSuite extends Suite with GivenWhenThen { def testCanAddAnnElementToAnEmptyMutableSet(implicit info: Informer) { Given("an empty mutable Set") val set = mutable.Set.empty[String] When("an element is added") set += "clarity" Then("the set should have size 1") assert(set.size === 1) And("the set should contain the added element") assert(set.contains("clarity")) } }

class SetSpec extends Spec with GivenWhenThen { def `An element can be added to an empty mutable Set` { Given("an empty mutable Set") val set = mutable.Set.empty[String] When("an element is added") set += "clarity" Then("the set should have size 1") asse

class SetSpec extends Spec with GivenWhenThen { def `An element can be added to an empty mutable Set` { Given("an empty mutable Set") val set = mutable.Set.empty[String] When("an element is added") set += "clarity" Then("the set should have size 1") assert(set.size === 1) And("the set should contain the added element") assert(set.contains("clarity")) } }

Set means scala.collection.immutable.Set Map means scala.collection.immutable.Map Seq means scala.collection.Seq

Set means scala.collection.immutable.Set Map means scala.collection.immutable.Map Seq means scala.collection.Seq

Demo

Set means scala.collection.immutable.Set Map means scala.collection.immutable.Map Seq means scala.collection.Seq

Set means scala.collection.immutable.Set Map means scala.collection.immutable.Map Seq means scala.collection.Seq

Use symbols when your users are already experts in them.

Use symbols when your users are already experts in them.

(a multiply a) add (b multiply b)

(a multiply a) add (b multiply b) (a * a) + (b * b)

class StackDashSpec extends Spec with SpecDasher { "A Stack" -- { "should pop values in last-in-first-out order" - { val stack = new Stack[Int] stack.push(1) stack.push(2) assert(stack.pop() === 2) assert(stack.pop() === 1) } "should throw NoSuchElementE

class StackDashSpec extends Spec with SpecDasher { "A Stack" -- { "should pop values in last-in-first-out order" - { val stack = new Stack[Int] stack.push(1) stack.push(2) assert(stack.pop() === 2) assert(stack.pop() === 1) } "should throw NoSuchElementException if an empty stack is popped" - { val emptyStack = new Stack[String] intercept[NoSuchElementException] { emptyStack.pop() } } } }

class StackDashSpec extends FreeSpec { "A Stack" - { "should pop values in last-in-first-out order" in { val stack = new Stack[Int] stack.push(1) stack.push(2) assert(stack.pop() === 2) assert(stack.pop() === 1) } "should throw NoSuchElementException if

class StackDashSpec extends FreeSpec { "A Stack" - { "should pop values in last-in-first-out order" in { val stack = new Stack[Int] stack.push(1) stack.push(2) assert(stack.pop() === 2) assert(stack.pop() === 1) } "should throw NoSuchElementException if an empty stack is popped" in { val emptyStack = new Stack[String] intercept[NoSuchElementException] { emptyStack.pop() } } } }

class StackDashSpec extends Spec with SpecDasher { "A Stack" -- { "should pop values in last-in-first-out order" - { val stack = new Stack[Int] stack.push(1) stack.push(2) assert(stack.pop() === 2) assert(stack.pop() === 1) } "should throw NoSuchElementE

class StackDashSpec extends Spec with SpecDasher { "A Stack" -- { "should pop values in last-in-first-out order" - { val stack = new Stack[Int] stack.push(1) stack.push(2) assert(stack.pop() === 2) assert(stack.pop() === 1) } "should throw NoSuchElementException if an empty stack is popped" - { val emptyStack = new Stack[String] intercept[NoSuchElementException] { emptyStack.pop() } } } }

name := "Dude!" libraryDependencies += "org.scalatest" %% "scalatest" % "2.0.M5" % "test" sourceDirectories in Compile ++= Seq(file("sources1"), file("sources2")) Problem symbols: ~=, <<=, <+=, <<++=

name := "Dude!" libraryDependencies += "org.scalatest" %% "scalatest" % "2.0.M5" % "test" sourceDirectories in Compile ++= Seq(file("sources1"), file("sources2")) Problem symbols: ~=, <<=, <+=, <<++=

(0 /: xs)(_ + _) xs.foldLeft(0)(_ + _)

(0 /: xs)(_ + _) xs.foldLeft(0)(_ + _)

(0 /: xs)(_ + _) xs.foldLeft(0)(_ + _) (a * a) + (b * b) (a multiply a) add (b multiply b)

(0 /: xs)(_ + _) xs.foldLeft(0)(_ + _) (a * a) + (b * b) (a multiply a) add (b multiply b)

Use a functional style by default, an imperative style where it improves usability.

Use a functional style by default, an imperative style where it improves usability.

class MathSuite extends FunSuite { test("addition") { assert(1 + 1 === 2) } test("subtraction") { assert(1 - 1 === 0) } test("multiplication") { assert(1 * 1 === 1) } test("division") { assert(1 / 1 === 1) } }

class MathSuite extends FunSuite { test("addition") { assert(1 + 1 === 2) } test("subtraction") { assert(1 - 1 === 0) } test("multiplication") { assert(1 * 1 === 1) } test("division") { assert(1 / 1 === 1) } }

class MathSuite extends FunSuite { val tests = test("addition") { assert(1 + 1 === 2) } ^ test("subtraction") { assert(1 - 1 === 0) } ^ test("multiplication") { assert(1 * 1 === 1) } ^ test("division") { assert(1 / 1 === 1) } }

class MathSuite extends FunSuite { val tests = test("addition") { assert(1 + 1 === 2) } ^ test("subtraction") { assert(1 - 1 === 0) } ^ test("multiplication") { assert(1 * 1 === 1) } ^ test("division") { assert(1 / 1 === 1) } }

class MathSuite extends FunSuite { val tests = test("addition") { assert(1 + 1 === 2) } :: test("subtraction") { assert(1 - 1 === 0) } :: test("multiplication") { assert(1 * 1 === 1) } :: test("division") { assert(1 / 1 === 1) } :: Nil }

class MathSuite extends FunSuite { val tests = test("addition") { assert(1 + 1 === 2) } :: test("subtraction") { assert(1 - 1 === 0) } :: test("multiplication") { assert(1 * 1 === 1) } :: test("division") { assert(1 / 1 === 1) } :: Nil }

class MathSuite extends FunSuite { val tests = test("addition") { assert(1 + 1 === 2) } ^ test("subtraction") { assert(1 - 1 === 0) } ^ test("multiplication") { assert(1 * 1 === 1) } ^ test("division") { assert(1 / 1 === 1) } }

class MathSuite extends FunSuite { val tests = test("addition") { assert(1 + 1 === 2) } ^ test("subtraction") { assert(1 - 1 === 0) } ^ test("multiplication") { assert(1 * 1 === 1) } ^ test("division") { assert(1 / 1 === 1) } }

class MathSuite extends FunSuite { val tests = test("addition") { assert(1 + 1 === 2) } :: test("subtraction") { assert(1 - 1 === 0) } :: test("multiplication") { assert(1 * 1 === 1) } :: test("division") { assert(1 / 1 === 1) } :: Nil }

class MathSuite extends FunSuite { val tests = test("addition") { assert(1 + 1 === 2) } :: test("subtraction") { assert(1 - 1 === 0) } :: test("multiplication") { assert(1 * 1 === 1) } :: test("division") { assert(1 / 1 === 1) } :: Nil }

class MathSuite extends FunSuite { val tests = List( test("addition") { assert(1 + 1 === 2) }, test("subtraction") { assert(1 - 1 === 0) }, test("multiplication") { assert(1 * 1 === 1) }, test("division") { assert(1 / 1 === 1) } ) }

class MathSuite extends FunSuite { val tests = List( test("addition") { assert(1 + 1 === 2) }, test("subtraction") { assert(1 - 1 === 0) }, test("multiplication") { assert(1 * 1 === 1) }, test("division") { assert(1 / 1 === 1) } ) }

class MathSuite extends FunSuite( test("addition") { assert(1 + 1 === 2) }, test("subtraction") { assert(1 - 1 === 0) }, test("multiplication") { assert(1 * 1 === 1) }, test("division") { assert(1 / 1 === 1) } )

class MathSuite extends FunSuite( test("addition") { assert(1 + 1 === 2) }, test("subtraction") { assert(1 - 1 === 0) }, test("multiplication") { assert(1 * 1 === 1) }, test("division") { assert(1 / 1 === 1) } )

class MathSuite extends FunSuite { test("addition") { assert(1 + 1 === 2) } test("subtraction") { assert(1 - 1 === 0) } test("multiplication") { assert(1 * 1 === 1) } test("division") { assert(1 / 1 === 1) } }

class MathSuite extends FunSuite { test("addition") { assert(1 + 1 === 2) } test("subtraction") { assert(1 - 1 === 0) } test("multiplication") { assert(1 * 1 === 1) } test("division") { assert(1 / 1 === 1) } } class MathSuite extends FunSuite { val tests = test("addition") { assert(1 + 1 === 2) } :: test("subtraction") { assert(1 - 1 === 0) } :: test("multiplication") { assert(1 * 1 === 1) } :: test("division") { assert(1 / 1 === 1) } :: Nil }

Functional style code can be easier to reason about than imperative style code.

Functional style code can be easier to reason about than imperative style code.

println("Hello, world!")

println("Hello, world!")

import collection.mutable.ListBuffer val buf = ListBuffer.empty[Int] for (i <- 1 to 3) buf += i buf foreach println

import collection.mutable.ListBuffer val buf = ListBuffer.empty[Int] for (i <- 1 to 3) buf += i buf foreach println

class MathSuite extends FunSuite { test("addition") { assert(1 + 1 === 2) } test("subtraction") { assert(1 - 1 === 0) } test("multiplication") { assert(1 * 1 === 1) } test("division") { assert(1 / 1 === 1) } }

class MathSuite extends FunSuite { test("addition") { assert(1 + 1 === 2) } test("subtraction") { assert(1 - 1 === 0) } test("multiplication") { assert(1 * 1 === 1) } test("division") { assert(1 / 1 === 1) } }

Remember what tools are for. They are for solving problems, not finding problems to solve. - Terry Wall

Remember what tools are for. They are for solving problems, not finding problems to solve. - Terry Wall

http://www.computermanuals.co.uk/

http://www.computermanuals.co.uk/ Q&A