Typing in Java,
Kotlin and Scala
Hanneli Tavante
Breandan Considine
Hi!
@breandan
@hannelita
Disclaimer
We will work on a single example
and rebuild it several times
It is not a language war!
We are going to discuss
some architecture concepts
Agenda
- Representing Layouts in Java
- Moving to Kotlin
- Covariance and contravariance in Scala
- Covariance and contravariance in Kotlin
- ByteCode and type erasure
- Why is this important?
Mobile
Vertical
Horizontal
Layout
How can we represent that in Java?
Layout
Vertical
Horizontal
class Layout {
//superclass
}
class Vertical
extends Layout {
}
class Horizontal
extends Layout {
}
Vertical
Horizontal
Layout
Inheritance... meh
- Tight coupling
- Reusing base class for unrelated child
Can we come up with a better layout?
interface Layout {
}
class Vertical
implements Layout {
}
class Horizontal
implements Layout {
}
Vertical
Horizontal
Layout
Does this object have these skills?
Does this object understand this protocol ?
Name refactor
interface Orientation {
}
class Vertical implements
Orientation {
}
class Horizontal implements
Orientation {
}
It is time to add some methods
rotate()
rotate() applied to a Horizontal -> Vertical
rotate() applied to a Vertical -> Horizontal
Where should we place the method rotate() ?
interface Orientation {
}
class Vertical implements
Orientation {
}
class Horizontal implements
Orientation {
}
interface Orientation {
}
class Vertical implements
Orientation {
Horizontal rotate() {
return
new Horizontal();
}
}
class Horizontal implements
Orientation {
Vertical rotate() {
return
new Vertical();
}
}
interface Orientation {
Orientation rotate();
}
class Vertical implements
Orientation {
Horizontal rotate() {
return
new Horizontal();
}
}
class Horizontal implements
Orientation {
Vertical rotate() {
return
new Vertical();
}
}
Can we make it better?
We also could use Generics
class Layout<T> {
}
class Layout<T> {
}
class Vertical {
}
class Horizontal {
}
Layout
Vertical
Horizontal
How do we restrict a Layout either Layout<Vertical> or Layout<Horizontal>
interface Orientation {
}
class Vertical implements
Orientation {
}
class Horizontal implements
Orientation {
}
class Layout<T extends Orientation> {
}
Can we have a generic Layout rotate() ?
interface Orientation {
Orientation rotate();
}
class Vertical implements
Orientation {
Horizontal rotate() {
return new Vertical();
}
}
class Horizontal implements
Orientation {
Vertical rotate() {
return new Horizontal();
}
}
class Layout<T extends Orientation> {
public Layout(T t){
this.t = t;
}
Orientation rotate(){
t.rotate();
}
}
We want to achieve:
Layout<Horizontal> layoutHorizontal =
new Layout<Horizontal>(new Horizontal());
layoutHorizontal.rotate();
Orientation result
=
Layout<Vertical> layoutvertical
We want:
(Layout<Vertical>)
layoutHorizontal.rotate();
=
Explicit casting
:(
Is there any way to avoid that?
Agenda
- Representing Layouts in Java
- Moving to Kotlin
- Covariance and contravariance in Scala
- Covariance and contravariance in Kotlin
- ByteCode and type erasure
- Why is this important?
In Kotlin:
interface Orientation {
fun rotate(): Orientation
}
class Vertical : Orientation {
override
fun rotate() = Horizontal()
}
class Horizontal : Orientation {
override
fun rotate() = Vertical()
}
class Layout<out T : Orientation>(val t: T) {
}
In Kotlin:
class Layout<out T : Orientation>(val t: T) {
}
In Kotlin:
class Layout<out T : Orientation>(val t: T) {
}
@JvmName("rotateVertical")
fun Layout<Horizontal>.rotate(): Vertical = this.t.rotate()
@JvmName("rotateHorizontal")
fun Layout<Vertical>.rotate(): Horizontal = this.t.rotate()
Extension functions
In Kotlin:
val horizontal = Layout(Horizontal())
val vertical = Layout(Vertical())
// Type safe rotation!
var v: Layout<Vertical> = horizontal.rotate()
var h: Layout<Horizontal> = vertical.rotate()
h = horizontal.rotate().rotate()
v = vertical.rotate().rotate()
In Kotlin:
val horizontal = Layout(Horizontal())
val vertical = Layout(Vertical())
// Type safe rotation!
var v: Layout<Vertical> = horizontal.rotate()
var h: Layout<Horizontal> = vertical.rotate()
h = horizontal.rotate().rotate()
v = vertical.rotate().rotate()
/* Does not compile!
v = horizontal.rotate().rotate()
h = vertical.rotate().rotate()
*/
Agenda
- Representing Layouts in Java
- Moving to Kotlin
- Covariance and contravariance in Scala
- Covariance and contravariance in Kotlin
- ByteCode and type erasure
- Why is this important?
Redesign In Scala:
trait Orientation {}
Redesign In Scala:
trait Orientation {}
class Vertical extends Orientation {}
class Horizontal extends Orientation {}
Redesign In Scala:
trait Orientation {}
class Vertical extends Orientation {}
class Horizontal extends Orientation {}
abstract class Layout[+T <: Orientation] {
def layout: T
}
Redesign In Scala:
trait Orientation {}
class Vertical extends Orientation {}
class Horizontal extends Orientation {}
abstract class Layout[T <: Orientation] {
def layout: T
}
class VerticalLayout(v: Vertical)
extends Layout[Vertical] {
override def layout = v
}
class HorizontalLayout(h: Horizontal)
extends Layout[Horizontal] {
override def layout = h
}
Orientation
Vertical
Horizontal
Orientation
Vertical
Horizontal
Layout
Horizontal
Layout
Vertical
Orientation
LayoutVertical
LayoutHorizontal
In Scala
val layout: Layout[Orientation] = new
HorizontalLayout(new Horizontal)
Compile error: "Expression doesn't conform to the expected type"
In Scala
val layout: Layout[Orientation] = new
HorizontalLayout(new Horizontal)
abstract class Layout[T <: Orientation] {
def layout: T
}
We need to tell the compiler that
Layout[Horizontal] is a subtype Layout[Orientation]
In Scala
val layout: Layout[Orientation] = new
HorizontalLayout(new Horizontal)
abstract class Layout[+T <: Orientation] {
def layout: T
}
We need to tell the compiler that
Layout[Horizontal] is a subtype Layout[Orientation]
(Covariance)
In Scala
abstract class Layout[+T <: Orientation] {
def layout: T
def rerender(layout: T)
}
Method to re-render the actual screen, keeping the layout.
In Scala
abstract class Layout[+T <: Orientation] {
def layout: T
def rerender(layout: T)
}
Compile error: "Covariant type occurs in contravariant position"
In Scala
abstract class Layout[+T <: Orientation] {
def layout: T
def rerender[T >: Orientation](layout: T)
}
Why does the injected argument needs to be contravariant?
In Scala
abstract class Layout[+T <: Orientation] {
def layout: T
def rerender(layout: T)
}
var layoutWrapper: Layout[Orientation] =
new HorizontalLayout(new Horizontal)
layoutWrapper.rerender(new Vertical)
^That is not right.
That is why input arguments are contravariant.
Scala and Kotlin help you actively think about covariance and contravariance
Agenda
- Representing Layouts in Java
- Moving to Kotlin
- Covariance and contravariance in Scala
- Covariance and contravariance in Kotlin
- ByteCode and type erasure
- Why is this important?
Back to Kotlin
interface Orientation {
fun rotate(): Orientation
}
object Vertical : Orientation {
override fun rotate() = Horizontal
}
object Horizontal : Orientation {
override fun rotate() = Vertical
}
Back to Kotlin
interface Orientation {
fun rotate(): Orientation
}
object Vertical : Orientation {
override fun rotate() = Horizontal
}
object Horizontal : Orientation {
override fun rotate() = Vertical
}
class Layout<out T : Orientation>(val t: T) {
}
Back to Kotlin
open class Builder<out T : Layout<*>> {
open fun build(): T = build()
}
Back to Kotlin
open class Builder<out T : Layout<*>> {
open fun build(): T = build()
}
open class Renderer<in T> {
open fun render(t: T) {
println(t)
}
}
Back to Kotlin
open class Builder<out T : Layout<*>> {
open fun build(): T = build()
}
open class Renderer<in T> {
open fun render(t: T) {
println(t)
}
}
fun covariance(horizontalBuilder: Builder<Layout<Horizontal>>,
layoutBuilder: Builder<Layout<Orientation>>) {
// Layout<Horizontal> is a subtype of Layout<Orientation> ✓
val bldr: Builder<Layout<Orientation>> = horizontalBuilder
// Layout<Orientation> is a supertype of Layout<Horizontal> ✗
val hlbr: Builder<Layout<Horizontal>> = layoutBuilder //ERROR
}
Back to Kotlin
open class Builder<out T : Layout<*>> {
open fun build(): T = build()
}
open class Renderer<in T> {
open fun render(t: T) {
println(t)
}
}
fun contrav(layoutRenderer: Renderer<Layout<Orientation>>,
horizontalRenderer: Renderer<Layout<Horizontal>>) {
// Layout<Orientation> is a supertype of Layout<Horizontal> ✓
val hlrd: Renderer<Layout<Horizontal>> = layoutRenderer
// Layout<Horizontal> is a subtype of Layout<Orientation> ✗
val lrdr: Renderer<Layout<Orientation>> = horizontalRenderer
}
Agenda
- Representing Layouts in Java
- Moving to Kotlin
- Covariance and contravariance in Scala
- Covariance and contravariance in Kotlin
- ByteCode and type erasure
- Why is this important?
JVM Languages
will loose the T parameter!
class Layout {}
(type erasure)
Strategies
- TypeTags and Shapeless libraries in Scala
- Extension functions in Kotlin
- Reified generics in Kotlin
- Reflection
- Manually store T in a field
Agenda
- Representing Layouts in Java
- Moving to Kotlin
- Covariance and contravariance in Scala
- Covariance and contravariance in Kotlin
- ByteCode and type erasure
- Why is this important?
To sum up
- Think about different coding approaches
- Different architectures
- New concepts
- Understand the goods and the bad parts of a language and when to use each of them
References
Thank you :)
Questions?
Typing in Java, Kotlin and Scala - Devoxx US 2017
By Hanneli Tavante (hannelita)
Typing in Java, Kotlin and Scala - Devoxx US 2017
- 3,816