© Toby Weston 2018

Toby Weston, Scala for Java Developers, https://doi.org/10.1007/978-1-4842-3108-1_10

10. Classes and Functions

Toby Weston

(1)London, UK

In this chapter we’ll look at the following:

  • Anonymous functions or lambdas.

  • How anonymous classes differ from anonymous functions.

  • First-class and higher-order functions.

  • The differences between a function and a method.

  • The differences between lambdas and closures.

Anonymous Functions

A function in the general sense is a code fragment designed to perform a specific task or calculation. You can create a function as a Java method or a Scala def. Both by their nature are named. Anonymous functions, on the other hand, do not have a name.

An anonymous function is also called a lambda and when used can be referred to as a function literal .

Java lambda syntax starts by listing the arguments to the function then the arrow token followed by the implementation body. The following example is a lambda taking two string arguments and comparing their values for a sorting algorithm.

  // java
  (String a, String b) -> {
      int comparison = a.compareTo(b);
      return comparison;
  };

If the compiler can infer the types of the argument, you can drop the types and, if the body is a single line expression, you can drop the return.

  (a, b) -> a.compareTo(b);         // abbreviated form

For single argument lambdas, you can even drop the parameter parenthesis but if there are no arguments, you need empty parenthesis.

  value -> value * 2;               // single argument
  () -> 2 + 2;                      // no arguments

In Scala, a lambda looks very similar. As the last statement in an expression is assumed to be the return value, the return keyword is not required.

  // scala
  (a: String, b: String) => {
    val comparison = a.compareTo(b)
 comparison
  }

If Scala can infer the arguments, the types can be dropped as in Java and single line expressions don’t require braces.

  // scala
  (a, b) => a.compareTo(b)          // abbreviated form

Anonymous classes can be used to provide functionality in a similar way to anonymous functions but they can not accurately be called lambdas.

Anonymous Classes

An anonymous class isn’t the same thing as an anonymous function. An anonymous class still needs to be instantiated to an object via the new keyword. It may not have a name but it’s only useful when it’s an instance object.

An anonymous function, on the other hand, has no instance associated with it. Functions are disassociated with the data they act on.

A typical anonymous class in Java might look like this:

  // java
  List<String> list = Arrays.asList("A", "C", "B", "D");
  list.sort(new Comparator<String>() {
      @Override
      public int compare(String a, String b) {
          return a.compareTo(b);
      }
  });

You can still create anonymous classes in Scala but you don’t often need to. In Scala it’s more common to create an anonymous function or lambda. Indeed, since the introduction of lambdas in Java 8, the same is also true of Java.

  // scala
  val list = List("A", "C", "B", "D")
  list.sorted(new Ordering[String] {
    def compare(a: String, b: String): Int = a.compareTo(b)
  })

Just as with Java, to create an anonymous class instance in Scala, you use the new keyword with braces and define the implementation within the body. Notice that you don’t need to add the constructor brackets (new Ordering[String]()).

First-class Functions

As we’ve seen, an anonymous function is called a lambda. It’s a compact way to define a function. Anonymous functions are useful when you want to define calculations or transformations and apply them to different values later. For example, passing functions into other functions or storing function definitions for later use.

This kind of usage is referred to as treating functions as first-class citizens. Specifically, a language supports first-class functions, if it:

  1. Supports passing functions as arguments into other functions.

  2. Can return functions as values from other functions.

  3. Can store them in variables or within data structures.

Lambdas facilitate first-class function support by providing a compact syntax and by the fact that the compiler generates special bytecode for the lambda syntax.1

A lambda defining the preceding sorting calculations looks like this:

  (String a, String b) -> a.compareTo(b);     // java

  (a: String, b: String) => a.compareTo(b)    // scala

Passing in Functions

Why would you want to pass a function into a function. Short answer: for flexibility. The sort or sorted functions given earlier are a good example . You can create one function to sort a list but allow the caller to influence how it’s sorted.

For example, you can sort the list by ascending or descending order by passing in different function literals.

  // java
  List<String> list = Arrays.asList("A", "C", "B", "D");
  list.sort((a, b) -> a.compareTo(b));                     // ascending
  list.sort((a, b) -> b.compareTo(a));                     // descending

The same is true for Scala:

  // scala
  val list = List("A", "C", "B", "D")
  list.sorted((a: String, b: String) => a.compareTo(b))    // ascending
  list.sorted((a: String, b: String) => b.compareTo(a))    // descending

Returning Functions

