2017年11月11日 星期六

[ FP with Java ] Ch3 - Making Java more functional - Part1


Preface
This chapter covers
* Making standard control structures functional
* Abstracting control structures
* Abstracting iteration
* Using the right types

You now have all the types of functions you’ll need. As you saw in the previous chapter, these functions don’t require any exceptions to the traditional Java coding rules. Using methods as pure functions (a.k.a. functional methods) is perfectly in line with most so-called Java best practices. You haven’t changed the rules or added any exotic constructs. You’ve just added some restrictions about what functional methods can do: they can return a value, and that’s all. They can’t mutate any objects or references in the enclosing scope, nor their arguments. In the first part of this chapter, you’ll learn how to apply the same principles to Java control structures.

You’ve also learned how to create objects representing functions, so that these functions can be passed as arguments to methods and other functions. But for such functions to be useful, you must create the methods or functions that can manipulate them. In the second part of this chapter, you’ll learn how to abstract collection operations and control structures to use the power of functions. The last part of the chapter presents techniques that will allow you to get the most out of the type system when handling business problems.

Making standard control structures functional
Control structures are the main building blocks of imperative programming. No imperative Java programmer would believe it’s possible to write programs without using if ... else, switch ... case, and for, while, and do loops. These structures are the essence of imperative programming. But in the following chapters, you’ll learn how to write functional programs with absolutely no control structures. In this section, you’ll be less adventurous—we’ll only look at using the traditional control structures in a more functional style.

One point you learned in chapter 2 is that purely functional methods can’t do anything but return a value. They can’t mutate an object or reference in the enclosing scope. The value returned by a method can depend only on its arguments, although the method can read data in the enclosing scope. In such a case, the data is considered to be implicit arguments. In imperative programming, control structures define a scope in which they generally do something, which means they have an effect. This effect might be visible only inside the scope of the control structure, or it might be visible in the enclosing scope. The control structures might also access the enclosing scope to read values. The following listing shows a basic example of email validation.


Listing 3.1. Simple email validation

In this example, the if ... else structure (1) accesses the emailPattern variable from the enclosing scope. From the Java syntax point of view, there’s no obligation for this variable to be final, but it’s necessary if you want to make the testMailmethod functional. Another solution would be to declare the pattern inside the method, but this would cause it to be compiled for each method call. If the pattern could change between calls, you should make it a second parameter of the method. If the condition is true, an effect (2) is applied to this email variable. This effect consists of sending a verification email, probably to check whether the email address, besides being well formed, is a valid one. In this example, the effect is simulated (4) by printing a message to standard output. If the condition is false, a different effect (3) is applied to the variable by including it in an error message. This message is logged (5), which once again is simulated by printing to standard error.

Abstracting control structures
The code in listing 3.1 is purely imperative. You’ll never find such code in functional programming. Although the testMail method seems to be a pure effect because it doesn’t return anything, it mixes data processing with effects. This is something you want to avoid, because it results in code that’s impossible to test. Let’s see how you can clean this up.

The first thing you may want to do is separate computation and effects so you can test the computation result. This could be done imperatively, but I prefer to use a function, as shown in the following listing.


Listing 3.2. Using a function to validate the email

Now you can test the data processing part of the program (validating the email string) because you’ve clearly separated it from the effects. But you still have many problems. One is that you handle only the case where the string doesn’t validate. But if the string received is null, a NullPointerException (NPE) is thrown. Consider the following example:
  1. testMail("john.doe@acme.com");  
  2. testMail(null);  
  3. testMail("paul.smith@acme.com");  
The third line won’t be executed, even though the email address is valid, because the NPE thrown by the second line kills the thread. It would be better to get a logged message indicating what happened, and to continue processing the next address. Another problem appears if you receive an empty string:
This won’t cause an error, but the address won’t validate, and the following message will be logged:
email is invalid.

The double space (between “email” and “is”) indicates that the string was empty. A specific message would be better, such as this:
email must not be empty.

