Difference between revisions of "Lambdas"

From CSE231 Wiki
Jump to: navigation, search
Line 5: Line 5:
 
In Habanero-Java, the async() method is used to spawn a task that will run asynchronously to the rest of your program. Ideally, the async() method would take a function as a parameter. The async() method could then call this function in parallel with the rest of your code. In Java, however, functions cannot be passed as parameters to methods. Only objects (and primitive types) can be passed in.
 
In Habanero-Java, the async() method is used to spawn a task that will run asynchronously to the rest of your program. Ideally, the async() method would take a function as a parameter. The async() method could then call this function in parallel with the rest of your code. In Java, however, functions cannot be passed as parameters to methods. Only objects (and primitive types) can be passed in.
  
To solve this problem, the async() method takes an HjSuspendable as a parameter. HjSuspendable is an interface which contains a single method: run(). You have to pass in an instance of a class that implements HjSuspendable--thus guaranteeing that it has a run() method--and the async() method will call the run() method of your object asynchronously.
+
To solve this problem, the async() method takes an HjSuspendable as a parameter. HjSuspendable is an interface which contains a single method: run(). You can read more about interfaces on the [[Interfaces]] page. The async() method requires you to pass in an instance of a class that implements HjSuspendable--thus guaranteeing that it has a run() method--and the async() method will call the run() method of your object asynchronously.
  
== Separate Classes ==
+
== Named Classes ==
  
 
A straightforward solution is to write your own class that implements HjSuspendable. Let's say, for example, that you want to print "Hello, World!" asynchronously. The pseudocode for this would be something like this:
 
