2018年1月2日 星期二

[ FP with Java ] Ch6 - Dealing with optional data - Part1

Preface 
This chapter covers 
* The null reference, or “the billion-dollar mistake”
* Alternatives to null references
* Developing an Option data type for optional data
* Applying functions to optional values
* Composing optional values
* Option use cases

Representing optional data in computer programs has always been a problem. The concept of optional data is very simple in everyday life. Representing the absence of something when this something is contained in a container is easy—whatever it is, it can be represented by an empty container. An absence of apples can be represented by an empty apple basket. The absence of gasoline in a car can be visualized as an empty gas tank. Representing the absence of data in computer programs is more difficult. Most data is represented as a reference pointing to it, so the most obvious way to represent the absence of data is to use a pointer to nothing. This is what a null pointer is. 

In Java, a variable is a pointer to a value. Variables may be created null (static and instance variables are created null by default), and they may then be changed to point to values. They can even be changed again to point to null if data is removed. To handle optional data, Java 8 introduced the Optional type. However, in this chapter, you’ll develop your own type, which you’ll call Option. The goal is to learn how this kind of structure works. After completing this chapter, you should feel free to use the standard Java 8 library version Optional, but you’ll see in the upcoming chapters that it’s much less powerful than the type you’ll create in this chapter. 

Problems with the null pointer 
One of the most frequent bugs in imperative programs is the NullPointerException. This error is raised when an identifier is dereferenced and found to be pointing to nothing. In other words, some data is expected but is found missing. Such an identifier is said to be pointing to null. The null reference was invented in 1965 by Tony Hoare while he was designing the ALGOL object-oriented language. Here’s what he said 44 years later: 
Tony Hoare, “Null References: The Billion Dollar Mistake” (QCon, August 25, 2009), http://mng.bz/l2MC.
I call it my billion-dollar mistake ... My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.