To handle these problems, you’ll first define a special component to handle the result of the computation.
  1. package fp.utils;  
  2.   
  3. public interface Result {  
  4.     public class Success implements Result{}  
  5.     public class Failure implements Result{  
  6.         private final String errorMsg;  
  7.         public Failure(String s)  
  8.         {  
  9.             this.errorMsg = s;  
  10.         }  
  11.         public String getMessage(){return errorMsg;}  
  12.     }  
  13. }  
Listing 3.3. A component to manage the result of a computation

Now you can write your new version of the program:
Listing 3.4. The program with better error handling
  1. package fp.compose.ch3;  
  2.   
  3. import java.util.regex.Pattern;  
  4.   
  5. import fp.utils.Function;  
  6. import fp.utils.Result;  
  7.   
  8. public class EmailValidation {  
  9.     static Pattern emailPattern = Pattern.compile("^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$");  
  10.   
  11.     static Function emailChecker = s -> {  
  12.         if (s == null) {  
  13.             return new Result.Failure("email must not be null");  
  14.         } else if (s.length() == 0) {  
  15.             return new Result.Failure("email must not be empty");  
  16.         } else if (emailPattern.matcher(s).matches()) {  
  17.             return new Result.Success();  
  18.         } else {  
  19.             return new Result.Failure("email " + s + " is invalid.");  
  20.         }  
  21.     };  
  22.   
  23.     public static void main(String... args) {  
  24.         validate("this.is@my.email");  
  25.         validate(null);  
  26.         validate("");  
  27.         validate("john.doe@acme.com");  
  28.     }  
  29.   
  30.     private static void logError(String s) {  
  31.         System.err.println("Error message logged: " + s);  
  32.     }  
  33.   
  34.     private static void sendVerificationMail(String s) {  
  35.         System.out.println("Mail sent to " + s);  
  36.     }  
  37.   
  38.     static void validate(String s) {  
  39.         Result result = emailChecker.apply(s);  
  40.         if (result instanceof Result.Success) {  
  41.             sendVerificationMail(s);  
  42.         } else {  
  43.             logError(((Result.Failure) result).getMessage());  
  44.         }  
  45.     }  
  46. }  
Running this program produces the expected output:
Error message logged: email this.is@my.email is invalid.
Mail sent to john.doe@acme.com
Error message logged: email must not be null
Error message logged: email must not be empty

But this still isn’t satisfactory. Using instanceof to determine whether the result is a success is ugly. And using a cast to access the failure message is even more so. But worse than this is the fact that you have some program logic in the validate method that can’t be tested. This is because the method is an effect, which means it doesn’t return a value but mutates the outside world.

Is there a way to fix this? Yes. Instead of sending an email or logging a message, you could return a small program that does the same thing. Instead of executing
  1. sendVerificationMail(s)  
and
  1. logError(((Result.Failure) result).getMessage());  
you could return instructions that, when executed, will produce the same results. Thanks to lambdas, you can do this easily. First, you need a functional interface representing an executable program:
  1. public interface Executable {  
  2.   void exec();  
  3. }  
