I love Scala, I really do. I find Scala both more productive and more fun then any of the many languages I have worked with before. There are many reasons to use to Scala; however, I will not be talking about them.
Instead, in this talk I will cover some of the things I find annoying or disappointing in Scala. For each I will explain the issue, why it is so, whether or not it is likely to ever change, and what, if anything, we can do about it in the mean time.
The issues covered will include:
Limitations on type inference
Type Erasure
Binary incompatibility
Limitations on overloading
Standard library quality
And more
The lecture is geared towards those who have written between 10 and 10 millions lines of Scala code.
2. About Me
Meir Maor
Chief Architect @ SparkBeyond
At SparkBeyond we leverage the collective
human knowledge to solve the world's toughest
problems
3. 1. Type Inference Limitations
Pop Quiz: What does the following expression
return?
List("foo").toSet.head.head
scala> List("foo").toSet.head.head
<console>:8: error: value head is not a member of type parameter B
List("foo").toSet.head.head
^
4. Why?
def toSet[B >: A]: immutable.Set[B] =
to[immutable.Set].asInstanceOf[immutable.Set[B]]
The required type should affect type inference:
val a: Set[Any] = List("foo").toSet
But not break Set Invariance:
val aSet: Set[String] = Set("foo")
val b: Set[Any] = aSet
As we want to use [A] in both variant and co-variant positions:
val s=Set("foo")
List("foo","bar").filter(s)
5. Type Inference – Cont.
Make up test:
math.sqrt(Some(1).getOrElse(1))
<console>:8: error: type mismatch;
found : AnyVal
required: Double
math.sqrt(Some(1).getOrElse(1))
^
6. Workarounds
Use to[Set] instead of toSet:
List("foo").to[Set].head.head
Explicitly state type parameter [Int]:
math.sqrt(Some(1).getOrElse[Int](1))
Assign a variable to force a concrete type sooner.
7. Fixing the root cause
Requires adding Back-tracking after failure
(less efficient)
Not planned for upcoming release
8. 2. Type Erasure
List(“foo”) match {
case x: List[Int] => 1
case x: List[_] => 2
}
9. Structural Type Erasure
val z: Any = "foo"
z match {
case x: { def noSuchMethod() : Int } => "???"
case _ => "OK"
}
10. Type Erasure – Why
Odersky did it. (in Java)
Prevents generation of extra classes and
overhead.
Refied Types (like reflection) enable breaking
type safety
11. Type Erasure - Workarounds
def foo[T](a: T)(implicit ev: TypeTag[T]) = {
ev.tpe <:< typeOf[Seq[String]]
}
def foo[T: TypeTag](a: T) = {
typeOf[T] <:< typeOf[Seq[String]]
}
def putInArray[T: ClassTag](a: T) = {
val res = new Array[T](1)
res(0) = a
res
}
12. More Workarounds
When TypeTags won't do:
● Pattern match member of collection
(implies non empty)
● Check members reflectively explicitly
(for structural type)
● Restructure to keep type safety
13. Type Erasure - Solutions
Fully Reified types - Not going to Happen
More Sugar may be added around TypeTags
while keeping them optional.
14. 3) Binary Incompatibility
● Scala 2.11.x / 2.10.x / 2.9.x aren’t compatible
with each other.
● Libraries need to be cross-compiled to different
Scala versions.
● Source compatibility isn’t perfect either.
15. Mitigations
● Cross compiling, upgrade libraries, compile library
from source yourself.
● Community builds
● Last resort: Separate JVM
● Isolate ClassLoaders (OSGI)
17. Why?
● In order to have a deterministic naming-scheme for the
generated methods which return default arguments.
If you write
def foo(a: Int,b: String = "foo")
the compiler generates:
def foo$default$2 = "foo"
● If two overloads have default parameters,
the generated code will conflict.
19. Fixing the root problem
● Nothing stopping it.
● Doesn’t seem to be anyone's top priority.
● A possible solution would be
adding the disambiguating types to the
generated method signature.
20. 5. Standard Library - Performance
Dearth of overriding implementations for
performance:
def getOrElseUpdate(key: A, op: => B): B =
get(key) match {
case Some(v) => v
case None => val d = op; this(key) = d; d
}
26. 7. Equality type unsafety
if (“foo” == 4) 1 else 2
// Compiles no warning
if (4 == “foo”) 1 else 2
// Compiles with warning
27. Developers can do many things
Proving the scala compiler wrong:
case class Foo(a: Int)
class Foo2(a: Int) extends Foo(a) { // Bad Idea
override def equals(other: Any) = true
}
val a: Foo = Foo2(1)
a == 1
<console>:62: warning: comparing values of types Bar and
Int using `==' will always yield false
a == 1
^
res39: Boolean = true
29. Mitigations
Scalaz === operator, gives compile time type safety.
scala> 1 === 1
res0: Boolean = true
scala> 1 === "foo"
<console>:14: error: could not find implicit value for parameter F0:
scalaz.Equal[Object]
1 === "foo"
^
30. 8. Explicit method return types
There are (too) many cases you must define an explicit
method return type:
● Explicitly call return in a method (even at the end)
● Recursive methods
● A method is overloaded and one of the methods calls
another. The calling method needs a return type
annotation.
● The inferred return type would be more general than
you intended, e.g. Any
31. Why
Some cases are difficult to infer
Need simple rules for requiring explicit types
Simple rules are overly broad
32. 9. Compile time
● Scala compilation lines/sec is
~10X slower than Java
● More functionality per line can shrink the
difference to 3X-5X range (benchmarks vary wildly)
33. Solution / Mitigations:
● SBT - for incremental compile (new flag)
● Supply explicit return types
● An area of continuous improvement in Scala
34. 10. REPL Is Different
val a = (1, 2)
println(a.getClass)
println(a.getClass.getSuperclass)
Compiling and running normally:
class scala.Tuple2$mcII$sp ←Specialization
class scala.Tuple2
From Repl:
a: (Int, Int) = (1,2)
class scala.Tuple2
class java.lang.Object
35. Final Thoughts
Scala is definitely production grade
Happily been using in production for 3 years
Undergoing constant improvement
You can take an active part in improving Scala