# Upcasting

Taking an object reference and treating it as a reference to its base type is called upcasting. The term upcast refers to the way inheritance hierarchies are traditionally represented with the base class at the top and derived classes fanning out below.

Inheriting and adding new member functions is the practice in Smalltalk, one of the first successful object-oriented languages. In Smalltalk, everything is an object and the only way to create a class is to inherit from an existing class, often adding new member functions. Smalltalk heavily influenced Java, which also requires everything to be an object.

Kotlin frees us from these constraints. We have stand-alone functions so everything doesn’t need to be contained within classes. Extension functions allow us to add functionality without inheritance. Indeed, requiring the open keyword for inheritance makes it a very conscious and intentional choice, not something to use all the time.

More precisely, it narrows inheritance to a very specific use, an abstraction that allows us to write code that can be reused across multiple classes within a single hierarchy. The [Polymorphism](javascript:void(0)) atom explores these mechanics, but first you must understand upcasting.

Consider some Shapes that can be drawn and erased:

// Upcasting/Shapes.kt
package upcasting

interface Shape {
  fun draw(): String
  fun erase(): String
}

class Circle : Shape {
  override fun draw() = "Circle.draw"
  override fun erase() = "Circle.erase"
}

class Square : Shape {
  override fun draw() = "Square.draw"
  override fun erase() = "Square.erase"
  fun color() = "Square.color"
}

class Triangle : Shape {
  override fun draw() = "Triangle.draw"
  override fun erase() = "Triangle.erase"
  fun rotate() = "Triangle.rotate"
}

The show() function accepts any Shape:

// Upcasting/Drawing.kt
package upcasting
import atomictest.*

fun show(shape: Shape) {
  trace("Show: ${shape.draw()}")
}

fun main() {
  listOf(Circle(), Square(), Triangle())
    .forEach(::show)
  trace eq """
    Show: Circle.draw
    Show: Square.draw
    Show: Triangle.draw
  """
}

In main(), show() is called with three different types: Circle, Square, and Triangle. The show() parameter is of the base class Shape, so show() accepts all three types. Each of those types is treated as a basic Shape—we say that the specific types are upcast to the basic type.

We typically draw a diagram for this hierarchy with the base class at the top:

upcasting

Shape Hierarchy

When we pass a Circle, Square, or Triangle as an argument of type Shape in show(), we cast up this inheritance hierarchy. In the process of upcasting, we lose the specific information about whether an object is of type Circle, Square, or Triangle. In each case, it becomes nothing more than a Shape object.

Treating a specific type as a more general type is the entire point of inheritance. The mechanics of inheritance exist solely to fulfill the goal of upcasting to the base type. Because of this abstraction (“everything is a Shape”), we can write a single show() function instead of writing one for every type of element. Upcasting is a way to reuse code for objects.

Indeed, in virtually every case where there’s inheritance without upcasting, inheritance is being misused—it’s unnecessary, and it makes the code needlessly complicated. This misuse is the reason for the maxim:

Prefer composition to inheritance.

If the point of inheritance is the ability to substitute a derived type for a base type, what happens to the extra member functions: color() in Square and rotate() in Triangle?

Substitutability, also called the Liskov Substitution Principle, says that, after upcasting, the derived type can be treated exactly like the base type—no more and no less. This means that any member functions added to the derived class are, in effect, “trimmed off.” They still exist, but because they are not part of the base-class interface, they are unavailable within show():

// Upcasting/TrimmedMembers.kt
package upcasting
import atomictest.*

fun trim(shape: Shape) {
  trace(shape.draw())
  trace(shape.erase())
  // Doesn't compile:
  // shape.color()    // [1]
  // shape.rotate()   // [2]
}

fun main() {
  trim(Square())
  trim(Triangle())
  trace eq """
    Square.draw
    Square.erase
    Triangle.draw
    Triangle.erase
  """
}

You can’t call color() in line [1] because the Square instance was upcast to a Shape, and you can’t call rotate() in line [2] because the Triangle instance is also upcast to a Shape. The only member functions available are the ones that are common to all Shapes—those defined in the base type Shape.

Note that the same applies when you directly assign a subtype of Shape to a general Shape. The specified type determines the available members:

// Upcasting/Assignment.kt
import upcasting.*

fun main() {
  val shape1: Shape = Square()
  val shape2: Shape = Triangle()
  // Doesn't compile:
  // shape1.color()
  // shape2.rotate()
}

After an upcast, you can only call members of the base type.

Exercises and solutions can be found at www.AtomicKotlin.com.