Analysing Scala Puzzlers:
Essential and Accidental
Complexity in Scala
Andrew Phillips & Nermin Serifovic
@ScalaPuzzlers
About us
Andrew
● Lots of enterprise software
development on high-
performance systems
● Active open source contributor
and committer
Nermin
Co-organizer of Boston Area Scala
Enthusiasts
Co-instructor of Concurrent
Programming in Scala
Maintainers of scalapuzzlers.com and
authors of Scala Puzzlers
Agenda
Introduction
Puzzler Clusters
Reflections & Conclusions
Introduction
What are we trying to do here?
● We’ve been collecting examples of “surprising”
Scala code for a couple of years now
● We thought it was time to see if we could
identify some patterns
● Looking at the results, we tried to ask
ourselves: is the “puzzler cost” of a particular
language area outweighed by the “feature
benefit”
What are we trying to do here?
The clusters, based on 46 puzzlers:
● Cluster 1: Object-orientation
● Cluster 2: Collections
● Cluster 3: Syntax sugar
● Cluster 4: The type system
●Cluster 5: Functional programming
Cluster 1: Object-
orientation
15 puzzlers
What’s the issue?
Scala aims to combine functional and object-
oriented features...and play nicely with Java on
top of that.
Gotcha: Java peeking in
def value: Int = {
def one(x: Int): Int = { return x; 1 }
val two = (x: Int) => { return x; 2 }
1 + one(2) + two(3)
}
println(value)
Gotcha: Initialization in subclasses
class A {
type X // equivalent to X <: Any
var x: X = _
}
class B extends A { type X = Int }
val b = new B
println(b.x)
val bX = b.x
println(bX)
Gotcha: Constructors
object HipNTrendyMarket extends App { // now extends App!
implicit val normalMarkup = new Markup
Console println makeLabel(3)
implicit val touristMarkup = new TouristMarkup
Console println makeLabel(5)
}
object OldSkoolMarket {
def main(args: Array[String]): Unit = {
implicit val normalMarkup = new Markup
Console println makeLabel(3)
implicit val touristMarkup = new TouristMarkup
Console println makeLabel(5)
}
}
Moral of the story
Try to prevent non-idiomatic elements from
seeping though Scala/Java boundaries in
your code
If you’re using a lot of inheritance, study the
rules for variable initialization
Class body != method body
Cluster 2: Collections
9 puzzlers
Collections are powerful...and sometimes
puzzling
What’s the issue?
Gotcha: collections are functions!
Most commonly used collections are instances
of Function1:
List: index => element
Set: element => Boolean
Map: key => value
Gotcha: collections are functions!
This comes handy in many situations, such as:
val daysOfWeek = Map("Mon" -> 1, "Tue" -> 2, "Wed" -> 3,
"Thu" -> 4, "Fri" -> 5, "Sat" -> 6, "Sun" -> 7)
def foo(day: String, daysOfWeek: String => Int) =
println(s"${day} is the ${daysOfWeek(day)}. day of the week")
scala> foo("Mon", daysOfWeek)
Mon is the 1. day of the week
Gotcha: collections are functions!
Sometimes, this produces undesired effects...
def pad2(sb: StringBuilder, width: Int) = {
1 to width - sb.length foreach { sb append '*' }
sb
}
Gotcha: collections are functions!
Sometimes, this produces undesired effects...
def pad2(sb: StringBuilder, width: Int) = {
1 to width - sb.length foreach { sb append '*' }
sb
}
// 1 to (width - sb.length) foreach (_ => sb append '*')
Gotcha: convenience (?) methods
val ints = Map(1 -> List(1, 2, 3, 4, 5))
val bits = ints map { case (k, v) => (k, v.toIterator) }
val nits = ints mapValues (_.toIterator)
scala> print(bits(1).next, bits(1).next)
(1,2)
scala> print(nits(1).next, nits(1).next)
(1,1)
// keys are mapped to key => this(key).toIterator
Gotcha: convenience (?) methods
import collection.mutable.Queue
val goodies: Map[String, Queue[String]] = Map().withDefault(_ =>
Queue("No superheros here. Keep looking."))
val baddies: Map[String, Queue[String]] =
Map().withDefaultValue(Queue("No monsters here. Lucky you."))
println(goodies("kitchen").dequeue)
println(baddies("in attic").dequeue)
println(goodies("dining room").dequeue)
println(baddies("under bed").dequeue)
Moral of the story
Scala collections are powerful
Can be passed where Function1 is expected,
which is a useful feature, but also one to be
careful about
Pay special attention when dealing with
convenience methods that appear similar -
not all of them behave intuitively
Cluster 3:
Syntax sugar
11 puzzlers
Scala promotes elegant, concise coding style.
At the same time, the spec tries to remove
complexity from the compilation process early.
This results in a lot of rewriting of expressions.
What’s the issue?
Gotcha: placeh_lders
List(1, 2).map { i => println("Hi"); i + 1 }
List(1, 2).map { println("Hi"); _ + 1 }
Gotcha: for expressions
val xs = Seq(Seq("a", "b", "c"), Seq("d", "e", "f"), Seq("g",
"h"), Seq("i", "j", "k"))
val ys = for (Seq(x, y, z) <- xs) yield x + y + z
val zs = xs map { case Seq(x, y, z) => x + y + z }
Gotcha: implicit magic
case class Card(number: Int, suit: String = "clubs") {
val value = (number % 13) + 1 // ace = 1, king = 13
def isInDeck(implicit deck: List[Card]) = deck contains this
}
implicit val deck = List(Card(1, "clubs"))
implicit def intToCard(n: Int) = Card(n)
println(1.isInDeck)
Moral of the story
Beware when the syntax sugar for similar but
different things looks almost identical
Learn the details of more complex
desugarings, e.g. how for expressions are
translated
There are too many ways to do (almost) the
same thing with implicits - stick to one style
Cluster 4:
The Type System
7 puzzlers
By design, the Scala compiler tries to be
tolerant and “make the most of” an expression
by slightly transforming it in various ways.
In lots of situations, this goes against
programmer’s intentions
What’s the issue?
Gotcha: Type widening
val zippedLists = (List(1,3,5), List(2,4,6)).zipped
val result = zippedLists.find(_._1 > 10).getOrElse(10)
result: Any = 10
def List.find(p: (A) ⇒ Boolean): Option[A]
def Option.getOrElse[B >: A](default: ⇒ B): B
Type B is inferred to be Any - the least common
supertype between Tuple2 and Int
Gotcha: Type widening
For explicitly defined type hierarchies, this works as
intended:
scala> trait Animal
scala> class Dog extends Animal
scala> class Cat extends Animal
scala> val dog = Option(new Dog())
scala> val cat = Option(new Cat())
scala> dog.orElse(cat)
res0: Option[Animal] = Some(Dog@7d8995e)
Gotcha: Auto-tupling
def foo(any: Any) = println(any)
foo(“a”, “b”, “c”)
(a, b, c)
The compiler tries to pack all arguments into a
tuple and applies foo to that. This is the last
thing which is tried (after default arguments).
Gotcha: Type adaption galore
val numbers = List("1", "2").toSet() + "3"
println(numbers)
false3
def List.toSet[B >: A]: Set[B]
List("1", "2").toSet.apply() // apply() == contains()
Compiler implicitly inserts Unit value () and infers
supertype Any:
List("1", "2").toSet[Any].apply(())
Gotcha: Type adaption galore
●Not desirable in general
●Too much “magic” involved
●Automatic () insertion deprecated in 2.11
Moral of the story
We often wish the compiler was more strict
and threw an error in such cases
We should take advantage of static analysis
tools available (WartRemover, compiler
flags, linters, etc.)
Extensive unit testing of statically typed code
is still necessary
Moral of the story
Take advantage of the compiler flags:
-deprecation,
-unchecked,
-feature,
-Xfatal-warnings,
-Xlint
Cluster 5:
Functional programming
5 puzzlers
What’s the issue?
There are lots of ways you can specify and call
functions...
Gotcha: Partial application
var x = 0
def counter = { x += 1; x }
def add(a: Int)(b: Int) = a + b
val adder1 = add(counter)(_)
val adder2 = add(counter) _
println("x = " + x)
println(adder1(10))
println("x = " + x)
println(adder2(10))
println("x = " + x)
Gotcha: Native function syntax
val isEven = PartialFunction[Int, String] {
case n if n % 2 == 0 => "Even"
}
Moral of the story
There are many flavours of partial application
that do slightly different things - try to stick to
a few
If you are going to use native function syntax,
ensure you know exactly what you’re
creating
Reflections &
Conclusions
●Java interoperability comes at a high cost
●It pays to study the rules of common
desugarings closely
●Implicits up the level of “magic” in your code
quite significantly
●Read the ScalaDoc carefully
●Don’t skimp on unit testing
TL;DR: agree on :
● Scala features you really need; use linters,
code review etc. to catch the others
●common code style in areas where the
language allows multiple options
Questions?
Thank you!
Be the one to submit the next puzzler at
scalapuzzlers.com!

