10 Things I Hate About Scala

Post on 01-Jul-2015

644 Views

Category:

Software

1 Downloads

Preview:

Click to see full reader

DESCRIPTION

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.

Transcript

10 Things I Hate About Scala

About Me

Meir Maor

Chief Architect @ SparkBeyond

At SparkBeyond we leverage the collective human knowledge to solve the world's toughest problems

1. Type Inference Limitations

Pop Quiz: What does the following expression return?

scala> List("foo").toSet.head.head<console>:8: error: value head is not a member of type parameter B List("foo").toSet.head.head ^

List("foo").toSet.head.head

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)

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)) ^

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.

Fixing the root cause

Requires adding Back-tracking after failure(less efficient)

Not planned for upcoming release

2. Type Erasure

List(“foo”) match {case x: List[Int] => 1case x: List[_] => 2

}

Structural Type Erasure

val z: Any = "foo"

z match {

case x: { def noSuchMethod() : Int } => "???"

case _ => "OK"

}

Type Erasure – Why

Odersky did it. (in Java)

Prevents generation of extra classes and overhead.

Refied Types (like reflection) enable breaking type safety

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 }

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

Type Erasure - Solutions

Fully Reified types - Not going to Happen

More Sugar may be added around TypeTags while keeping them optional.

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.

Mitigations

● Cross compiling, upgrade libraries, compile library from source yourself.

● Community builds

● Last resort: Separate JVM

● Isolate ClassLoaders (OSGI)

4. Overloading with default arguments

object Foo { def foo(a: Int, b: String = "foo") def foo(a: Double, b: String = "foo")}

error: in object Foo, multiple overloaded alternatives of method foo define default arguments

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.

Workaround

Avoid overloadswhen you need

default parameters :(

Fixing the root problem

● Nothing stopping it.

● Doesn’t seem to be anyone's top priority.

● A possible solution would beadding the disambiguating types to the generated method signature.

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 }

override def add(elem: Int): Boolean = {

require(elem >= 0)

if (contains(elem)) false

else {

val idx = elem >> LogWL

updateWord(idx, word(idx) | (1L << elem))

true

}

}

6. Standard Library - Quality

Bugs escape:

Range equality - 2.11.0, 2.11.1 (0 until 0) == (0 until 10) // True

(0 until 10) == (0 until 0) // NoSuchElementException

Searching: (Should return Insertion point for missing value)

Vector(1,2,4).search(0) // InsertionPoint(-1)

Why

The Scala standard library is very rich

Efforts focus more on the compiler

Mitigation

Know what's going on inside

Don't assume you can't do better than the standard library.

Solutions

Focus of up-coming versions

Smaller Standard Library

7. Equality type unsafety

if (“foo” == 4) 1 else 2// Compiles no warning

if (4 == “foo”) 1 else 2

// Compiles with warning

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 andInt using `==' will always yield false

a == 1

^

res39: Boolean = true

Why?

● Interoperability with Java● Simplicity

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"

^

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

Why

Some cases are difficult to infer

Need simple rules for requiring explicit types

Simple rules are overly broad

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)

Solution / Mitigations:

● SBT - for incremental compile (new flag)● Supply explicit return types● An area of continuous improvement in Scala

10. REPL Is Different

val a = (1, 2)println(a.getClass)println(a.getClass.getSuperclass)

Compiling and running normally:class scala.Tuple2$mcII$sp ←Specializationclass scala.Tuple2

From Repl:a: (Int, Int) = (1,2)

class scala.Tuple2

class java.lang.Object

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

Thank You

Join Us

top related