You could have used the standard Runnable interface, but most code verifiers raise a warning if this interface is used for something other than running a thread. So you’ll use your own interface. You can easily change your program, as shown in the following listing.
Listing 3.5. Returning executables
  1. package fp.compose.ch3;  
  2.   
  3. import java.util.regex.Pattern;  
  4.   
  5. import fp.utils.Function;  
  6. import fp.utils.Result;  
  7.   
  8. public class EmailValidationV2 {  
  9.     static Pattern emailPattern = Pattern.compile("^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$");  
  10.       
  11.     static Function emailChecker = s ->   
  12.         s == null  
  13.                 ? new Result.Failure("email must not be null")  
  14.                 :s.length() == 0  
  15.                     ? new Result.Failure("email must not be empty")  
  16.                     : emailPattern.matcher(s).matches()  
  17.                         ? new Result.Success()  
  18.                         : new Result.Failure("email " + s + " is invalid.");  
  19.   
  20.         // (1)                        
  21.     public static void main(String... args) {  
  22.         validate("this.is@my.email");  
  23.         validate(null);  
  24.         validate("");  
  25.         validate("john.doe@acme.com");  
  26.     }  
  27.   
  28.     private static void logError(String s) {  
  29.         System.err.println("Error message logged: " + s);  
  30.     }  
  31.   
  32.     private static void sendVerificationMail(String s) {  
  33.         System.out.println("Mail sent to " + s);  
  34.     }  
  35.   
  36.         // (2)  
  37.     static Executable validate(String s) {  
  38.         Result result = emailChecker.apply(s);  
  39.         return (result instanceof Result.Success)  
  40.                 ? ()->sendVerificationMail(s)  
  41.                 : ()->logError(((Result.Failure)result).getMessage());  
  42.     }  
  43. }  
The validate method (2) now returns Executable instead of void. It no longer has any side effect, and it’s a pure function. When an Executable is returned (1), it can be executed by calling its exec method. Note that the Executable could also be passed to other methods or stored away to be executed later. In particular, it could be put in a data structure and executed in sequence after all computations are done. This allows you to separate the functional part of the program from the part that mutates the environment.

You’ve also replaced the if ... else control structure with the ternary operator. This is a matter of preference. The ternary operator is functional because it returns a value and has no side effect. In contrast, the if ... else structure can be made functional by making it mutate only local variables, but it can also have side effects. If you see imperative programs with many embedded if ... else structures, ask yourself how easy it would be to replace them with the ternary operator. This is often a good indication of how close to functional the design is. Note, however, that it’s also possible to make the ternary operator nonfunctional by calling nonfunctional methods to get the resulting values.

Cleaning up the code
Your validate method is now functional, but it’s dirty. Using the instanceof operator is almost always an indication of bad code. Another problem is that reusability is low. When the validate method returns a value, you have no choice besides executing it or not. What if you want to reuse the validation part but produce a different effect?

The validate method shouldn’t have a dependency on sendVerificationMail or logError. It should only return a result expressing whether the email is valid, and you should be able to choose whatever effects you need for success or failure. Or you might prefer not to apply the effect but to compose the result with some other processing. Let's try to decouple the validation from the effects applied (Exercise 3.1).
Hint.
First, you’ll need an interface with a single method to represent an effect. Second, because the emailChecker function returns a Result, the validate method could return this Result. In such a case, you’d no longer need the validate method. Third, you’ll need to “bind” an effect to the Result. But because the result may be a success or a failure, it would be better to bind two effects and let the Result class choose which one to apply.

The first thing to do is create the interface representing an effect, such as the following:
  1. package fp.compose.ch3;  
  2.   
  3. public interface Effect {  
  4.      void apply(T t);  
  5. }  
You may prefer the Consumer interface of Java 8. Although the name was badly chosen, it does the same job. Then you’ll need to make some changes to the Result interface, as shown in figure 3.1.


Figure 3.1. Changes to the Result interface

Take the example of Java interfaces. They’re supposed to be named either after what objects are (Comparable, Clonable, Serializable) or what they can do (Listener, Supplier, Consumer). Following this rule, a Function should be renamed Applicable and should have a method apply. A Supplier should define a method supply, and a Consumer should consume something and have a method named consume. But a Consumer defines an acceptmethod, and it doesn’t consume anything, because after having accepted an object, this object is still available.

