Click here to load reader
Upload
mircodotta
View
3.265
Download
0
Embed Size (px)
DESCRIPTION
The following is the abstract submitted for my Scala Days 2011 talk: Binary compatibility is not a topic specific to the Scala language, but rather a concern for all languages targeting the JVM, Java included. Scala shares with Java many sources of potential binary incompatibilities, however, because of Scala greater expressiveness, Scala code has unique sources of incompatibility. The Scala programming language offers several language constructs that do not have an equivalent in Java and are not natively supported by the JVM. Because of this, the Scala compiler (scalac) transforms these constructs into lower-lever, Java compatible, patterns that can be then easily translated into bytecode. Good examples of such high-level Scala constructs are traits, for mixin-based inheritance, and functions as first data citizens. During this presentation we will review the main sources of binary incompatibility for the Scala language, providing you with useful insights about how you should evolve your codebase to avoid binary incompatibilities. Furthermore, we will show a tool, the Migration Manager, that can be used to automatically diagnose binary incompatibilities between two versions of a same library.
Citation preview
Managing Binary Compatibility in Scala
Mirco Dotta
Typesafe
June 3, 2011
Mirco Dotta Managing Binary Compatibility in Scala
Introduction Sources of Incompatibility Conclusion
Outline
IntroductionExampleScala vs. Java
Sources of IncompatibilityType InferencerTrait
Conclusion
Mirco Dotta Managing Binary Compatibility in Scala
Introduction Sources of Incompatibility Conclusion Example Scala vs. Java
Example
Assume Analyzer is part of a library we produce. We decide thatits API has to evolve as follows:
class Analyzer { // old versiondef analyze(issues: HashMap[ , ]) {...}
}
class Analyzer { // new versiondef analyze(issues: Map[ , ]) {...}
}
Further, assume the next expression was compiled against the oldlibrary
new Analyzer().analyze(new HashMap[Any,Any]())
Would the compiled code work if run against the new library?
The answer lies in the bytecode...
Mirco Dotta Managing Binary Compatibility in Scala
Introduction Sources of Incompatibility Conclusion Example Scala vs. Java
Example: Bytecode
Let’s take look at the generated bytecode for the two versions:
class Analyzer { // old versionanalyze(Lscala/collection/immutable/HashMap);V}}
class Analyzer { // new versionanalyze(Lscala/collection/immutable/Map);V}}
The expression compiled against the old library would look like:
...invokevirtual #9;// #9 == Analyzer.analyze:(Lscala/collection/immutable/HashMap;)V
⇒ The method’s name has been statically resolved atcompile-time.
Running it against the new library would result in the JVMthrowing a NoSuchMethodException.
⇒ The evolution of class Analyzer breaks compatibility withpre-existing binaries.
Mirco Dotta Managing Binary Compatibility in Scala
Introduction Sources of Incompatibility Conclusion Example Scala vs. Java
Is Binary Compatibility a Scala issue?
The short answer is No. The discussed example can be easilyported in Java.
Scala shares with Java many sources of binary incompatibility.
But Scala offers many language features not available in Java:
I Type Inferencer
I First-class functions
I Multiple inheritance via mixin composition (i.e., traits)
. . . Just to cite a few.
⇒ Scala code has new “unique” sources of binary incompatibility.
Mirco Dotta Managing Binary Compatibility in Scala
Introduction Sources of Incompatibility Conclusion Type Inferencer Trait
Type Inferencer: Member Signature
Does the following evolution break binary compatibility?
class TextAnalyzer { // old versiondef analyze(text: String) = {val issues = Map[String,Any]()// ...issues
}}
class TextAnalyzer { // new versiondef analyze(text: String) = {val issues = new HashMap[String,Any]()// ...issues
}}
QuestionWhat is the inferred return type of analyze?
Let’s compare the two methods’ signature.
class TextAnalyzer { // old versionpublic scala.collection.immutable.Mapanalyze(java.lang.String);
}
class TextAnalyzer { // new versionpublic scala.collection.immutable.HashMapanalyze(java.lang.String);
}
Mirco Dotta Managing Binary Compatibility in Scala
Introduction Sources of Incompatibility Conclusion Type Inferencer Trait
Type Inferencer: Member Signature (2)
QuestionCan we prevent the method’s signature change?
That’s easy! The method’s return type has to be explicitlydeclared:
class TextAnalyzer { // bytecode compatible new versiondef analyze(text: String): Map[String,Any] = {val issues = new HashMap()// ...issues
}}
Take Home Message
Always declare the member’s type. If you don’t, you mayinadvertently change the member’s signature.
Mirco Dotta Managing Binary Compatibility in Scala
Introduction Sources of Incompatibility Conclusion Type Inferencer Trait
Trait Compilation
Traits are a powerful language construct that enablesmultiple-inheritance on top of a runtime – the JVM – that doesnot natively support it.
Understanding how traits are compiled is crucial if you need toensure release-to-release binary compatibility.
So, how does the Scala compiler generate the bytecode of a trait?
There are two key elements:
I A trait is compiled into an interface plus an abstract classcontaining only static methods.
I “Forwarder” methods are injected in classes inheriting traits.
Mirco Dotta Managing Binary Compatibility in Scala
Introduction Sources of Incompatibility Conclusion Type Inferencer Trait
Trait Compilation Explained
An example will help visualize how traits get compiled:
// declared in a librarytrait TypeAnalyzer {def analyze(prog: Program) {...}
}
// client codeclass TypingPhase extends TypeAnalyzer
The following is the (pseudo-)bytecode generated by scalac:
interface TypeAnalyzer {void analyze(prog: Program);
}abstract class TypeAnalyzer$class {static void analyze($this: TypeAnalyzer,
prog: Program) {// the trait’s method impl code
}}
class TypingPhase implements TraitAnalyzer {// forwarder method injected by scalacvoid analyze(prog: Program) {// delegates to implementationTypeAnalyzer$class.analyze(this,prog)
}}
Mirco Dotta Managing Binary Compatibility in Scala
Introduction Sources of Incompatibility Conclusion Type Inferencer Trait
Trait: Adding a concrete method
QuestionCan we add a member in a trait without breaking compatibilitywith pre-existing binaries?
trait TypeAnalyzer { // new versiondef analyze(prog: Program) {...}def analyze(clazz: ClassInfo) {...}
}
//TypeAnalyzer trait compiledinterface TypeAnalyzer {void analyze(prog: Program);void analyze(clazz: ClassInfo);
}abstract class TypeAnalyzer$class {static void analyze($this: TypeAnalyzer,
prog: Program{...}static void analyze($this: TypeAnalyzer,
clazz: ClassInfo) {...}}
// compiled against the old versionclass TypingPhase implements TraitAnalyzer {// forwarder method injected by scalacvoid analyze(prog: Program) {// delegates to implementationTypeAnalyzer$class.analyze(this,prog)
}// missing concrete implementation!??analyze(clazz: ClassInfo)??
}
Take Home Message
Adding a concrete method in a traitbreaks binary compatibility.
Mirco Dotta Managing Binary Compatibility in Scala
Introduction Sources of Incompatibility Conclusion Conclusion Future Work Scala Migration Manager
Conclusion
I Ensuring release-to-release binary compatibility of Scalalibraries is possible.
I Though, sometimes it can be difficult to tell if a change in theAPI of a class/trait will break pre-existing binaries.
I In the discussed examples we have seen that:I Type inferencer may be at the root of changes in the signature
of a method.I Traits are a sensible source of binary incompatibilities.
It really looks like library’s maintainers’ life ain’t that easy...
Mirco Dotta Managing Binary Compatibility in Scala
Introduction Sources of Incompatibility Conclusion Conclusion Future Work Scala Migration Manager
Introducing the Scala Migration Manager (MiMa)
Today we release the Scala Migration Manager! (beta)
I It’s free!!
I It will tell you, library maintainers, if your next release isbinary compatible with the current one.
I It will tell you, libraries users, if two releases of a library arebinary compatible.
MiMa can collect and report all sources of “syntactic” binaryincompatibilities between two releases of a same library.
I “Syntactic” means NO LinkageError (e.g.,NoSuchMethodException) will ever be thrown at runtime.
Now it’s time for a demo!
Mirco Dotta Managing Binary Compatibility in Scala
Introduction Sources of Incompatibility Conclusion Conclusion Future Work Scala Migration Manager
Future Work
Reporting binary incompatibilities is only half of the story. We arealready working on a “companion” tool that will help you migratebinary incompatibilities.
For the reporting there are many ideas spinning around. Yourfeedback will help us decide what brings you immediate value
One that I believe is useful:
I Maven/Sbt integration.
Mirco Dotta Managing Binary Compatibility in Scala
Introduction Sources of Incompatibility Conclusion Conclusion Future Work Scala Migration Manager
Scala Migration Manager
Visit http://typesafe.com/technology/migration-manager
I More information about the Migration Manager
I Download it and try it out, it’s free!
We want to hear back from you.
I Success stories
I Request new features
I Report bugs
Want to know more, make sure to get in touch!
Mirco Dotta, email: [email protected], twitter: @mircodotta
Mirco Dotta Managing Binary Compatibility in Scala