Although it should be well known nowadays that null references should be avoided, that’s far from being the case. The Java standard library contains methods and constructors taking optional parameters that must be set to null if they’re unused. Take, for example, the java.net.Socket class. This class defines the following constructor: 
  1. public Socket(String address,  
  2.               int port,  
  3.               InetAddress localAddr,  
  4.               int localPort throws IOException  
According to the documentation, If the specified local address is null, it is the equivalent of specifying the address as the AnyLocal address. Here, the null reference is a valid parameter. This is sometimes called a business null. Note that this way of handling the absence of data isn’t specific to objects. The port may also be absent, but it can’t be null because it’s a primitive: 
A local port number of zero will let the system pick up a free port in the bind operation.

This kind of value is sometimes called a sentinel value. It’s not used for the value itself (it doesn’t mean port 0) but to specify the absence of a port value. There are many other examples of handling the absence of data in the Java library. This is really dangerous because the fact that the local address is null could be unintentional and due to a previous error. But this won’t cause an exception. The program will continue working, although not as intended. 

There are other cases of business nulls. If you try to retrieve a value from a HashMap using a key that’s not in the map, you’ll get a null. Is this an error? You don’t know. It might be that the key is valid but has not been registered in the map; or it might be that the key is supposedly valid and should be in the map, but there was a previous error while computing the key. For example, the key could be null, whether intentionally or due to an error, and this wouldn’t raise an exception. It could even return a non-null value because the null key is allowed in a HashMap. This situation is a complete mess. 

Of course, you know what to do about this. You know that you should never use a reference without checking whether it’s null or not. (You do this for each object parameter received by a method, don’t you?And you know that you should never get a value from a map without first testing whether the map contains the corresponding key. And you know that you should never try to get an element from a list without verifying first that the list is not empty and that it has enough elements if you’re accessing the element through its index. And you do this all the time, so you never get a NullPointerException or an IndexOutOfBoundsException

If you’re this kind of perfect programmer, you can live with null references. But for the rest of us, an easier and safer way of dealing with the absence of a value, whether intentional or resulting from an error, is necessary. In this chapter, you’ll learn how to deal with absent values that aren’t the result of an error. This kind of data is called optional data

Tricks for dealing with optional data have always been around. One of the best known and most often used is the list. When a method is supposed to return either a value or nothing, some programmers use a list as the return value. The list may contain zero or one element. Although this works perfectly, it has several important drawbacks: 
* There’s no way to ensure that the list contains at most one element. What should you do if you receive a list of several elements?
* How can you distinguish between a list that’s supposed to hold at most one element and a regular list?
* The List class defines many methods and functions to deal with the fact that lists may contain several elements. These methods are useless for our use case.
* Functional lists are recursive structures, and you don’t need this. A much simpler implementation is sufficient.

Alternatives to null references 
It looks like our goal is to avoid the NullPointerException, but this isn’t exactly the case. The NullPointerException should always indicate a bug. As such, you should apply the “fail fast” principle: if there’s an error, the program should fail as fast as possible. Totally removing business nulls won’t allow you to get rid of the NullPointerException. It will just ensure that null references will only be caused by bugs in the program and not by optional data. 

The following code is an example of a method returning optional data: 
  1. static Function, Double> mean = xs -> {  
  2.   if (xs.isEmpty()) {  
  3.     ???;  
  4.   } else {  
  5.     return xs.foldLeft(0.0, x -> y -> x + y) / xs.length();  
  6.   }  
  7. };  
The mean function is an example of a partial function, as you saw in chapter 2: it’s defined for all lists except the empty list. How should you handle the empty list case? One possibility is to return a sentinel value. What value should you choose? Because the type is Double, you can use a value that’s defined in the Double class: 
  1. static Function, Double> mean = xs -> {  
  2.   if (xs.isEmpty()) {  
  3.     return Double.NaN;  
  4.   } else {  
  5.     return xs.foldLeft(0.0, x -> y -> x + y) / xs.length();  
  6.   }  
  7. };  
This works because Double.NaN (Not a Number) is actually a double value (note the lowercase d). Double.NaN is a primitive! So far so good, but you have three problems: 
* What if you want to apply the same principle to a function returning an Integer? There’s no equivalent to the NaN value in the integer class.
* How can you signal to the user of your function that it could return a sentinel value?
* How can you handle a parametric function, such as
  1. static  Function, B> f = xs -> {  
  2.   if (xs.isEmpty()) {  
  3.     ???;  
  4.   } else {  
  5.     return ...;  
  6.   };  

Another solution is to throw an exception: 
  1. static Function, Double> mean = xs -> {  
  2.   if (xs.isEmpty()) {  
  3.     throw new MeanOfEmptyListException();  
  4.   } else {  
  5.     return xs.foldLeft(0.0, x -> y -> x + y) / xs.length();  
  6.   }  
  7. };  
But this solution is ugly and creates more trouble than it solves: 
* Exceptions are generally used for erroneous results, but here there’s no error. There’s simply no result, and that’s because there was no input data! Or should you consider calling the function with an empty list a bug?
* What exception should you throw? A custom one (like in the example)? Or a standard one?
* Should you use a checked or unchecked exception? Moreover, your function is no longer a pure function. It’s no longer referentially transparent, which leads to the numerous problems I talked about in chapter 2. Also, your function is no longer composable.

You could also return null and let the caller deal with it: 
  1. static Function, Double> mean = xs -> {  
  2.   if (xs.isEmpty()) {  
  3.     return null;  
  4.   } else {  
  5.     return xs.foldLeft(0.0, x -> y -> x + y) / xs.length();  
  6.   }  
  7. };  
Returning null is the worst possible solution: 
* It forces (ideally) the caller to test the result for null and act accordingly.
* It will crash if boxing is used.
* As with the exception solution, the function is no longer composable.
* It allows the potential problem to be propagated far from its origin. If the caller forgets to test for a null result, a NullPointerException could be thrown from anywhere in the code.

A better solution would be to ask the user to provide a special value that will be returned if no data is available. For example, this function computes the maximum value of a list: 
  1. static  Function, B>> max = x0 -> xs -> {  
  2.   return xs.isEmpty()  
  3.     ? x0  
  4.     : ...;  
Here’s how you could define a max function: 
  1. static extends Comparable> Function, A>> max() {  
  2.   return x0 -> xs -> xs.isEmpty()  
  3.     ? x0  
  4.     : xs.tail().foldLeft(xs.head(), x -> y -> x.compareTo(y) < 0 ? x : y);  
  5. }  
Remember that you must use a method that returns the function because there’s no way to parameterize a property. If you find this too complex, here’s a functional method version: 
  1. static extends Comparable> A max(A x0, List xs) {  
  2.   return xs.isEmpty()  
  3.     ? x0  
  4.     : xs.tail().foldLeft(xs.head(), x -> y -> x.compareTo(y) < 0 ? x : y);  
  5. }  
This works, but it’s overcomplicated. The simplest solution would be to return a list: 
  1. public static extends Comparable> Function, List> max() {  
  2.   return xs -> xs.isEmpty()  
  3.     ? List.list()  
  4.     : List.list(xs.foldLeft(xs.head(), x -> y -> x.compareTo(y) < 0  
  5.                                                                 ? x : y));  
  6. }  
Although this solution works perfectly, it’s a bit ugly because the argument type and the return type of the function are the same, although they don’t represent the same thing. To solve this problem, you could simply create a new type, similar to List but with a different name indicating what it’s supposed to mean. And while you’re at it, you could select a more suitable implementation ensuring that this “list” will have at most one element

The Option data type 
The Option data type you’ll create in this chapter will be very similar to the List data type. Using an Option type for optional data allows you to compose functions even when the data is absent (see figure 6.1). It will be implemented as an abstract class, Option, containing two private subclasses representing the presence and the absence of data. The subclass representing the absence of data will be called None, and the subclass representing the presence of data will be called Some. A Some will contain the corresponding data value. 

Figure 6.1. Without the Option type, composing functions wouldn’t produce a function because the resulting program would potentially throw a NullPointerException. 

The following listing shows the code for these three classes. 
- Listing 6.1. The Option data type 
  1. package fp.utils;  
  2.   
  3. public abstract class Option {  
  4.     @SuppressWarnings("rawtypes")  
  5.     private static Option none = new None();  
  6.       
  7.     public abstract A getOrThrow();  
  8.     private static class None extends Option  
  9.     {  
  10.         private None(){}  
  11.         public A getOrThrow(){throw new IllegalStateException("get called on None");}  
  12.     }  
  13.     private static class Some extends Option  
  14.     {  
  15.         private final A value;  
  16.         private Some(A a){this.value = a;}  
  17.           
  18.         @Override  
  19.         public A getOrThrow(){return this.value;}  
  20.         @Override  
  21.         public String toString(){return String.format("Some(%s)"this.value);}  
  22.     }  
  23.     public static  Option some(A a){return new Some<>(a);}  
  24.     public static  Option none(){return none;}  
  25. }  
In this listing, you can see how close Option is to List. They’re both abstract classes with two private implementations. The None subclass corresponds to Nil and the Some subclass to Cons. The getOrThrow method is similar to the headmethod in List. You can use Option for your definition of the max function, as shown here: 
  1. static extends Comparable> Function, Option> max() {  
  2.   return xs -> xs.isEmpty()  
  3.       ? Option.none()  
  4.       : Option.some(xs.foldLeft(xs.head(),  
  5.                     x -> y -> x.compareTo(y) > 0 ? x : y));  
  6. }  
Now your function is a total function, which means it has a value for all lists, including the empty one. Note how similar this code is to the version returning a list. Although the implementation of Option is different from the List implementation, its usage is nearly the same. As you’ll see soon, the similarity extends much further. 

But as it is, the Option class isn’t very useful. The only way to use an Option would be to test the actual class to see if it’s a Some or a None, and call the getOrThrow method to obtain the value in the former case. And this method will throw an exception if there’s no data, which isn’t very functional. To make it a powerful tool, you’ll need to add some methods, in the same way you did for List

Getting a value from an Option 
Many methods that you created for List will also be useful for Option. In fact, only methods related to multiple values, such as folds, may be useless here. But before you create these methods, let’s start with some Option-specific usage. To avoid testing for the subclass of an Option, you need to define methods that, unlike getOrThrow, may be useful in both subclasses, so you can call them from the Option parent class. The first thing you’ll need is a way to retrieve the value in an Option. One frequent use case when data is missing is to use a default value. 

Let's implement a getOrElse method that will return either the contained value if it exists, or a provided default one otherwise. Here’s the method signature (Exercise 6.1): 
  1. A getOrElse(A defaultValue)  
The Some implementation is obvious and will simply return the value it contains: 
  1. public A getOrElse(A defaultValue) {  
  2.   return this.value;  
  3. }  
The None implementation will return the default value: 
  1. public A getOrElse(A defaultValue) {  
  2.   return defaultValue;  
  3. }  
So far so good. You can now define methods that return options and use the returned value transparently, as follows: 
  1. int max1 = max().apply(List.list(35721)).getOrElse(0);  
  2. int max2 = max().apply(List.list()).getOrElse(0);  
Here, max1 will be equal to 7 (the maximum value in the list), and max2 will be set to 0 (the default value). But you might be having a problem. Look at the following example: 
  1. int max1 = max().apply(List.list(35721)).getOrElse(getDefault());  
  2. System.out.println(max1);  
  3. int max2 = max().apply(List.list()).getOrElse(getDefault());  
  4. System.out.println(max2);  
  5.   
  6. int getDefault() {  
  7.   throw new RuntimeException();  
  8. }  
Of course, this example is a bit contrived. The getDefault method isn’t functional at all. This is only to show you what’s happening. What will this example print? If you think it will print 7 and then throw an exception, think again. This example will print nothing and will directly throw an exception because Java is a strict language. Method parameters are evaluated before the method is actually executed, whether they’re needed or not. The getOrElse method parameter is thus evaluated in any case, whether it’s called on a Some or a None. The fact that the method parameter isn’t needed for a Some is irrelevant. This makes no difference when the parameter is a literal, but it makes a huge difference when it’s a method call. The getDefault method will be called in any case, so the first line will throw an exception and nothing will be displayed. This is generally not what you want. 

Let's fix the previous problem by using lazy evaluation for the getOrElse method parameter (Exercise 6.2). Use the Supplier class you defined in chapter 3 (exercise 3.2). The signature of the method will be changed to: 
  1. public abstract A getOrElse(Supplier defaultValue);  
The Some implementation doesn’t change, except for the method signature, because the parameter isn’t used: 
  1. @Override  
  2. public A getOrElse(Supplier defaultValue) {  
  3.   return this.value;  
  4. }  
The most important change is in the None class: 
  1. @Override  
  2. public A getOrElse(Supplier defaultValue) {  
  3.   return defaultValue.get();  
  4. }  
In the absence of a value, the parameter is evaluated through a call to the Supplier.get() method. The max example can now be rewritten as follows: 
  1. int max1 = max().apply(List.list(35721))  
  2.                 .getOrElse(() -> getDefault());  
  3.   
  4. System.out.println(max1);  
  5. int max2 = max().apply(List.list()).getOrElse(() -> getDefault());  
  6. System.out.println(max2);  
  7. int getDefault() {  
  8.   throw new RuntimeException();  
  9. }  
This program prints 7 to the console before throwing an exception. Now that you have the getOrElse method, you don’t need the getOrThrow method any longer. But it might be useful when developing other methods for the Option class, so we’ll keep it and make it protected. 

Applying functions to optional values 
One very important method in List is the map method, which allows you to apply a function from A to B to each element of a list of A, producing a list of B. Considering that an Option is like a list containing at most one element, you can apply the same principle. Let's create a map method to change an Option into an Option by applying a function from A to B (Exercise 6.3). 

Define an abstract method in the Option class with one implementation in each subclass. The method signature in Option will be 

  1. public abstract  Option map(Function f)  
The None implementation is simple. You just have to return a None instance. As I said earlier, the Option class contains a None singleton that can be used for this: 
  1. public  Option map(Function f){  
  2.     return Option.none();  
  3. }  
Note that although this and none refer to the same object, you can’t return this because it’s parameterized with A. The none reference points to the same object, but with a raw type (no parameter). This is why you annotate none with @SuppressWarnings("rawtypes") in order to keep compiler warnings from leaking to the caller. In the same manner, you use a call to the none() factory method instead of directly accessing the none instance in order to avoid the “Unchecked assignment warning” that you already avoided in the none() method by using the @SuppressWarnings("unchecked") annotation. 

The Some implementation isn’t much more complex. All you need to do is get the value, apply the function to it, and wrap the result in a new Some
  1. public  Option map(Function f) {  
  2.   return new Some<>(f.apply(this.value));  
  3. }  
Dealing with Option composition 
As you’ll soon realize, functions from A to B aren’t the most common ones in functional programming. At first you may have trouble getting acquainted with functions returning optional values. After all, it seems to involve extra work to wrap values in Some instances and later retrieve these values. But with further practice, you’ll see that these operations occur only rarely. When chaining functions to build a complex computation, you’ll often start with a value that’s returned by some previous computation and pass the result to a new function without seeing the intermediate result. In other words, you’ll more often use functions from A to Option than functions from A to B. 

Think about the List class. Does this ring a bell? Yes, it leads to the flatMap method. Let's create a flatMap instance method that takes as an argument a function from A to Option and returns an Option (Exercise 6.4). You can define different implementations in both subclasses; but you should try to devise a unique implementation that works for both subclasses and put it in the Option class. Its signature will be: 

Try using some of the methods you already have (map and getOrElse). The trivial solution would be to define an abstract method in the Option class, return none() in the None class, and return f.apply(this.value) in the Some class. This is probably the most efficient implementation. But a more elegant solution is to map the f function, giving an Option, and then use the getOrElse method to extract the value (Option), providing None as the default value: 
  1. public  Option flatMap(Function> f) {  
  2.     return map(f).getOrElse(Option::none);  
  3. }  
Just as you needed a way to map a function that returns an Option (leading to flatMap), you’ll need a version of getOrElse for Option default values. Create the orElse method with the following signature: 
As you might guess from the name, there’s no need to “get” the value in order to implement this method. This is how Option is mostly used: through Option composition rather than wrapping and getting values. One consequence is that the same implementation will work for both subclasses. The solution consists in mapping the function x -> this, which results in an Option<Option, and then using getOrElse on this result with the provided default value: 
  1. public Option orElse(Supplier
  2.     return map(x -> this).getOrElse(defaultValue);  
  3. }  
In chapter 5, you created a filter method to remove from a list all elements that didn’t satisfy a condition expressed in the form of a predicate (in other words, it was a function returning a Boolean). Create the same method for Option. Here’s its signature: 
Because an Option is like a List with at most one element, the implementation seems trivial. In the None subclass, you simply return none(). In the Some class, you return the original Option if the condition holds, and none() otherwise. But try to devise a smarter implementation that fits in the Option parent class. The solution is to flatMap the function used in the Some case: 
  1. public Option filter(Function f) {  
  2.   return flatMap(x -> f.apply(x)  
  3.       ? this  
  4.       : none());  
  5. }  
Option use cases 
If you already know about the Java 8 Optional class, you may have remarked that Optional contains an isPresent() method allowing you to test whether the Optional contains a value or not. (Optional has a different implementation that’s not based on two different subclasses.) You can easily implement such a method, although you’ll call it isSome() because it will test whether the object is a Some or a None. You could also call it isNone(), which might seem more logical because it would be the equivalent of the List.isEmpty() method. 

Although the isSome() method is sometimes useful, it’s not the best way to use the Option class. If you were to test an Option through the isSome() method before calling getOrThrow() to get the value, it wouldn’t be much different from testing a reference for null before dereferencing it. The only difference would be in the case where you forget to test first: you’d risk seeing an IllegalStateException instead of a NullPointerException

The best way to use Option is through composition. To do this, you must create all the necessary methods for all use cases. These use cases correspond to what you’d do with the value after testing that it’s not null. You could do one of the following: 
* Use the value as the input to another function
* Apply an effect to the value
* Use the value if it’s not null, or use a default value to apply a function or an effect

The first and third use cases have already been made possible through the methods you’ve already created. Applying an effect can be done in different ways that you’ll learn about in chapter 13. As an example, look at how the Option class can be used to change the way you use a map. Listing 6.2 shows the implementation of a functional Map. This is not a functional implementation, but only a wrapper around a legacy ConcurrentHashMap to give it a functional interface. 
- Listing 6.2. Using Option in a functional Map 
  1. package fp.utils;  
  2.   
  3. import java.util.concurrent.ConcurrentHashMap;  
  4. import java.util.concurrent.ConcurrentMap;  
  5.   
  6. public class Map {  
  7.     private final ConcurrentMap map = new ConcurrentHashMap<>();  
  8.       
  9.     public static  Map empty(){ return new Map<>(); }  
  10.     public static  Map add(Map m, T t, U u)  
  11.     {  
  12.         m.map.put(t, u);   
  13.         return m;  
  14.     }  
  15.       
  16.     public Option get(final T t)  
  17.     {  
  18.         return this.map.containsKey(t)  
  19.                 ? Option.some(this.map.get(t))  
  20.                 : Option.none();  
  21.     }  
  22.       
  23.     public Map put(T t, U u)  
  24.     {  
  25.         return add(this, t, u);  
  26.     }  
  27.       
  28.     public Map removeKey(T t)  
  29.     {  
  30.         this.map.remove(t);  
  31.         return this;  
  32.     }  
  33. }  
As you can see, Option allows you to encapsulate into the map implementation the pattern for querying the map with containsKey before calling get. The following listing shows how this is intended to be used. 
- Listing 6.3. Putting Option to work 
  1. package fp.ch6;  
  2.   
  3. import fp.utils.Option;  
  4. import fp.utils.Map;  
  5.   
  6. public class UseMap {  
  7.     public static class Toon{  
  8.         private final String fn;  
  9.         private final String sn;  
  10.         private final Option email;  
  11.           
  12.         public Toon(String fn, String sn, String email)  
  13.         {  
  14.             this.fn = fn; this.sn = sn; this.email = Option.some(email);  
  15.         }  
  16.           
  17.         public Toon(String fn, String sn){  
  18.             this.fn = fn; this.sn = sn; this.email = Option.none();  
  19.         }  
  20.           
  21.         public Option getEmail(){ return email; }  
  22.           
  23.         @Override  
  24.         public String toString(){return String.format("%s/%s (%s)", fn, sn, email.getOrElse(()->"No data"));}  
  25.     }  
  26.       
  27.     public static void main(String[] args) {  
  28.         Map toons = new Map()  
  29.                 .put("Mickey"new Toon("Mickey""Mouse""mickey@disney.com"))  
  30.                 .put("Minnie"new Toon("Minnie""Mouse"))  
  31.                 .put("Donald"new Toon("Donald""Duck""donald@disney.com"));  
  32.           
  33.         Option mickey = toons.get("Mickey").flatMap(Toon::getEmail);  
  34.         Option minnie = toons.get("Minnie").flatMap(Toon::getEmail);  
  35.         Option goofy  = toons.get("Goofy").flatMap(Toon::getEmail);  
  36.           
  37.         System.out.printf("Mickey: %s\n", mickey.getOrElse(()->"No data"));  
  38.         System.out.printf("Minnie: %s\n", minnie.getOrElse(()->"No data"));  
  39.         System.out.printf("Goofy: %s\n", goofy.getOrElse(()->"No data"));  
  40.     }  
  41. }  
In this (very simplified) program, you can see how various functions returning Option can be composed. You don’t have to test for anything, and you don’t risk a NullPointerException, although you may be asking for the email of a Toonthat doesn’t have one, or even for a Toon that doesn’t exist in the map. But there’s a little problem. This program prints 
Mickey: mickey@disney.com
Minnie: No data
Goofy: No data

The first line is Mickey’s email. The second line says “No data” because Minnie has no email. The third line says “No data” because Goofy isn’t in the map. Clearly, you’d need a way to distinguish these two cases. The Option class doesn’t allow you to distinguish the two. You’ll see in the next chapter how you can solve this problem. 

Implement the variance function in terms of flatMap. The variance of a series of values represents how those values are distributed around the mean. If all values are very near to the mean, the variance is low. A variance of 0 is obtained when all values are equal to the mean. The variance of a series is the mean of Math.pow(x - m, 2) for each element x in the series, m being the mean of the series. Here’s the signature of the function (Exercise 6.7): 
  1. Function, Option> variance = ...  
To implement this function, you must first implement a function to compute the sum of a List. Then you should create a mean function like the one you created previously in this chapter, but working on doubles. If you have trouble defining these functions, refer to chapters 4 and 5 or use the following functions: 
  1. static Function, Double> sum = ds -> ds.foldLeft(0.0, a -> b -> a + b);  
  2.    static Function, Option> mean = ds -> ds.isEmpty()  
  3.                     ? Option.none()  
  4.                     : Option.some(sum.apply(ds) / ds.length());  
Once you’ve defined the sum and mean functions, the variance function is quite simple: 
  1. static Function, Option> variance =   
  2.         ds -> mean.apply(ds).flatMap(m -> mean.apply(ds.map(x -> Math.pow(x - m, 2))));  
Note that using functions isn’t mandatory. You must use functions if you need to pass them as arguments to higher-order functions, but when you only need to apply them, functional methods may be simpler to use. If you prefer to use methods when possible, you may arrive at the following solution: 
  1. public static Double sum(List ds) {  
  2.   return sum_(0.0, ds).eval();  
  3. }  
  4.   
  5. public static TailCall sum_(Double acc, List ds) {  
  6.   return ds.isEmpty()  
  7.       ? ret(acc)  
  8.       : sus(() -> sum_(acc + ds.head(), ds.tail()));  
  9. }  
  10.   
  11. public static Option mean(List ds) {  
  12.   return ds.isEmpty()  
  13.       ? Option.none()  
  14.       : Option.some(sum(ds) / ds.length());  
  15. }  
  16.   
  17. public static Option variance(List ds) {  
  18.   return mean(ds).flatMap(m -> mean(ds.map(x -> Math.pow(x - m, 2))));  
  19. }  
As you can see, functional methods are simpler to use for two reasons. First, you don’t need to write .apply between the name of the function and the argument. Second, the types are shorter because you don’t need to write the word Function. For this reason, you’ll use functional methods instead of functions as often as possible. But remember that it’s very easy to switch from one to the other. Given this method, 
  1. B aToBmethod(A a) {  
  2.   return ...  
  3. }  

you can create an equivalent function by writing this: 
Or you can use a method reference: 
Conversely, you can create a method from the preceding function: 
  1. B aToBmethod2(A a) {  
  2.   return aToBfunction.apply(a)  
  3. }  
As the implementation of variance demonstrates, with flatMap you can construct a computation with multiple stages, any of which may fail, and the computation will abort as soon as the first failure is encountered, because None.flatMap(f) will immediately return None without applying f. 

Other ways to combine options 
Deciding to use Option may seem to have huge consequences. In particular, some developers may believe that their legacy code will be made obsolete. What can you do now that you need a function from Option to Option, and you only have an API with methods for converting an A into a B? Do you need to rewrite all your libraries? Not at all. You can easily adapt them. 

Define a lift method that takes a function from A to B as its argument and returns a function from Option to Option. As usual, use the methods you’ve defined already (Exercise 6.8). Figure 6.2 shows that the lift method works. 

Figure 6.2. Lifting a function 

Use the map method to create a static method in the Option class. The solution is pretty simple: 
  1. static  Function f) {  
  2.       return x -> x.map(f);  
  3. }  
Of course, most of your existing libraries won’t contain functions but methods. Converting a method that takes an A as its argument and returns a B into a function from Option to Option is easy. For example, lifting the method String.toUpperCase can be done this way: 
  1. Function
Or you can use a method reference: 
  1. Function
Such solutions are useless for methods that throw exceptions. Write a lift method that works with methods that throw exceptions (Exercise 6.9). All you have to do is wrap the implementation of the function returned by lift in a try ... catch block, returning None if an exception is thrown: 
  1. static  Function f) {  
  2.   return x -> {  
  3.     try {  
  4.       return x.map(f);  
  5.     } catch (Exception e) {  
  6.       return Option.none();  
  7.     }  
  8.   };  
  9. }  
You might also need to transform a function from A to B into a function from A to Option. You can apply the same technique: 
  1. static  Function> hlift(Function f) {  
  2.   return x -> {  
  3.     try {  
  4.       return Option.some(x).map(f);  
  5.     } catch (Exception e) {  
  6.       return Option.none();  
  7.     }  
  8.   };  
  9. }  
Note, however, that this is not very useful, because the exception is lost. In the next chapter, you’ll learn how to solve this problem. 

What if you want to use a legacy method taking two arguments? Let’s say you want to use the Integer.parseInt(String s, int radix) with an Option and an Option. How can you do this? The first step is to create a function from this method. That’s simple: 
  1. Function> parseWithRadix =  
  2.                        radix -> string -> Integer.parseInt(string, radix);  
Note that I’ve inverted the arguments here to create a curried function. This makes sense because applying the radix only would give us a useful function that can parse all strings with a given radix
  1. Function parseHex = parseWithRadix.apply(16);  
The inverse (applying a String first) would make much less sense. 

Let's write a method map2 taking as its arguments an Option, an Option, and a function from (AB) to C in curried form, and returning an Option (Exercise 6.10). Here’s the solution using flatMap and map. This pattern is very important to understand, and you’ll come across it often. We’ll come back to this in chapter 8
  1. public static  Option map2(Option a, Option b, Function> f) {  
  2.     return a.flatMap(ax -> b.map(bx -> f.apply(ax).apply(bx)));  
  3. }  
With map2, you can now use any two-argument method as if it had been created for manipulating Option. What about methods with more arguments? Here’s an example of a map3 method: 
  1. Option map3(Option a,  
  2.                             Option b,  
  3.                             Option c,  
  4.                             Function>> f) {  
  5.   return a.flatMap(ax -> b.flatMap(bx -> c.map(cx ->  
  6.                                    f.apply(ax).apply(bx).apply(cx))));  
  7. }  
Do you see the pattern? 

Composing List with Option 
Composing Option instances is not all you need. Each new type you define must be, at some point, composable with any other. In the previous chapter, you defined the List type. To write useful programs, you need to be able to compose List and Option. The most common operation is converting a List<Option> into an Option<List>. A List is what you get when mapping a List with a function from B to Option. Usually, what you’ll need for the result is a Some> if all elements are Some, and a None> if at least one element is a None

Write a function sequence that combines a List into an Option>. It will be a Some> if all values in the original list were Some instances, or a None> otherwise. Here’s its signature: 
  1. Option> sequence(List
To find your way, you can test the list to see whether it’s empty or not and make a recursive call to sequence if not. Then, remembering that foldRight and foldLeft abstract recursion, you could use one of those methods to implement sequence. Here’s an explicitly recursive version that could be used if list.head() and list.tail() were made public: 
  1. public Option> sequence(List
  2. {  
  3.     return list.isEmpty()  
  4.               ? some(List.list())  
  5.               : list.head()  
  6.                     .flatMap(hh -> sequence(list.tail()).map(x -> x.cons(hh)));  
  7. }  
But list.head() and list.tail() should be usable only inside the List class, because these methods may throw exceptions. Fortunately, the sequence method can also be implemented using foldRight and map2. This is even better, because fold-Right uses heap-based recursion. 
  1. Option> sequence(List
  2.   return list.foldRight(some(List.list()),  
  3.                              x -> y -> map2(x, y, a -> b -> b.cons(a)));  
  4. }  
Consider the following example: 
  1. Function> parseWithRadix = radix -> string -> Integer.parseInt(string, radix);  
  2. Function> parse16 = Option.hlift(parseWithRadix.apply(16));  
  3. List list = List.list("9""10""11""12""13""14");  
  4. Option> result = Option.sequence(list.map(parse16));  
  5. for(Integer i:result.getOrElse(()->List.list()))  
  6. {  
  7.     System.out.printf("%d\n", i);  
  8. }  
This produces the intended result but is somewhat inefficient, because the map method and the sequence method will both invoke foldRight

Let's define a traverse method that produces the same result but invokes foldRight only once. Here’s its signature (Exercise 6.12): 
You need to implement sequence in terms of traverse. Don’t use recursion. Prefer the foldRight method that abstracts recursion for you. First define the traverse method: 
  1. Option> traverse(List list,  
  2.                                 Function> f) {  
  3.   return list.foldRight(some(List.list()),  
  4.                     x -> y -> map2(f.apply(x), y, a -> b -> b.cons(a)));  
  5. }  
Then you can redefine the sequence method in terms of traverse: 
  1. Option> sequence(List
  2.   return traverse(list, x -> x);  
  3. }  
Supplement 
FP with Java - Ch6 - Dealing with optional data - Part1 
FP with Java - Ch6 - Dealing with optional data - Part2 

沒有留言:

張貼留言

[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...