Returning functions from functions is useful to decouple implementation from behavior. You might want to create factory style functions to be passed around or create calculations but don’t have all the values yet.

An example might be that you want to convert currency amounts from US dollars. A simple enough function to write, a function that takes a target currency and dollar amount. However, let’s say that you want to call this in lots of places but don’t want to pass around the target currency everywhere.

If you create a function to create the function, you can call that just once (when you know the target currency) and pass it around instead. It saves you passing around implementation details (in this case the target currency) and so decouples clients from the details.

So rather than the imperative way like this:

  // scala
  def dollarTo(currency: String, dollar: Double) = {
    if (currency == "GBP") dollar * 0.76
    else if (currency == "EUR") dollar * 0.83
    else dollar
  }

…you’d create a higher-order function that takes the target currency but returns a function to be evaluated later. That function would take a currency amount and return the converted amount, as below.

  // scala
  def dollarTo(currency: String): Double => Double = {
    if (currency == "GBP") dollar => dollar * 0.76
    else if (currency == "EUR") dollar => dollar * 0.83
    else dollar => dollar
  }

Then rather than lock yourself into the implementation of the imperative version anywhere you call it — for example, from the 'calculateTicketPrice' function below):

  // scala
  def calculateTicketPrice(targetCurrency: String) = {
    dollarTo(targetCurrency, 199.99)
    // ...
  }

…you instead pass in a function signature anywhere you want the conversion.

  def calculateTicketPrice(currencyConversion: Double=> Double) = {
    currencyConversion(199.99)
    // ...
  }

You can think of it as an informal interface; any function that converts one double to another can be used here. Instead of using the one above, you could pass in a function that calls a web service to get latest FX rates or one that shows historical rates without having to change any client code. Neat.

  calculateTicketPrice(dollarTo("GBP"))
  calculateTicketPrice(yahooFxRateFor("GBP"))
  calculateTicketPrice(historicalRateFor("GBP"))

Storing Functions

The following is an example of a lambda to add two numbers together:

  (Integer a, Integer b) -> a + b;        // java

If you want to store this as a value, you need a type. Under the hood, Java treats lambdas as an instance of a single method interface (SAM) and provides suitable interfaces in the java.util.function package to do so.

It defines java.util.function.Function to represent a single argument function and java.util.function.BiFunction for a function with two arguments.

You can therefore assign the lambda like this:

  // java
  BiFunction<Integer, Integer, Integer> plus = (Integer a, Integer b) -> a+b;

The BiFunction defines the two input arguments T and U and return value R. So, the above represents a function with two integer arguments, returning another integer.

  public interface BiFunction<T, U, R> {
      R apply(T t, U u);
  }

As the variable declaration includes the type information, Java allows you to drop the repetition in the lambda’s arguments:

  // java
  BiFunction<Integer, Integer, Integer> plus = (a, b) -> a + b;

To execute or apply the function, you’d use the following syntax:

  // java
  plus.apply(2, 2);                     // 4
  plus.apply(plus.apply(2, 2), 2);      // 6

The Scala version of the basic lambda is as you’d expect:

  (a: Int, b: Int) => a + b             // scala

Although Scala uses the same trick under the covers to represent a function as a type, in this case scala.Function2[Int, Int, Int] it also allows a more succinct representation. So, although you can write it out long hand like this:

  val plus: Function2[Int, Int, Int] = (a: Int, b: Int) => a + b   // scala

…it’s more concise to use the function type (Int, Int) => Int:

  val plus: (Int, Int) => Int = (a: Int, b: Int) => a + b          // scala

Again, with the additional type information on the val, you can drop the repetition in the lambda arguments.

  val plus: (Int, Int) => Int = (a, b) => a + b                    // scala

Calling it is more succinct as Scala will automatically call the apply method.

  // scala
  plus(2, 2)              // 4
  plus(plus(2, 2), 4)     // 8

Function Types

Both Java and Scala support functions as first-class citizens as they support all the preceding points. However, an academically interesting difference is that Java doesn’t support function types whereas Scala does.

Although you can define a lambda’s type using Function2 in Scala as follows:

  val f: Function2[String, String, Int] = (a, b) => a.compareTo(b)

…you can also define it using a function type.

  val f: (String, String) => Int = (a, b) => a.compareTo(b)

The type declaration to the left of the assignment is known as a function type. (String, String) => Int is a full type in Scala, it declares a function from two string arguments returning an integer. It can be used anywhere an ordinary type declaration can be used; to describe the type of a value, as we’ve done here, an argument to a function, or a return type.

