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.
Published on: 2012-12-05T12:44:18.000Z
Channel: Devoxx 2012 (all)
Tags: design scala
Speakers:
Bill Venners
Bill Venners is president of Artima Software, Inc. and editor-in- chief of Artima Developer. He is author of the book, Inside the Java Virtual Machine, a programmer-oriented survey of the Java platforms architecture and internals. His popular columns in JavaWorld magazine covered Java internals, object-oriented design, and Jini. Bill has been active in the Jini Community since its inception. He led the Jini Community ServiceUI project, whose ServiceUI API became the de facto standard way to associate user interfaces to Jini services.
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