Mike Slinn

Structural Types

— Draft —

Published 2014-08-04. Last modified 2016-10-25.
Time to read: 3 minutes.

Structural types provide a typesafe way to use reflection. With structural types, you trade off execution speed for extra flexibility when using inheritance.

The sample code for this lecture can be found in courseNotes/src/main/scala/Structural.scala.

Structural types are sometimes called duck types. This is because if something acts like a duck, then it can be considered as a duck for practical purposes. Similarly, if an object has one or more methods with specific signatures, or properties with specific names and types then the object can be considered to be a given type. In Scala, a structural type is defined { by writing one or more method signatures and/or properties within curly braces }.

Scala code
object Ducks1 extends App {
  case class Mallard(age: Double) {
    def quack(count: Int): Unit = println("Quack! " * count)
  }

  case class FrenchDuck(weight: Double) {
    def quack(count: Int): Unit = println("Le quack !  " * count)
  }

  val mallard: { def quack(count: Int): Unit } = Mallard(4)
  val frenchDuck: { def quack(count: Int): Unit } = FrenchDuck(5)

  mallard.quack(2)
  frenchDuck.quack(3)
}

You can run this code by typing.

Shell
$ sbt "runMain Ducks1"
Quack! Quack!
Le quack !  Le quack !  Le quack ! 

Using Type Aliases

Structural types are easier to work with when defined as type aliases. We could rewrite the above using a type alias as follows.

Scala code
object Ducks2 extends App {
  type Duck = { def quack(count: Int): Unit }
case class Mallard(age: Double) { def quack(count: Int): Unit = println("Quack! " * count) }
case class FrenchDuck(weight: Double) { def quack(count: Int): Unit = println("Le quack ! " * count) }
val mallard: Duck = Mallard(4) val frenchDuck: Duck = FrenchDuck(5)
mallard.quack(2) frenchDuck.quack(3) }

You can run this code by typing:

Shell
$ sbt "runMain Ducks2"

Output is the same as before.

Structural Types With Properties

Structural types can also be defined by their properties. Type aliases make this code much more readable. Duck3 shows how structural type Duck requires that candidate objects possess a method called quack and a String property called color.

Scala code
object Ducks3 extends App {
  type Duck = {
    def quack(count: Int): Unit
val color: String }
case class Mallard(age: Double) { def quack(count: Int): Unit = println(s"Mallard has $color feathers: " + "Quack! " * count)
val color = "yellow" }
case class FrenchDuck(weight: Double) { def quack(count: Int): Unit = println(s"Le canard français a des plumes $color : " + "Le quack ! " * count)
val color = "blue" }
val mallard: Duck = Mallard(4) val frenchDuck: Duck = FrenchDuck(5)
mallard.quack(2) frenchDuck.quack(3) }

You can run this example by typing:

Shell
$ sbt "runMain Ducks3"
Mallard has yellow feathers: Quack! Quack!
Le canard français a des plume blue: Le quack !  Le quack !  Le quack ! 

Structural Types and Reflection

Structural types are syntactic sugar around reflection. It is not possible to extends a structural type or inherit from it, and not possible to override structural type methods. This means the following will not compile.

Scala code
class BadDuck extends { def quack(Int) } {
  def waddle() = println("Waddling...")
}

Here are two ways of accomplishing this legally, first using a trait, second using an abstract class:

Scala code
object GoodDuck extends App {
  trait DuckLike {
    def quack(count: Int): Unit
  }
class DuckExtendsTrait extends DuckLike { def quack(count: Int) = println("DuckExtendsTrait: " + "Quack! " * count)
def waddle() = println("DuckExtendsTrait Waddling...") }
abstract class AbstractDuck { def quack(count: Int): Unit }
class DuckConcrete extends AbstractDuck { def quack(count: Int) = println("DuckConcrete: " + "Quack! " * count)
def waddle() = println("DuckConcrete: Waddling...") }
val duck1 = new DuckExtendsTrait duck1.quack(3) duck1.waddle()
val duck2 = new DuckConcrete duck2.quack(3) duck2.waddle() }