Don’t trust names. Trust types. Types don’t lie. Types are your friends! The following listing shows the modified version of the Result class.
Listing 3.6. A Result that can handle Effects
  1. package fp.compose.ch3;  
  2.   
  3. public interface Result {  
  4.     void bind(Effect success, Effect failure);  
  5.       
  6.     public static  Result failure(String message)  
  7.     {  
  8.         return new Failure<>(message);  
  9.     }  
  10.       
  11.     public static  Result success(T value)  
  12.     {  
  13.         return new Success<>(value);  
  14.     }  
  15.       
  16.     public class Success implements Result  
  17.     {  
  18.         private final T value;  
  19.           
  20.         private Success(T t){this.value = t;}  
  21.         @Override  
  22.         public void bind(Effect success, Effect failure)  
  23.         {  
  24.             success.apply(value);  
  25.         }  
  26.     }  
  27.       
  28.     public class Failure implements Result  
  29.     {  
  30.         private final String errorMsg;  
  31.         private Failure(String s){  
  32.             this.errorMsg = s;  
  33.         }  
  34.           
  35.         @Override  
  36.         public void bind(Effect success, Effect failure)  
  37.         {  
  38.             failure.apply((T)errorMsg);  
  39.         }  
  40.     }  
  41. }  
You can choose whatever name you want for the bind method. You could call it ifSuccess or forEach. Only the type is important. Now you can clean up the program by using the new Effect and Result interfaces, as shown in the following listing.
Listing 3.7. A cleaner version of the program
  1. package fp.compose.ch3;  
  2.   
  3. import java.util.regex.Pattern;  
  4.   
  5. import fp.utils.Function;  
  6.   
  7. public class EmailValidationV3 {  
  8.     static Pattern emailPattern = Pattern.compile("^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$");  
  9.     // (3)  
  10.     static Effect SuccessEft = s -> System.out.printf("Mail sent to %s", s);  
  11.     static Effect FailureEft = s -> System.err.printf("Error message logged: %s", s);  
  12.       
  13.     // (1)  
  14.     static Function> emailChecker = s -> {  
  15.         if(s==null)  
  16.         {  
  17.             return Result.failure("email must not be null");  
  18.         }  
  19.         else if(s.length() == 0)  
  20.         {  
  21.             return Result.failure("email must not be empty");  
  22.         }  
  23.         else if(emailPattern.matcher(s).matches())  
  24.         {  
  25.             return Result.success(s);  
  26.         }  
  27.         else  
  28.         {  
  29.             return Result.failure("email " + s + " is invalid.");  
  30.         }  
  31.     };  
  32.       
  33.     // (2)  
  34.     public static void main(String args[])  
  35.     {  
  36.         emailChecker.apply("this.is@my.email").bind(SuccessEft, FailureEft);  
  37.         emailChecker.apply(null).bind(SuccessEft, FailureEft);  
  38.         emailChecker.apply("").bind(SuccessEft, FailureEft);  
  39.         emailChecker.apply("john.doe@acme.com").bind(SuccessEft, FailureEft);  
  40.     }  
  41. }  
The emailChecker function now returns a parameterized Result . It’s irrelevant that Result is parameterized by the same type as the type of an error message. It could have been any type, such as Result. If you look at the Result implementation, you’ll see that the value of Failure is always String, whatever the value of Success might be. The Success class holds a value of type T, and the Failure class holds a value of type String. In this example, it just so happens that T is String, but it could have been anything else. (You’ll come back to this subject in the last section of this chapter.) The validate method has been removed, and two Effect instances are now defined : one for success and one for failure. These two effects are bound to the result of the emailChecker function.

