Loan Pattern

In OO programming, classes (or objects) abstract and encapsulate behavior and data. Function values also abstract and encapsulate behavior. And rather than holding on to state, they can help transform state.

Now let’s look at two examples where function values come in handy.

Continuing with the stock prices examples in this series, you’re asked to write a function that will total the prices given in a collection. You figure a simple iteration is sufficient, and you write:

  val prices = List(10, 20, 15, 30, 45, 25, 82)
  def totalPrices(prices : List[Int]) = {
  prices.foldLeft(0) { (total, price) =>
  total + price
  }
  }

In the totalPrices function, the foldLeft method of the list is used to compute the total in a functional style with pure immutability. You pass a function value to the foldLeft method. This function value accepts two parameters and returns the total of these two parameters. ThefoldLeft method invokes the function value as many times as the number of elements in the list. The first time, total and price are bound to the value 0 (passed in as parameter to thefoldLeft method) and the first element in the list, respectively. In the second call, total is bound to the total returned from the previous call to the function value and price is bound to the second element in the collection. The foldLeft function iterates this sequence of calls for the remaining elements in the collection.

Exercise your totalPrices function to see the result.

  println("Total of prices is " + totalPrices(prices))
  //Total of prices is 227

Before you could declare this done, you’re asked to write one more function, to total only prices that are greater than a given value. Clearly you could reuse most of the code from that little function you just wrote. Looking at your watch (you have those pesky meetings to attend) you say to yourself, “there’s a reason God created copy-and-paste,” and you end up with the following function.

  def totalOfPricesOverValue(prices : List[Int], value : Int) = {
  prices.foldLeft(0) { (total, price) =>
  if (price > value) total + price else total
  }
  }

Sadly, the demand for features seems to be relentless today and you’re asked for yet another function, this time to total only prices that are less than a given value. You know that copying and pasting code anymore is morally wrong, but you decide to make this work for now and refactor it to make it better right after that meeting you have to run to.

Right after the meeting, you stare at the following version of the code:

  val prices = List(10, 20, 15, 30, 45, 25, 82)
  def totalPrices(prices : List[Int]) = {
  prices.foldLeft(0) { (total, price) =>
  total + price
  }
  }
  def totalOfPricesOverValue(prices : List[Int], value : Int) = {
  prices.foldLeft(0) { (total, price) =>
  if (price > value) total + price else total
  }
  }
  def totalOfPricesUnderValue(prices : List[Int], value : Int) = {
  prices.foldLeft(0) { (total, price) =>
  if (price < value) total + price else total
  }
  }

Let’s exercise it.

  println("Total of prices is " + totalPrices(prices))
  //Total of prices is 227
  println("Total of prices over 40 is " +
  totalOfPricesOverValue(prices, 40))
  //Total of prices over 40 is 127
  println("Total of prices under 40 is " +
  totalOfPricesUnderValue(prices, 40))
  //Total of prices under 40 is 100

You have good intentions to make it work and make it better, but you want to quickly refactor it to remove the duplications, before your colleagues accuse you of revealing your dark side through this code. Function values to the rescue here.

If you slightly modify the first function, to if (true) total + price, then you notice the only difference between the three function bodies is the conditional expression in the ifstatement. You can extract this condition as a function value.

This extracted function value would accept an Int as parameter and return a Boolean. You can express this as a mapping or transformation from Int to Boolean, or selector : Int => Boolean. Just as prices : List[Int] represents a reference prices of type List[Int],selector : Int => Boolean represents a reference selector of type function value that accepts an Int and returns a Boolean.

Now you can replace the three previous functions with one function:

  def totalSelectPrices(prices : List[Int],
  selector : Int => Boolean) = {
 
  prices.foldLeft(0) { (total, price) =>
  if (selector(price)) total + price else total
  }
  }

The function totalSelectPrices accepts as parameter a collection and a function value. Within the function, in the if condition, you call the function value with the price as parameter. If the selector function value returns a true, you add that price to the total; ignore the price otherwise.

Exercise the code and ensure that this version produces the same result as the three functions in the earlier version.

  println("Total of prices is " +
  totalSelectPrices(prices, { price => true }))
  //Total of prices is 227
  println("Total of prices over 40 is " +
  totalSelectPrices(prices, { price => price > 40 }))
  //Total of prices over 40 is 127
  println("Total of prices under 40 is " +
  totalSelectPrices(prices, { price => price < 40 }))
  //Total of prices under 40 is 100

You pass both the collection and the function value as parameters, within the parentheses (), to the totalSelectPrices function. You can also pass the function value outside of the parentheses; however, you have to do a bit more work for that.

It’s time to meet multiple parameter lists. We’re mostly used to single parameter lists with multiple parameters. However, in Scala you can also have multiple parameter lists, each with multiple parameters.

  def totalSelectPrices(
  prices : List[Int])(selector : Int => Boolean) = {
  prices.foldLeft(0) { (total, price) =>
  if (selector(price)) total + price else total
  }
  }