Functions vs. Methods

In Scala, a def can be used as a function or a method. Scala developers often talk about functions and shy away from talking about methods, so what’s the difference?

A method def will be defined within a Scala class, it will usually refer to instance data within the class. Its nomenclature is firmly in the object-oriented world; methods define behaviors for instance objects.

Function defs are defined in Scala singleton objects (but, if defined in a class, can be converted into functions under certain conditions). Functions are firmly from the mathematical world, they take inputs and produce outputs. In the functional programming world, they don’t define behaviors for instance objects but instead implement independent, repeatable transformations or apply calculations.

The difference between a function and a method then is partly about describing the context of their use; you talk about methods when you’re building object-oriented systems and functions when talking about transformations or functional programming. Methods will also be associated with instance objects and functions will not.

There are several ways to create functions in Scala.

  1. A def in a singleton object is inherently a function.

  2. Create a lambda.

  3. Create a def in a class which Scala will convert to a function when passed to a higher-order function.

There are only two ways to create functions in Java.

  1. Create static class method.

  2. Create a lambda.

There is special type of lambda called a closure. In both Java and Scala, these are created differently under the hood. They are closer to anonymous classes than functions as a new instance is created when they are defined. You can therefore argue that they are not “functions” in the sense I’m describing here.

Anonymous Class != Anonymous Function

When you create an anonymous class, you “new up” an instance. A function has no instance associated with it and therefore, an instance of an anonymous class can not be called an anonymous function, even though it can be used and passed around like a first-class citizen.

Lambdas vs. Closures

A closure is a special type of lambda . It’s an anonymous function but it also captures something from its environment when created.

Every closure is a lambda but not all lambdas are closures! To make it more confusing, an anonymous class can also be called a closure when in captures some data.

Let’s take a look at an example. Given the following interface Condition:

  // java
  interface Condition {
      Boolean isSatisfied();
  }

…an anonymous class might implement Condition to check if some server has shutdown (it calls waitFor to poll continuously until the isRunning method returns false).

  // java
  void anonymousClass() {
      final Server server = new HttpServer();
      waitFor(new Condition() {
          @Override
          public Boolean isSatisfied() {
              return !server.isRunning();
          }
      });
  }

The functionally equivalent closure would look like this:

  // java
  void closure() {
      Server server = new HttpServer();
      waitFor(() -> !server.isRunning());
  }

In the interest of completeness, the waitFor method might naively be implemented like this:

  // java
  class WaitFor {
      static void waitFor(Condition condition) throws InterruptedException {
          while (!condition.isSatisfied())
              Thread.sleep(250);
      }
  }

Both implementations are closures; the former is an anonymous class and a closure, the latter is a lambda and a closure. As we’ve said, a closure captures its "environment" at runtime. In the anonymous class version, this means copying the value of server into an anonymous instance of Condition. In the lambda, the server variable would also need to be copied into the function at runtime.

As it’s a copy, it has to be declared final to ensure that it can not be changed between when it’s captured and when it’s used. The value at these two points in time could be very different as closures are often used to defer execution until some later point.

Java uses a neat trick whereby, if it can reason that a variable is never updated, it might as well be final, so it treats it as effectively final and you don’t need to declare it using the final keyword explicitly.

A lambda, on the other hand, doesn’t need to copy its environment or capture any terms. This means it can be treated as a genuine function and not an instance of a class.

The same example expressed as a lambda and not a closure would look like this:

  // java
  void lambda() {
      Server httpServer = new HttpServer();
      waitFor(server -> !server.isRunning(), httpServer);
  }


  void waitFor(ServerCondition condition, Server server) {
      while (!condition.isSatisfied(server))
          Thread.sleep(250);
  }


  interface ServerCondition {
      Boolean isSatisfied(Server server);
  }

The trick here is that the lambda takes the httpServer instance as an argument and so doesn’t need to capture it on construction. It doesn’t reference anything outside of its scope so nothing is copied in. The same lambda instance can be reused with different instances of a server.

It’s an important distinction to make as a lambda doesn’t need to be instantiated many times. A closure has to be instantiated and memory allocated for each value it closes over. Memory need only be allocated once for non-closure lambdas and then the lambda can be reused. This makes it more efficient, at least in theory.

Footnotes

1 Lambdas are called using the invokedynamic bytecode. It’s more efficient than generating new classes and instantiating them as is the case for anonymous classes.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.212.217