You can run this by typing:

Shell
$ sbt "runMain GoodDuck"
DuckExtendsTrait: Quack! Quack! Quack!
DuckExtendsTrait Waddling...
DuckConcrete: Quack! Quack! Quack!
DuckConcrete: Waddling... 

Structural Types With Parametrics

This example extends the With pattern first introduced in the More Fun With Functions lecture of the Introduction to Scala course, which was made parametric in the ’With’ Pattern Revisited section of the Parametric Types lecture of this course, and further explored in the Parametric ’With’ Pattern Revisited section of the Partial Functions lecture. We will now use the With pattern to define a using method that is an example of a best practice: employing a mechanism that automatically closes resources, even when an exception is thrown.

Structural types are often used in conjunction with parametric types. Here is a useful example that shows how the structural type Closeable defines an upper bound for A; in other words, A must be a subclass of Closeable. For a structural type, that means A must implement Closeable. Note that ByteArrayInputStream has a close method, so it conforms to the Closeable structural type signature.

Scala code
object Structural extends App {
  type Closeable = { def close(): Unit }
def using[A <: Closeable, B](closeable: A)(f: A => B): B = { try { f(closeable) } finally { try { closeable.close() } catch { case _: Throwable => () } } }
val byteStream = new java.io.ByteArrayInputStream("hello world".getBytes) using(byteStream){ in => val str = io.Source.fromInputStream(in).mkString println(s"’$str’ has ${str.length} characters") } }

You can run this code by typing:

Scala REPL
$ sbt "runMain Structural"
’hello world’ has 11 characters 

BTW, we can improve this program by using call by name for the closeable parameter. The reasons for doing that were explored in the Call By Name / Lazy Evaluation of a Parameter section of the More Fun With Functions lecture of the Introduction to Scala course.

Scala code
object Structural2 extends App {
  type Closeable = { def close(): Unit }

  def using[A <: Closeable, B](closeable: => A)(f: A => B): B = {
    val closeableRef = closeable // only reference closeable once
    try {
      f(closeableRef)
    } finally {
      try {
        closeableRef.close()
      } catch { case _: Throwable => () }
    }
  }

  val byteStream = new java.io.ByteArrayInputStream("hello world".getBytes)
  using(byteStream){ in =>
    val str = io.Source.fromInputStream(in).mkString
    println(s"’$str’ has ${str.length} characters")
  }
}

You can run this code by typing:

Shell
$ sbt "runMain Structural2"
’hello world’ has 11 characters 

Self Traits and Structural Types

Self types can extend structural types.

Scala code
type Openable = { def open(): Unit }
type Closeable = { def close(): Unit }
trait Door { self: Openable with Closeable => def doSomething(f: () => Unit): Unit = try { open() f() } finally { close() } }
class FrontDoor extends Door { def open(): Unit = println("Door is open")
def walkThrough(): Unit = doSomething { () => println("Walking through door") }
def close(): Unit = println("Door is closed") }

BTW, we could equally well have defined a type alias that specifies both the open and close methods:

Scala code
type OpenAndCloseable = {
  def open(): Unit
  def close(): Unit
}

... and then the self trait would have looked like this.

Scala code
trait Door { self: OpenAndCloseable =>

Let’s play with this in the REPL:

Scala REPL
scala> val frontDoor = new FrontDoor
frontDoor: FrontDoor = FrontDoor@595b5d6b
scala>
frontDoor.walkThrough() Door is open Walking through door Door is closed

You can also run this code by typing:

Shell
$ sbt "runMain SelfStructural"

This trait assumes that the object which it extends, referred to as self, has methods called open() and close(). This allows for safe mixins for duck typing. Remember that structural types are somewhat inefficient because they use reflection, so don’t write this type of code in a loop that gets called a lot.


* indicates a required field.

Please select the following to receive Mike Slinn’s newsletter:

You can unsubscribe at any time by clicking the link in the footer of emails.

Mike Slinn uses Mailchimp as his marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp’s privacy practices.