A straightforward solution is to write your own class that implements HjSuspendable. Let's say, for example, that you want to print "Hello, World!" asynchronously. The pseudocode for this would be something like this:
Line 66: Line 66:
 
     }
 
     }
  
    @Override
 
 
     public void run() {
 
     public void run() {
 
         for (int i = 0; i < midpoint; i++)
 
         for (int i = 0; i < midpoint; i++)
Line 91: Line 90:
 
     }
 
     }
  
    @Override
 
 
     public void run() {
 
     public void run() {
  
 
         sumLeftHalf = new SumLeftHalf(array);
 
         sumLeftHalf = new SumLeftHalf(array);
         HabaneroClassic.async(sumLeftHalf);
+
         async(sumLeftHalf);
  
 
         for (int j = midpoint; j < array.length; j++)
 
         for (int j = midpoint; j < array.length; j++)
Line 110: Line 108:
  
 
  <nowiki>AsyncArraySum arraySum = new AsyncArraySum(array);
 
  <nowiki>AsyncArraySum arraySum = new AsyncArraySum(array);
HabaneroClassic.finish(arraySum);
+
finish(arraySum);
 
System.out.println(arraySum.getSum());</nowiki>
 
System.out.println(arraySum.getSum());</nowiki>
  
 
Note the incredible bulkiness of this code. This code is significantly longer than the pseudocode, and part of this stems from the fact that we are writing entire classes to contain methods. The other issue is that a method in one class does not have access to the local variables in another class. This fact requires us to create methods and fields to store and access these variables, making the code a lot less readable.
 
Note the incredible bulkiness of this code. This code is significantly longer than the pseudocode, and part of this stems from the fact that we are writing entire classes to contain methods. The other issue is that a method in one class does not have access to the local variables in another class. This fact requires us to create methods and fields to store and access these variables, making the code a lot less readable.
 +
 +
=== Nested Classes ===
 +
 +
One way to organize this code a bit better would be to make the AsyncArraySum and SumLeftHalf classes nested inside the original class. This would make the code a bit more readable and would also provide more access to local variables. You can read more about nested classes from the [https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html Java Tutorials].
 +
 +
== Anonymous Classes ==
 +
 +
Anonymous classes have existed in Java since Java 1.1, and they've been the best solution to this problem up until Java 8. Anonymous classes allow you to simplify your code by declaring an unnamed class within the method of another class. Anonymous classes are good for short, one-time-use-only classes, exactly what we're looking for here. You can read more about anonymous classes from the [https://docs.oracle.com/javase/tutorial/java/javaOO/anonymousclasses.html Java Tutorials]. Here is some example syntax for our code:
 +
 +
<nowiki>int mid = array.length / 2;
 +
int[] subSums = new int[2];
 +
 +
finish(new HjSuspendable() {
 +
 +
    public void run() {
 +
 +
        async(new HjSuspendable() {
 +
 +
            public void run() {
 +
                for (int i = 0; i < mid; i++) {
 +
                    subSums[0] += array[i];
 +
                }
 +
            }
 +
 +
        });
 +
 +
        for (int j = mid; j < array.length; j++) {
 +
            subSums[1] += array[j];
 +
        }
 +
    }
 +
 +
});
 +
System.out.println(subSums[0] + subSums[1]);</nowiki>
 +
 +
Notice that the array and subSums variables can be accessed within and outside of the anonymous class. This is a key feature that anonymous inner classes have that regular classes don't. Java allows us to access variables in an entirely different scope, '''so long as they are final or effectively final'''. What it is doing behind the scenes is functionally similar to our named classes example. It passes a reference to the subSums array, and it doesn't allow you to change the reference in the enclosing class because it would be too complicated to then change it in the anonymous class instance. This is a key limitation to anonymous classes, and it leads to some annoying issues in HW1.
 +
 +
== Lambdas ==
 +
 +
Lambdas, added in Java 8, make the syntax for anonymous classes significantly simpler. They are allowed only when an interface has a single method. In this case, the HjSuspendable class has only one method: run(). The syntax using lambdas would look like this:
 +
 +
<nowiki>int mid = array.length / 2;
 +
int[] subSums = new int[2];
 +
 +
finish(() -> {
 +
 +
    async(() -> {
 +
        for (int i = 0; i < mid; i++) {
 +
            subSums[0] += array[i];
 +
        }
 +
 +
    });
 +
 +
    for (int i = mid; i < array.length; i++) {
 +
        subSums[1] += array[i];
 +
    }
 +
 +
});
 +
System.out.println(subSums[0] + subSums[1]);</nowiki>
 +
 +
Lambdas act very similar to anonymous classes, but the syntax is significantly clearer. Our code is easier to read and it makes sense what it's doing on a high level--passing a method as a parameter to another method.

Revision as of 23:18, 31 January 2017

Lambdas are one of the most recently-added features in Java, essentially allowing one to pass a function as an parameter to a method. The functionality provided by lambdas has always been available, but lambdas allow the syntax to be significantly more succinct. This page will discuss alternatives to lambdas in order to better explain what lambdas are doing behind the scenes. It is based on the Java Tutorials, but has been adapted for the code used more frequently in CSE 231.

The Problem

In Habanero-Java, the async() method is used to spawn a task that will run asynchronously to the rest of your program. Ideally, the async() method would take a function as a parameter. The async() method could then call this function in parallel with the rest of your code. In Java, however, functions cannot be passed as parameters to methods. Only objects (and primitive types) can be passed in.

To solve this problem, the async() method takes an HjSuspendable as a parameter. HjSuspendable is an interface which contains a single method: run(). You can read more about interfaces on the Interfaces page. The async() method requires you to pass in an instance of a class that implements HjSuspendable--thus guaranteeing that it has a run() method--and the async() method will call the run() method of your object asynchronously.

Named Classes

A straightforward solution is to write your own class that implements HjSuspendable. Let's say, for example, that you want to print "Hello, World!" asynchronously. The pseudocode for this would be something like this:

async {
  print "Hello, World!"
}

We could create a class called PrintHelloWorld that implements HjSuspendable. It would look something like this:

public class PrintHelloWorld implements HjSuspendable {

    @Override
    public void run() throws SuspendableException {
        System.out.println("Hello, World!");
    }

}

An instance of this class could then be passed into the async() method. For example:

HabaneroClassic.async(new PrintHelloWorld());

This code would perform the way we would expect from the pseudocode.

Now let's try a more complex example. Let's try to recreate the ArraySum program using this method. The pseudocode for this program would look like this:

finish {

    async {
        leftSum = 0
        for j in [0, midpoint) {
            leftSum += array[j]
        }
    }

    rightSum = 0;
    for j in [midpoint, array.length) {
        rightSum += array[j]
    }

}

print leftTotal + rightTotal

For this program, we'd need to write two classes: one for the HjSuspendable object passed to the finish() method, and one for the HjSuspendable object passed to the async() method. There is an issue, however. Several variables need to be accessed both inside and outside of the async. The array, instantiated outside of the finish, must be accessible within both the finish and the async. The leftTotal and rightTotal variables, instantiated inside of the finish, need to be accessible outside of it. Because we're creating a new class, this can easily be accomplished by creating instance variables within our new classes. The code would look something like this:

class SumLeftHalf implements HjSuspendable {

    int[] array;
    int midpoint;

    int leftSum;

    public SumLeftHalf(int[] array) {
        this.array = array;
        midpoint = array.length / 2;
    }

    public void run() {
        for (int i = 0; i < midpoint; i++)
            leftSum += array[i];
    }

    int getLeftSum() {
        return leftSum;
    }

}
class AsyncArraySum implements HjSuspendable {

    int[] array;
    int midpoint;

    SumLeftHalf sumLeftHalf;
    int rightSum;

    AsyncArraySum(int[] array) {
        this.array = array;
        midpoint = array.length / 2;
    }

    public void run() {

        sumLeftHalf = new SumLeftHalf(array);
        async(sumLeftHalf);

        for (int j = midpoint; j < array.length; j++)
            rightSum += array[j];

    }

    int getSum() {
        int leftSum = sumLeftHalf.getLeftSum();
        return leftSum + rightSum;
    }

}
AsyncArraySum arraySum = new AsyncArraySum(array);
finish(arraySum);
System.out.println(arraySum.getSum());

Note the incredible bulkiness of this code. This code is significantly longer than the pseudocode, and part of this stems from the fact that we are writing entire classes to contain methods. The other issue is that a method in one class does not have access to the local variables in another class. This fact requires us to create methods and fields to store and access these variables, making the code a lot less readable.

Nested Classes

One way to organize this code a bit better would be to make the AsyncArraySum and SumLeftHalf classes nested inside the original class. This would make the code a bit more readable and would also provide more access to local variables. You can read more about nested classes from the Java Tutorials.

Anonymous Classes

Anonymous classes have existed in Java since Java 1.1, and they've been the best solution to this problem up until Java 8. Anonymous classes allow you to simplify your code by declaring an unnamed class within the method of another class. Anonymous classes are good for short, one-time-use-only classes, exactly what we're looking for here. You can read more about anonymous classes from the Java Tutorials. Here is some example syntax for our code:

int mid = array.length / 2;
int[] subSums = new int[2];

finish(new HjSuspendable() {

    public void run() {

        async(new HjSuspendable() {

            public void run() {
                for (int i = 0; i < mid; i++) {
                    subSums[0] += array[i];
                }
            }

        });

        for (int j = mid; j < array.length; j++) {
            subSums[1] += array[j];
        }
    }

});
System.out.println(subSums[0] + subSums[1]);

Notice that the array and subSums variables can be accessed within and outside of the anonymous class. This is a key feature that anonymous inner classes have that regular classes don't. Java allows us to access variables in an entirely different scope, so long as they are final or effectively final. What it is doing behind the scenes is functionally similar to our named classes example. It passes a reference to the subSums array, and it doesn't allow you to change the reference in the enclosing class because it would be too complicated to then change it in the anonymous class instance. This is a key limitation to anonymous classes, and it leads to some annoying issues in HW1.

Lambdas

Lambdas, added in Java 8, make the syntax for anonymous classes significantly simpler. They are allowed only when an interface has a single method. In this case, the HjSuspendable class has only one method: run(). The syntax using lambdas would look like this:

int mid = array.length / 2;
int[] subSums = new int[2];

finish(() -> {

    async(() -> {
        for (int i = 0; i < mid; i++) {
            subSums[0] += array[i];
        }

    });

    for (int i = mid; i < array.length; i++) {
        subSums[1] += array[i];
    }

});
System.out.println(subSums[0] + subSums[1]);

Lambdas act very similar to anonymous classes, but the syntax is significantly clearer. Our code is easier to read and it makes sense what it's doing on a high level--passing a method as a parameter to another method.