An alternative to if ... else
You may wonder whether it’s possible to completely remove conditional structures or operators. Can you write a program without any of these constructs? This may seem impossible, because many programmers have learned that decision-making is the basic building block of programming. But decision-making is an imperative programming notion. It’s the notion of examining a value and deciding what to do next based on this observation. In functional programming, there’s no “what to do next” question, but only functions returning values. The most basic if structure may be seen as the implementation of a function:
  1. if (x > 0) {  
  2.   return x;  
  3. else {  
  4.   return -x;  
  5. }  
This is a function of x. It returns the absolute value of x. You could write this function as follows:
  1. Function abs = x -> {  
  2.   if (x > 0) {  
  3.     return x;  
  4.   } else {  
  5.     return -x;  
  6.   }  
  7. }  
The difference with a function such as
  1. Function square = x -> x * x;  
is that you have two implementations of the function and have to choose between the two depending on the value of the argument. This isn’t a big problem, but what if you had many possible implementations? You’d end up with as many embedded if ... else structures as you have in listing 3.7, or as many embedded ternary operators as in listing 3.5. Can you do better?

(Exercise 3.2) Let's write a Case class representing a condition and corresponding result. The condition will be represented by a Supplier, where Supplier is a functional interface such as this:
  1. interface Supplier {  
  2.   T get();  
  3. }  
You can use the Java 8 implementation of Supplier or your own. The result corresponding to the condition will be represented by a Supplier>. To hold both, you can use a Tuple, Supplier>>. The Case class should define three methods:
  1. public static  Case mcase(Supplier condition,  
  2.                                 Supplier> value)  
  3.   
  4. public static  DefaultCase mcase(Supplier> value)  
  5.   
  6. public static  Result match(DefaultCase defaultCase,  
  7.                                   Case... matchers)  
I used the name mcase because case is a reserved word in Java; m stands for match. Of course, you can choose any other name. The first mcase method defines a normal case, with a condition and a resulting value. The second mcasemethod defines a default case, represented by a subclass. The third method, match, selects a case. Because this method uses a vararg, the default case is to be put first, but will be the last to be used! Additionally, the Case class should define the private DefaultCase subclass with the following signature:
  1. private static class DefaultCase extends Case  
I said that the class must represent a Supplier for the condition and a Supplier>> for the resulting value. The simplest way to do this is to define it as follows:
  1. package fp.compose.ch3;  
  2.   
  3. import fp.utils.Tuple;  
  4.   
  5. public class Case extends Tuple, Supplier>>{  
  6.     private Case(Supplier booleanSupplier,  
  7.             Supplier> resultSupplier) {  
  8.         super(booleanSupplier, resultSupplier);  
  9.     }  
  10. }  
The mcase methods are simple. The first one takes the two parameters and creates a new instance. The second receives only the second parameter (the Supplier for the value) and creates the default Supplier for the condition, which always returns true:
  1. public static  Case mcase(Supplier condition, Supplier> value) {  
  2.     return new Case<>(condition, value);  
  3. }  
  4.   
  5. public static  DefaultCase mcase(Supplier> value) {  
  6.     return new DefaultCase<>(() -> true, value);  
  7. }  
The DefaultCase class couldn’t be simpler. It’s only a marker class, so you only have to create a constructor calling super:
  1. private static class DefaultCase extends Case {  
  2.     private DefaultCase(Supplier booleanSupplier, Supplier> resultSupplier) {  
  3.         super(booleanSupplier, resultSupplier);  
  4.     }  
  5. }  
The match method is more complex, but that’s an overstatement because it has only few lines of code:
  1. @SafeVarargs  
  2. public static  Result match(DefaultCase defaultCase, Case... matchers) {  
  3.     for (Case aCase : matchers) {  
  4.         if (aCase._1.get())  
  5.             return aCase._2.get();  
  6.     }  
  7.     return defaultCase._2.get();  
  8. }  
As I previously mentioned, the default case has to come first in the argument list because the second argument is a vararg, but this case is used last. You test all cases one by one by evaluating them through a call to the get method. If the result is true, you return the corresponding value after having evaluated it. If no case matches, the default case is used. Note that evaluation means evaluation of the returned value. No effect is applied at this time. The following listing shows the complete class.
Listing 3.8. Matching conditions with the Case class
  1. package fp.compose.ch3;  
  2.   
  3. import fp.utils.Tuple;  
  4.   
  5. public class Case extends Tuple, Supplier>>{  
  6.     private static class DefaultCase extends Case {  
  7.         private DefaultCase(Supplier booleanSupplier, Supplier> resultSupplier) {  
  8.             super(booleanSupplier, resultSupplier);  
  9.         }  
  10.     }  
  11.   
  12.     private Case(Supplier booleanSupplier,  
  13.             Supplier> resultSupplier) {  
  14.         super(booleanSupplier, resultSupplier);  
  15.     }  
  16.       
  17.     public static  Case mcase(Supplier condition, Supplier> value) {  
  18.         return new Case<>(condition, value);  
  19.     }  
  20.   
  21.     public static  DefaultCase mcase(Supplier> value) {  
  22.         return new DefaultCase<>(() -> true, value);  
  23.     }  
  24.   
  25.     @SafeVarargs  
  26.     public static  Result match(DefaultCase defaultCase, Case... matchers) {  
  27.         for (Case aCase : matchers) {  
  28.             if (aCase._1.get())  
  29.                 return aCase._2.get();  
  30.         }  
  31.         return defaultCase._2.get();  
  32.     }  
  33. }  
Now you can greatly simplify the code of your email validation application. As you can see in the following listing, it contains absolutely no control structures. (Note the use of static import for methods of Case and Result.)
Listing 3.9. The email validation application with no control structures
  1. package fp.compose.ch3;  
  2.   
  3. import java.util.regex.Pattern;  
  4. import static fp.compose.ch3.Case.*;  
  5. import static fp.compose.ch3.Result.*;  
  6. import fp.utils.Function;  
  7.   
  8. public class EmailValidationV4 {  
  9.     static Pattern emailPattern = Pattern.compile("^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$");  
  10.     static Effect SuccessEft = s -> System.out.printf("Mail sent to %s", s);  
  11.     static Effect FailureEft = s -> System.err.printf("Error message logged: %s", s);  
  12.       
  13.     static Function> emailChecker = s ->   
  14.         match(  
  15.                 mcase(()->success(s)),  
  16.                 mcase(()->s==null,()->failure("email must not be null")),  
  17.                 mcase(()->s.length()==0, ()->failure("email must not be empty")),  
  18.                 mcase(()->emailPattern.matcher(s).matches(), ()->failure("email " + s + " is invalid.")));  
  19.           
  20.       
  21.       
  22.     public static void main(String args[])  
  23.     {  
  24.         emailChecker.apply("this.is@my.email").bind(SuccessEft, FailureEft);  
  25.         emailChecker.apply(null).bind(SuccessEft, FailureEft);  
  26.         emailChecker.apply("").bind(SuccessEft, FailureEft);  
  27.         emailChecker.apply("john.doe@acme.com").bind(SuccessEft, FailureEft);  
  28.     }  
  29. }  
But wait. There’s a trick! You don’t see any control structures because they’re hidden in the Case class, which contains an if instruction and even a for loop. So are you cheating? Not really. First, you have a single clean loop and a single clean if. No more series of embedded if statements. Second, you’ve abstracted these structures. You can now write as many conditional applications as you want without having to write a single if or for. But most important, you’re only at the beginning of your trip into functional programming. In chapter 5 you’ll learn how to completely remove these two constructs.

In this chapter, you’ll see how to generalize abstractions of all control structures. You’ve done this for conditional control structures such as embedded if..else statements (and switch..case is no different). Let’s see how to do the same with loops.

Supplement
Java Annotation - 常用標準標註
在使用泛型定義不定長度引數時,編譯器會提示開發人員,有沒有注意到 heap pollution 問題,如果開發人員確定避免了這個問題,則可以使用 @SafeVarargs 加以標註. 加上@SafeVarargs 後,編譯 Util 類別就不會發生警訊,由於開發人員要在確定避免了heap pollution 的情況下,才會加上@SafeVarargs,呼叫 Util.doSome() 的使用者就不用提心吊膽...

沒有留言:

張貼留言

[ Py DS ] Ch1 - IPython: Beyond Normal Python

Source From  Here   Keyboard Shortcuts in the IPython Shell   If you spend any amount of time on the computer, you’ve probably found a u...