In this version of the totalSelectPrices function, rather than accepting two parameters in one parameter list, you have two parameter lists, each with one parameter. This allows you to invoke the method using the following syntax:

  totalSelectPrices(prices) { price => price > 40 }

You’re attaching the function call to the end of the method call, like it is a parasite (in a good way) on the totalSelectPrices function. This syntax is a common way to pass function values to functions.

Scala allows you to pass function names in place of function values. So, if you have to pass the same function value to multiple functions, you have a few options, as shown below:

  Console println "Total of prices over 40 is " +
  totalSelectPrices(prices) { price => price > 40 }
  //Total of prices over 40 is 127
  val returnTrue = { price : Int => true }
  Console println "Total of prices is " +
  totalSelectPrices(prices)(returnTrue)
  //Total of prices is 227
  def isLessThan40(price : Int) = price < 40
  Console println "Total of prices under 40 is " +
  totalSelectPrices(prices) { isLessThan40 }
  //Total of prices under 40 is 100

In the first call to totalSelectPrices you pass a just-in-time created function value. In the second call you pass returnTrue, which is an immutable reference to a function value. In the third call, you use yet another way: you pass a function as a function value. You can use either the () or the {} syntax to pass returnTrue and isLessThan40.

You saw different ways to create function values and pass them to functions. Let’s take a look at one more example of the use of function values.

Suppose you have a Resource class:

  class Resource {
  println("Creating Resource")
 
  def someop1() { println("someop1") }
  def someop2() { println("someop2") }
 
  def close() { println("cleaning-up...") }
  }

If an instance of Resource takes up significant resources outside the JVM, like database connections, open files, handles to external systems with significant memory usage, etc., you’d want to clean up the resource quickly after use. You can’t rely on the JVM’s garbage collector (GC), as you can’t predict when it would run and your memory usage within the JVM may be too low to trigger the GC reasonably soon to clean up the external resources. Users of your class may be tempted to write code like this:

  val resource = new Resource
  resource.someop1()
  resource.someop2()
  //forgot to call close
  //Creating Resource
  //someop1
  //someop2

In this case, forgetful programmers like me will omit the call to the close method. Even if they remember, a simple call to close is not sufficient. An exception before the code reachesclose will prevent proper cleanup. So the right way to approach this would be:

  val resource = new Resource
  try {
  resource.someop1()
  resource.someop2()
  } finally {
  resource.close()
  }
  //Creating Resource
  //someop1
  //someop2
  //cleaning-up...

This is too verbose and taxes the forgetful programmers even more each time an instance of your Resource class is created.

(As an aside, Java 7 has a feature named Automatic Resource Management (ARM) to make this less verbose; however, it still requires the programmers to remember to do the right thing.)

If the resource has to be cleaned up quickly, why not do it for the programmers rather than expecting them to remember? You can do that using function values.

You can force (kindly) the programmer to use your class in a certain way, so they don’t have to suffer the consequences of forgetting, but instead can be guided by the compiler to do the right thing.

First, let’s make the constructor private. This will prevent the users of your class from creating an arbitrary instance. Make the close method private as well; that way they don’t have to worry about calling it, as you’ll take care of it. That leaves only the instance functions likesomeop1() and someop2() public.

  class Resource private {
  println("Creating Resource")
 
  def someop1() { println("someop1") }
  def someop2() { println("someop2") }
 
  private def close() { println("cleaning-up...") }
  }

With this change, calls like new Resource() will result in a compilation error. It’s time to manage the creation and cleanup of the instances of Resource.

  object Resource {
  def use[T](block : Resource => T) = {
  val resource = new Resource
  try {
  block(resource)
  } finally {
  resource.close()
  }
  }
  }

You created a companion object (see Scala for the Intrigued: Cute Classes and Pure OO in the November edition of this magazine) for the Resource class. The use method accepts a function value, called block, as a parameter and returns whatever the block returns. You specify that the block should receive an instance of Resource and may return anything, expressed as parametric type T.

Within the use method, you create an instance of Resource. Remember companion objects have full access to the companion class, so the constructor and the close method being private is not an issue from within here. Then within the safe haven of the try-finallystatements, you pass the Resource instance to the function value. In the finally block, you call close to clean up the instance.

Users of your Resource can now use an instance and have it automatically cleaned up with ease, as follows:

  Resource.use { resource =>
  resource.someop1()
  resource.someop2()
  }
  //Creating Resource
  //someop1
  //someop2
  //cleaning-up

By using the function value, you’ve made it easy for the programmers to do the right thing. They don’t have to endure the consequences of forgetting to clean up; you’ve done it for them. This approach is called the loan pattern, and it also goes by the name execute around method pattern. Next time you write a method, add a little spice to your code, and see how you can benefit from function values.

你可能感兴趣的:(scala)