Slides from the presentation "Analysing Scala Puzzlers: Essential and Accidental Complexity in Scala" at Scala Up North 2015, by Andrew Phillips & Nermin Serifovic. See http://scalaupnorth.com/speakers.html#andrew
2. 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
5. 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”
6. 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
8. What’s the issue?
Scala aims to combine functional and object-
oriented features...and play nicely with Java on
top of that.
9. 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)
10. 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)
11. 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)
}
}
12. 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
15. Gotcha: collections are functions!
Most commonly used collections are instances
of Function1:
List: index => element
Set: element => Boolean
Map: key => value
16. 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
17. Gotcha: collections are functions!
Sometimes, this produces undesired effects...
def pad2(sb: StringBuilder, width: Int) = {
1 to width - sb.length foreach { sb append '*' }
sb
}
21. 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
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?
25. 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 }
26. 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)
27. 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
29. 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?
30. 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
31. 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)
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 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(())
34. Gotcha: Type adaption galore
●Not desirable in general
●Too much “magic” involved
●Automatic () insertion deprecated in 2.11
35. 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
36. Moral of the story
Take advantage of the compiler flags:
-deprecation,
-unchecked,
-feature,
-Xfatal-warnings,
-Xlint
39. 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)
40. Gotcha: Native function syntax
val isEven = PartialFunction[Int, String] {
case n if n % 2 == 0 => "Even"
}
41. 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
43. ●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
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