Scala Up North: "Analysing Scala Puzzlers: Essential and Accidental Complexity in Scala"

  • 1.
    Analysing Scala Puzzlers: Essentialand Accidental Complexity in Scala Andrew Phillips & Nermin Serifovic @ScalaPuzzlers
  • 2.
    About us Andrew ● Lotsof enterprise software development on high- performance systems ● Active open source contributor and committer Nermin Co-organizer of Boston Area Scala Enthusiasts Co-instructor of Concurrent Programming in Scala Maintainers of scalapuzzlers.com and authors of Scala Puzzlers
  • 3.
  • 4.
  • 5.
    What are wetrying to do here? ● We’ve been collecting examples of “surprising” Scala code for a couple of years now ● We thought it was time to see if we could identify some patterns ● Looking at the results, we tried to ask ourselves: is the “puzzler cost” of a particular language area outweighed by the “feature benefit”
  • 6.
    What are wetrying to do here? The clusters, based on 46 puzzlers: ● Cluster 1: Object-orientation ● Cluster 2: Collections ● Cluster 3: Syntax sugar ● Cluster 4: The type system ●Cluster 5: Functional programming
  • 7.
  • 8.
    What’s the issue? Scalaaims to combine functional and object- oriented features...and play nicely with Java on top of that.
  • 9.
    Gotcha: Java peekingin def value: Int = { def one(x: Int): Int = { return x; 1 } val two = (x: Int) => { return x; 2 } 1 + one(2) + two(3) } println(value)
  • 10.
    Gotcha: Initialization insubclasses class A { type X // equivalent to X <: Any var x: X = _ } class B extends A { type X = Int } val b = new B println(b.x) val bX = b.x println(bX)
  • 11.
    Gotcha: Constructors object HipNTrendyMarketextends App { // now extends App! implicit val normalMarkup = new Markup Console println makeLabel(3) implicit val touristMarkup = new TouristMarkup Console println makeLabel(5) } object OldSkoolMarket { def main(args: Array[String]): Unit = { implicit val normalMarkup = new Markup Console println makeLabel(3) implicit val touristMarkup = new TouristMarkup Console println makeLabel(5) } }
  • 12.
    Moral of thestory Try to prevent non-idiomatic elements from seeping though Scala/Java boundaries in your code If you’re using a lot of inheritance, study the rules for variable initialization Class body != method body
  • 13.
  • 14.
    Collections are powerful...andsometimes puzzling What’s the issue?
  • 15.
    Gotcha: collections arefunctions! Most commonly used collections are instances of Function1: List: index => element Set: element => Boolean Map: key => value
  • 16.
    Gotcha: collections arefunctions! This comes handy in many situations, such as: val daysOfWeek = Map("Mon" -> 1, "Tue" -> 2, "Wed" -> 3, "Thu" -> 4, "Fri" -> 5, "Sat" -> 6, "Sun" -> 7) def foo(day: String, daysOfWeek: String => Int) = println(s"${day} is the ${daysOfWeek(day)}. day of the week") scala> foo("Mon", daysOfWeek) Mon is the 1. day of the week
  • 17.
    Gotcha: collections arefunctions! Sometimes, this produces undesired effects... def pad2(sb: StringBuilder, width: Int) = { 1 to width - sb.length foreach { sb append '*' } sb }
  • 18.
    Gotcha: collections arefunctions! Sometimes, this produces undesired effects... def pad2(sb: StringBuilder, width: Int) = { 1 to width - sb.length foreach { sb append '*' } sb } // 1 to (width - sb.length) foreach (_ => sb append '*')
  • 19.
    Gotcha: convenience (?)methods val ints = Map(1 -> List(1, 2, 3, 4, 5)) val bits = ints map { case (k, v) => (k, v.toIterator) } val nits = ints mapValues (_.toIterator) scala> print(bits(1).next, bits(1).next) (1,2) scala> print(nits(1).next, nits(1).next) (1,1) // keys are mapped to key => this(key).toIterator
  • 20.
    Gotcha: convenience (?)methods import collection.mutable.Queue val goodies: Map[String, Queue[String]] = Map().withDefault(_ => Queue("No superheros here. Keep looking.")) val baddies: Map[String, Queue[String]] = Map().withDefaultValue(Queue("No monsters here. Lucky you.")) println(goodies("kitchen").dequeue) println(baddies("in attic").dequeue) println(goodies("dining room").dequeue) println(baddies("under bed").dequeue)
  • 21.
    Moral of thestory Scala collections are powerful Can be passed where Function1 is expected, which is a useful feature, but also one to be careful about Pay special attention when dealing with convenience methods that appear similar - not all of them behave intuitively
  • 22.
  • 23.
    Scala promotes elegant,concise coding style. At the same time, the spec tries to remove complexity from the compilation process early. This results in a lot of rewriting of expressions. What’s the issue?
  • 24.
    Gotcha: placeh_lders List(1, 2).map{ i => println("Hi"); i + 1 } List(1, 2).map { println("Hi"); _ + 1 }
  • 25.
    Gotcha: for expressions valxs = Seq(Seq("a", "b", "c"), Seq("d", "e", "f"), Seq("g", "h"), Seq("i", "j", "k")) val ys = for (Seq(x, y, z) <- xs) yield x + y + z val zs = xs map { case Seq(x, y, z) => x + y + z }
  • 26.
    Gotcha: implicit magic caseclass Card(number: Int, suit: String = "clubs") { val value = (number % 13) + 1 // ace = 1, king = 13 def isInDeck(implicit deck: List[Card]) = deck contains this } implicit val deck = List(Card(1, "clubs")) implicit def intToCard(n: Int) = Card(n) println(1.isInDeck)
  • 27.
    Moral of thestory Beware when the syntax sugar for similar but different things looks almost identical Learn the details of more complex desugarings, e.g. how for expressions are translated There are too many ways to do (almost) the same thing with implicits - stick to one style
  • 28.
    Cluster 4: The TypeSystem 7 puzzlers
  • 29.
    By design, theScala compiler tries to be tolerant and “make the most of” an expression by slightly transforming it in various ways. In lots of situations, this goes against programmer’s intentions What’s the issue?
  • 30.
    Gotcha: Type widening valzippedLists = (List(1,3,5), List(2,4,6)).zipped val result = zippedLists.find(_._1 > 10).getOrElse(10) result: Any = 10 def List.find(p: (A) ⇒ Boolean): Option[A] def Option.getOrElse[B >: A](default: ⇒ B): B Type B is inferred to be Any - the least common supertype between Tuple2 and Int
  • 31.
    Gotcha: Type widening Forexplicitly defined type hierarchies, this works as intended: scala> trait Animal scala> class Dog extends Animal scala> class Cat extends Animal scala> val dog = Option(new Dog()) scala> val cat = Option(new Cat()) scala> dog.orElse(cat) res0: Option[Animal] = Some(Dog@7d8995e)
  • 32.
    Gotcha: Auto-tupling def foo(any:Any) = println(any) foo(“a”, “b”, “c”) (a, b, c) The compiler tries to pack all arguments into a tuple and applies foo to that. This is the last thing which is tried (after default arguments).
  • 33.
    Gotcha: Type adaptiongalore val numbers = List("1", "2").toSet() + "3" println(numbers) false3 def List.toSet[B >: A]: Set[B] List("1", "2").toSet.apply() // apply() == contains() Compiler implicitly inserts Unit value () and infers supertype Any: List("1", "2").toSet[Any].apply(())
  • 34.
    Gotcha: Type adaptiongalore ●Not desirable in general ●Too much “magic” involved ●Automatic () insertion deprecated in 2.11
  • 35.
    Moral of thestory We often wish the compiler was more strict and threw an error in such cases We should take advantage of static analysis tools available (WartRemover, compiler flags, linters, etc.) Extensive unit testing of statically typed code is still necessary
  • 36.
    Moral of thestory Take advantage of the compiler flags: -deprecation, -unchecked, -feature, -Xfatal-warnings, -Xlint
  • 37.
  • 38.
    What’s the issue? Thereare lots of ways you can specify and call functions...
  • 39.
    Gotcha: Partial application varx = 0 def counter = { x += 1; x } def add(a: Int)(b: Int) = a + b val adder1 = add(counter)(_) val adder2 = add(counter) _ println("x = " + x) println(adder1(10)) println("x = " + x) println(adder2(10)) println("x = " + x)
  • 40.
    Gotcha: Native functionsyntax val isEven = PartialFunction[Int, String] { case n if n % 2 == 0 => "Even" }
  • 41.
    Moral of thestory There are many flavours of partial application that do slightly different things - try to stick to a few If you are going to use native function syntax, ensure you know exactly what you’re creating
  • 42.
  • 43.
    ●Java interoperability comesat a high cost ●It pays to study the rules of common desugarings closely ●Implicits up the level of “magic” in your code quite significantly ●Read the ScalaDoc carefully ●Don’t skimp on unit testing
  • 44.
    TL;DR: agree on: ● Scala features you really need; use linters, code review etc. to catch the others ●common code style in areas where the language allows multiple options
  • 45.
  • 46.
    Thank you! Be theone to submit the next puzzler at scalapuzzlers.com!