How to support a fluent style of programming in Scala

This is an excerpt from the Scala Cookbook (partially modified for the internet). This is Recipe 5.9, “How to support a fluent style of programming in Scala.”

Problem

You want to create a Scala API so developers can write code in a fluent programming style, also known as method chaining.

Solution

A fluent style of programming lets users of your API write code by chaining method calls together, as in this example:

person.setFirstName("Leonard")
      .setLastName("Nimoy")
      .setAge(82)
      .setCity("Los Angeles")
      .setState("California")

To support this style of programming:

  • If your class can be extended, specify this.type as the return type of fluent style methods.
  • If you’re sure that your class won’t be extended, you can optionally return this from your fluent style methods.

Returning this.type

The following code demonstrates how to specify this.type as the return type of the set* methods:

class Person {
    protected var fname = ""
    protected var lname = ""
    def setFirstName(firstName: String): this.type = {
        fname = firstName
        this
    }
    def setLastName(lastName: String): this.type = {
        lname = lastName
        this
    }
}

class Employee extends Person {
  protected var role = ""
  def setRole(role: String): this.type = {
      this.role = role
      this
  }
  override def toString = {
      "%s, %s, %s".format(fname, lname, role)
  }
}

The following test object demonstrates how these methods can be chained together:

object Main extends App {
    val employee = new Employee
    // use the fluent methods
    employee.setFirstName("Al")
            .setLastName("Alexander")
            .setRole("Developer")
    println(employee)
}

Discussion

If you’re sure your class won’t be extended, specifying this.type as the return type of your set* methods isn’t necessary; you can just return the this reference at the end of each fluent style method. This is shown in the addTopping, setCrustSize, and setCrustType methods of the following Pizza class, which is declared to be final:

final class Pizza {
    import scala.collection.mutable.ArrayBuffer
    private val toppings = ArrayBuffer[String]()
    private var crustSize = 0
    private var crustType = ""
    def addTopping(topping: String) = {
        toppings += topping
        this
    }
    def setCrustSize(crustSize: Int) = {
        this.crustSize = crustSize
        this
    }
    def setCrustType(crustType: String) = {
        this.crustType = crustType
        this
    }
    def print() {
        println(s"crust size: $crustSize")
        println(s"crust type: $crustType")
        println(s"toppings: $toppings")
    }
}

The use of this class is demonstrated with the following driver program:

object FluentPizzaTest extends App {
    val p = new Pizza
    p.setCrustSize(14)
     .setCrustType("thin")
     .addTopping("cheese")
     .addTopping("green olives")
     .print()
}

That code results in the following output:

crust size: 14
crust type: thin
toppings:   ArrayBuffer(cheese, green olives)

Returning this in your methods works fine if you’re sure your class won’t be extended, but if your class can be extended—as in the first example where the Employee class extended the Person class—explicitly setting this.type as the return type of your set* methods ensures that the fluent style will continue to work in your subclasses. In this example, this makes sure that methods like setFirstName on an Employee object return an Employee reference and not a Person reference.

See Also

  • Definition of a fluent interface
  • Method chaining
  • Martin Fowler’s discussion of a fluent interface

The Scala Cookbook

This tutorial is sponsored by the Scala Cookbook, which I wrote for O’Reilly:

You can find the Scala Cookbook at these locations:

Add new comment

The content of this field is kept private and will not be shown publicly.

Anonymous format

  • Allowed HTML tags: <em> <strong> <cite> <code> <ul type> <ol start type> <li> <pre>
  • Lines and paragraphs break automatically.
By submitting this form, you accept the Mollom privacy policy.