We are very happy to announce the availability of Scala 2.12.0!
Headline features
The Scala 2.12 compiler has been completely overhauled to make use of the new VM features available in Java 8:
- A trait compiles directly to an interface with default methods. This improves binary compatibility and Java interoperability.
- Scala and Java 8 interop is also improved for functional code, as methods that take functions can easily be called in both directions using lambda syntax. The
FunctionN
classes in Scala’s standard library are now Single Abstract Method (SAM) types, and all SAM types are treated uniformly – from type checking through code generation. No class file is generated for a lambda;invokedynamic
is used instead.
This release ships with a powerful new optimizer:
- Inlining: many more (effectively) final methods, including those defined in objects and traits, are now inlined.
- Closure allocations, dead code, and box/unbox pairs are eliminated more often.
For additional features, read on.
Compatibility
Although Scala 2.11 and 2.12 are mostly source compatible to facilitate cross-building, they are not binary compatible. This allows us to keep improving the Scala compiler and standard library.
All 2.12.x releases will be fully binary compatible with 2.12.0, in according with the policy we have followed since 2.10.
The list of open-source libraries released for Scala 2.12 is growing quickly!
This release is identical to 2.12.0-RC2.
Coming soon
Our roadmap lists the following upcoming releases for 2016:
- 2.12.1 will be out shortly (by the end of November) to address some known (but rare) issues in 2.12.0.
- 2.11.9 will be the last planned 2.11.x release (due by mid December)
In the next few weeks, we at Lightbend will share our plans for Scala 2.13.
Known issues
There are some known issues with this release that will be resolved in 2.12.1, due later in November.
The heavy use of default methods for compiling traits caused some performance regressions in the startup time of Scala applications. Note that steady-state performance is not affected according to our measurements.
The regression was mitigated 2.12.0-RC2 (and the final release) by generating forwarder methods in classes that inherit concrete methods from traits, which unfortunately increases bytecode size while improving JVM startup performance.
Please let us know if you notice any performance regressions. We will continue to tweak the bytecode during the 2.12.x cycle to get the best performance out of the JVM.
We hope to address the following in a later 2.12.x release:
- SI-9824: Parallel collections are prone to deadlock in the REPL and in object initializers.
Obtaining Scala
Java 8 runtime
Install a recent build of the Java 8 Platform, such as OpenJDK or Oracle Java. Any Java 8 compliant runtime will do (but note that Oracle versions before 8u102 have a known issue that affects Scala).
We are planning to add (some) support for Java 9 in the near future. Full Java 9 support will be part of the 2.13 roadmap discussions.
Build tool
We recommend using sbt 0.13.13. Simply bump the scalaVersion
setting in your existing project, or start a new project using sbt new scala/scala-seed.g8
. We strongly recommend upgrading to sbt 0.13.13 for templating support using the new command, faster compilation, and much more.
Please head over to the scala-seed repo to extend this giter8 template with an example of your favorite 2.12 feature!
Scala also works with Maven, Gradle, and Ant.
You can also download a distribution from scala-lang.org, or obtain the JARs yourself from Maven Central.
Contributors
A big thank you to everyone who’s helped improve Scala by reporting bugs, improving our documentation, kindly helping others on forums and at meetups, and submitting and reviewing pull requests! You are all magnificent.
Scala 2.12.0 is the result of merging over 500 pull requests out of about 600 received PRs. The contributions to 2.12.x over the last 2 years were split 64/32/4 between the Scala team at Lightbend (lrytz, retronym, adriaanm, SethTisue, szeiger), the community, and EPFL.
The new encodings of traits, lambdas, and lazy vals were developed in fruitful collaboration with the Dotty team at EPFL.
The new compiler back end and the new optimizer are based on earlier work by Miguel Garcia at EPFL.
Scala 2.12 overview
Scala 2.12 is all about making optimal use of Java 8’s new features. Thus, it generates code that requires a Java 8 runtime.
- Traits (#5003) and functions are compiled to their Java 8 equivalents. The compiler no longer generates trait implementation classes (
T$class.class
) and anonymous function classes (C$$anonfun$1.class
). - We treat Single Abstract Method types and Scala’s built-in function types uniformly from type checking to the back end (#4971).
- We use
invokedynamic
for compiling functions. It also now provides a more natural encoding of other language features (#4896). - We’ve standardized on the GenBCode back end (#4814, #4838) and the flat classpath implementation is now the default (#5057).
- The optimizer has been completely overhauled for 2.12.
The new encodings for traits and lambdas lead to significantly smaller JAR files. For example, for ScalaTest 3.0.0, the jar size dropped from 9.9M to 6.7M.
Except for the breaking changes listed below, code that compiles on 2.11.x without deprecation warnings should compile on 2.12.x, unless you use experimental APIs such as reflection. If you find incompatibilities that are not listed below, please file an issue.
Thanks to source compatibility, cross-building is a one-line change to most sbt builds. Where needed, sbt provides support for version-specific source folders out of the box.
New language features
The next sections introduce new features and breaking changes in Scala 2.12 in more detail. To understand more technicalities and review past discussions, you can also take a look at the full list of noteworthy pull request that went into this release.
Traits compile to interfaces
Because Java 8 allows concrete methods in interfaces, Scala 2.12 is able to compile a trait to a single interface classfile. Before, a trait was represented as an interface and a class that held the method implementations (T$class.class
).
Additional magic is still involved, so care must be taken if a trait is meant to be implemented in Java. Briefly, if a trait does any of the following, its subclasses require synthetic code:
- defining fields (
val
orvar
, but a constant is ok –final val
without result type) - calling super
- initializer statements in the body
- extending a class
- relying on linearization to find implementations in the right supertrait
Lambda syntax for SAM types
The Scala 2.12 type checker accepts a function literal as a valid expression for any Single Abstract Method (SAM) type, in addition to the FunctionN
types from standard library. This improves the experience of using libraries written for Java 8 from Scala code. Here is a REPL example using java.lang.Runnable
:
scala> val r: Runnable = () => println("Run!")
r: Runnable = $$Lambda$1073/754978432@7cf283e1
scala> r.run()
Run!
Note that only lambda expressions are converted to SAM type instances, not arbitrary expressions of FunctionN
type:
scala> val f = () => println("Faster!")
scala> val fasterRunnable: Runnable = f
<console>:12: error: type mismatch;
found : () => Unit
required: Runnable
The language specification has the full list of requirements for SAM conversion.
With the use of default methods, Scala’s built-in FunctionN
traits are compiled to SAM interfaces. This allows creating Scala functions from Java using Java’s own lambda syntax:
public class A {
scala.Function1<String, String> f = s -> s.trim();
}
Specialized function classes are also SAM interfaces and can be found in the package scala.runtime.java8
.
Thanks to an improvement in type checking, the parameter type in a lambda expression can be omitted even when the invoked method is overloaded. See #5307 for details. In the following example, the compiler infers parameter type Int
for the lambda:
scala> trait MyFun { def apply(x: Int): String }
scala> object T {
| def m(f: Int => String) = 0
| def m(f: MyFun) = 1
| }
scala> T.m(x => x.toString)
res0: Int = 0
Note that though both methods are applicable, overloading resolution selects the one with the Function1
argument type, as explained in more detail below.
Java 8-style bytecode for lambdas
Scala 2.12 emits bytecode for functions in the same style as Java 8, whether they target a FunctionN
class from the standard library or a user-defined Single Abstract Method (SAM) type.
For each lambda the compiler generates a method containing the lambda body, and emits an invokedynamic
that will spin up a lightweight class for this closure using the JDK’s LambdaMetaFactory
. Note that in the following situations, an anonymous function class is still synthesized at compile time:
- If the SAM type is not a simple interface, for example an abstract class or a trait with a field definition (see #4971)
- If the abstract method is specialized – except for
scala.FunctionN
, whose specialized variants can be instantiated usingLambdaMetaFactory
(see #4971) - If the function literal is defined in a constructor or super call (#3616)
Compared to Scala 2.11, the new scheme has the advantage that, in most cases, the compiler does not need to generate an anonymous class for each closure.
Our backend support for invokedynamic
is also available to macro authors, as shown in this test case.
Partial unification for type constructor inference
Compiling with -Ypartial-unification
improves type constructor inference with support for partial unification, fixing the notorious SI-2712. Thank you, Miles Sabin for contributing your implementation (and backporting to 2.11.9)!
Also, hat tip to Daniel Spiewak for a great explanation of this feature.
We recommend enabling this with -Ypartial-unification
rather than -Xexperimental
, as the latter enables some surprising features that will not ship with a future release of Scala.
New representation and locking scope for local lazy vals
Local lazy vals and objects, i.e., those defined in methods, now use a more efficient representation (implemented in #5294 and #5374).
In Scala 2.11, a local lazy val was encoded using two heap-allocated objects (one for the value, a second for the initialized flag). Initialization was synchronized on the enclosing class instance.
In 2.12, with the new representation for lambdas, which emits the lambda body as a method in the enclosing class, new deadlocks can arise for lazy vals or objects defined in the lambda body.
This has been fixed by creating a single heap-allocated object that is used for init locking and holds both the value and the initialized flag. (A similar implementation already existed in Dotty.)
Better type inference for Scala.js
The improved type inference for lambda parameters also benefits js.Function
s. For example, you can now write:
dom.window.requestAnimationFrame { now => // inferred as Double
...
}
without having to specify (now: Double)
explicitly.
In a similar spirit, the new inference for overriding val
s allows to more easily implement Scala.js-defined JS traits with anonymous objects. For example:
@ScalaJSDefined
trait SomeOptions extends js.Object {
val width: Double | String // e.g., "300px"
}
val options = new SomeOptions {
// implicitly converted from Int to the inferred Double | String
val width = 200
}
Tooling improvements
New back end
Scala 2.12 standardizes on the “GenBCode” back end, which emits code more quickly because it directly generates bytecode from Scala compiler trees. (The old back end used an intermediate representation.) The old back ends (GenASM and GenIcode) have been removed (#4814, #4838).
New optimizer
The GenBCode back end includes a new inliner and bytecode optimizer. The optimizer is configured using the -opt
compiler option. By default it only removes unreachable code within a method. Check -opt:help
to see the list of available options for the optimizer.
The following optimizations are available:
- Inlining final methods, including methods defined in objects and final methods defined in traits
- If a closure is allocated and invoked within the same method, the closure invocation is replaced by an invocations of the corresponding lambda body method
- Dead code elimination and a small number of cleanup optimizations
- Box/unbox elimination #4858: primitive boxes and tuples that are created and used within some method without escaping are eliminated.
For example, the following code
def f(a: Int, b: Boolean) = (a, b) match {
case (0, true) => -1
case _ if a < 0 => -a
case _ => a
}
produces, when compiled with -opt:l:method
, the following bytecode (decompiled using cfr):
public int f(int a, boolean b) {
int n = 0 == a && true == b ? -1 : (a < 0 ? - a : a);
return n;
}
The optimizer supports inlining (disabled by default). With -opt:l:project
code from source files currently being compiled is inlined, while -opt:l:classpath
enables inlining code from libraries on the compiler’s classpath. Other than methods marked @inline
, higher-order methods are inlined if the function argument is a lambda, or a parameter of the caller.
Note that:
- We recommend enabling inlining only in production builds, as sbt’s incremental compilation does not track dependencies introduced by inlining.
- When inlining code from the classpath, you must ensure that all dependencies have exactly the same versions at compile time and run time.
- If you are building a library to publish on Maven Central, you should not inline code from dependencies. Users of your library might have different versions of those dependencies on the classpath, which breaks binary compatibility.
The Scala distribution is built using -opt:l:classpath
, which improves the performance of the Scala compiler by roughly 5% (hot and cold, measured using our JMH-based benchmark suite) compared to a non-optimized build.
Scaladoc look-and-feel overhauled
Scaladoc’s output is now more attractive, more modern, and easier to use. Take a look at the Scala Standard Library API.
Thanks, Felix Mulder, for leading this effort.
Scaladoc can be used to document Java sources
This fix for SI-4826 simplifies generating comprehensive documentation for projects with both Scala and Java sources. Thank you for your contribution, Jakob Odersky!
This feature is enabled by default, but can be disabled with:
scalacOptions in (Compile, doc) += "-no-java-comments"
Some projects with very large Javadoc comments may run into a stack overflow in the Javadoc scanner, which will be fixed in 2.12.1.
Scala shell (REPL)
Scala’s interactive shell ships with several spiffy improvements. To try it out, launch it from the command line with the scala
script or in sbt using the console
task. If you like color (who doesn’t!), use scala -Dscala.color
instead, until it’s turned on by default.
Since 2.11.8, the REPL uses the same tab completion logic as ScalaIDE and ENSIME, which greatly improves the experience. Check out PR 4725 for some tips and tricks.
sbt builds Scala
Scala itself is now completely built, tested and published with sbt!
This makes it easier to get started hacking on the compiler and standard library. All you need on your machine is JDK 8 and sbt - no ant, no environment variables to set, no shell scripts to run. You can build, use, test and publish Scala like any other sbt-based project.
Due to Scala’s bootstrapped nature, IntelliJ cannot yet import our sbt build directly. Use the intellij
task instead to generate suitable project files.
Library improvements
Either is now right-biased
Either
now supports operations like map
, flatMap
, contains
, toOption
, and so forth, which operate on the right-hand side. The .left
and .right
methods may be deprecated in favor of .swap
in a later release.
The changes are source-compatible with existing code (except in the presence of conflicting extension methods).
This change has allowed other libraries, such as cats to standardize on Either
.
Thanks, Simon Ochsenreither, for this contribution.
Futures improved
A number of improvements to scala.concurrent.Future
were made for Scala 2.12. This blog post series by Viktor Klang explores them in detail.
scala-java8-compat
The Java 8 compatibility module for Scala has received an overhaul for Scala 2.12. Even though interoperability of Java 8 SAMs and Scala functions is now baked into the language, this module provides additional convenience for working with Java 8 SAMs. Java 8 streams support was also added during the development cycle of Scala 2.12. Releases are available for both Scala 2.11 and Scala 2.12.
Other changes and deprecations
- For comprehension desugaring requires
withFilter
now, never falls back tofilter
(#5252) - A mutable TreeMap implementation was added (#4504).
- ListSet and ListMap now ensure insertion-order traversal (in 2.11.x, traversal was in reverse order), and their performance has been improved (#5103).
- The
@deprecatedInheritance
and@deprecatedOverriding
are now public and available to library authors. - The
@hideImplicitConversion
Scaladoc annotation allows customizing which implicit conversions are hidden (#4952). - The
@shortDescription
Scaladoc annotation customizes the method summary on entity pages (#4991). - JavaConversions, providing implicit conversions between Scala and Java collection types, has been deprecated. We recommend using JavaConverters and explicit
.asJava
/.asScala
conversions. - Eta-expansion (conversion of a method to a function value) of zero-args methods has been deprecated, as this can lead to surprising behavior (#5327).
- The Scala library is now free of references to
sun.misc.Unsafe
, and no longer ships with a fork of the forkjoin library. - Exhaustiveness analysis in the pattern matcher has been improved (#4919).
- We emit parameter names according to JEP-118, which makes them available to Java tools and exposes them through Java reflection.
Breaking changes
Object initialization locks and lambdas
In Scala 2.11, the body of a lambda was in the apply
method of the anonymous function class generated at compile time. The new lambda encoding in 2.12 lifts the lambda body into a method in the enclosing class. An invocation of the lambda therefore involves the enclosing class, which can cause deadlocks that did not happen before.
For example, the following code
import scala.concurrent._
import scala.concurrent.duration._
import ExecutionContext.Implicits.global
object O { Await.result(Future(1), 5.seconds) }
compiles to (simplified):
public final class O$ {
public static O$ MODULE$;
public static final int $anonfun$new$1() { return 1; }
public static { new O$(); }
private O$() {
MODULE$ = this;
Await.result(Future.apply(LambdaMetaFactory(Function0, $anonfun$new$1)), DurationInt(5).seconds);
}
}
Accessing O
for the first time initializes the O$
class and executes the static initializer (which invokes the instance constructor). Class initialization is guarded by an initialization lock (Chapter 5.5 in the JVM specification).
The main thread locks class initialization and spawns the Future. The Future, executed on a different thread, attempts to execute the static lambda body method $anonfun$new$1
, which also requires initialization of the class O$
. Because initialization is locked by the main thread, the thread running the future will block. In the meantime, the main thread continues to run Await.result
, which will block until the future completes, causing the deadlock.
One example of this surprised the authors of ScalaCheck – now fixed in version 1.13.4.
Lambdas capturing outer instances
Because lambda bodies are emitted as methods in the enclosing class, a lambda can capture the outer instance in cases where this did not happen in 2.11. This can affect serialization.
The Scala compiler analyzes classes and methods to prevent unnecessary outer captures: unused outer parameters are removed from classes (#4652), and methods not accessing any instance members are made static (#5099). One known limitation is that the analysis is local to a class and does not cover subclasses.
class C {
def f = () => {
class A extends Serializable
class B extends A
serialize(new A)
}
}
In this example, the classes A
and B
are first lifted into C
. When flattening the classes to the package level, the A
obtains an outer pointer to capture the A
instance. Because A
has a subclass B
, the class-level analysis of A
cannot conclude that the outer parameter is unused (it might be used in B
).
Serializing the A
instance attempts to serialize the outer field, which causes a NotSerializableException: C
.
SAM conversion precedes implicits
The SAM conversion built into the type system takes priority over implicit conversion of function types to SAM types. This can change the semantics of existing code relying on implicit conversion to SAM types:
trait MySam { def i(): Int }
implicit def convert(fun: () => Int): MySam = new MySam { def i() = 1 }
val sam1: MySam = () => 2 // Uses SAM conversion, not the implicit
sam1.i() // Returns 2
To retain the old behavior, your choices are:
- compile under
-Xsource:2.11
- use an explicit call to the conversion method
- disqualify the type from being a SAM (e.g. by adding a second abstract method).
Note that SAM conversion only applies to lambda expressions, not to arbitrary expressions with Scala FunctionN
types:
val fun = () => 2 // Type Function0[Int]
val sam2: MySam = fun // Uses implicit conversion
sam2.i() // Returns 1
SAM conversion in overloading resolution
In order to improve source compatibility, overloading resolution has been adapted to prefer methods with Function
-typed arguments over methods with parameters of SAM types. The following example is identical in Scala 2.11 and 2.12:
scala> object T {
| def m(f: () => Unit) = 0
| def m(r: Runnable) = 1
| }
scala> val f = () => ()
scala> T.m(f)
res0: Int = 0
In Scala 2.11, the first alternative was chosen because it is the only applicable method. In Scala 2.12, both methods are applicable, therefore overloading resolution needs to pick the most specific alternative. The specification for type compatibility has been updated to consider SAM conversion, so that the first alternative is more specific.
Note that SAM conversion in overloading resolution is always considered, also if the argument expression is not a function literal (like in the example). This is unlike SAM conversions of expressions themselves; see the previous section. See also the discussion in scala-dev#158.
While the adjustment to overloading resolution improves compatibility overall, code does exist that compiles in 2.11 but is ambiguous in 2.12, for example:
scala> object T {
| def m(f: () => Unit, o: Object) = 0
| def m(r: Runnable, s: String) = 1
| }
defined object T
scala> T.m(() => (), "")
<console>:13: error: ambiguous reference to overloaded definition
Inferred types for fields
Type inference for val
, and lazy val
has been aligned with def
, fixing assorted corner cases and inconsistencies (#5141 and #5294). Concretely, when computing the type of an overriding field, the type of the overridden field is used as the expected type. As a result, the inferred type of a val
or lazy val
may change in Scala 2.12.
In particular, an implicit val
that did not need an explicitly declared type in 2.11 may need one now. (Type-annotating implicits is always good practice anyway.)
You can get the old behavior with -Xsource:2.11
. This may be useful for testing whether these changes are responsible if your code fails to compile.
Changed syntax trees (affects macro and compiler plugin authors)
PR #4794 changed the syntax trees for selections of statically accessible symbols. For example, a selection of Predef
no longer has the shape q"scala.this.Predef"
but simply q"scala.Predef"
. Macros and compiler plugins matching on the old tree shape need to be adjusted.
Improving these notes
Improvements to these release notes are welcome!
Conclusion
We again thank our contributors and the entire Scala community. May you find Scala 2.12 a pleasure to code in.