2018年1月2日 星期二

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


Miscellaneous utilities for Option 
In order to make Option as useful as possible, you need to add some utility methods. Some of these methods are a must, and others are questionable because their use is not in the spirit of functional programming. You nevertheless must consider adding them. You may need a method to test whether an Option is a None or a Some. You may also need an equals method for comparing options, in which case you mustn’t forget to define a compatible hashCode method. 

Testing for Some or None 
Until now, you haven’t needed to test an option to know whether it was a Some or a None. Ideally, you should never have to do this. In practice, though, there are times when it’s simpler to use this trick than to resort to real functional techniques. For example, you defined the map2 method as: 
  1. Option map2(Option a,  
  2.                          Option b,  
  3.                          Function> f) {  
  4.   return a.flatMap(ax -> b.map(bx -> f.apply(ax).apply(bx)));  
  5. }  
This is very smart, and because you want to look smart, you might prefer this solution. But some may find the following version simpler to understand: 
  1. Option map2(Option a,  
  2.                          Option b,  
  3.                          Function> f) {  
  4.   return a.isSome() && b.isSome()  
  5.       ? some(f.apply(a.get()).apply(b.getOrThrow()))  
  6.       : none();  
  7. }  
Testing the Code 
If you want to test this code, you’ll have to define the isSome method first, but this is not to encourage you to use this nonfunctional technique. You should always prefer the first form, but you should also understand fully the relation between the two forms. Besides, you’ll probably find yourself needing the isSome method someday. 

equals and hashcode 
Much more important are the definitions of the equals and hashcode methods. As you know, these methods are strongly related and must be consistently defined. If equals is true for two instances of Option, their hashcode methods should return the same value. (The inverse is not true. Objects having the same hashcode may not always be equal.) Here are the implementations of equals and hashcode for Some
  1. @Override  
  2. public boolean equals(Object o) {  
  3.   return (this == o || o instanceof Some)  
  4.                              && this.value.equals(((Some) o).value);  
  5. }  
  6.   
  7. @Override  
  8. public int hashCode() {  
  9.   return Objects.hashCode(value);  
  10. }  
And here are the corresponding implementations for None
  1. @Override  
  2. public boolean equals(Object o) {  
  3.   return this == o || o instanceof None;  
  4. }  
  5.   
  6. @Override  
  7. public int hashCode() {  
  8.   return 0;  
  9. }  
How and when to use Option 
As you may know, Java 8 has introduced the Optional class that may be seen by some as identical to your Option, although it’s not implemented in the same way at all, and it lacks most of the functional methods you’ve put into Option. There’s much controversy about whether the new features of Java 8 are a move toward functional programming. They certainly are, although this is not official. The official position is that Optional is not a functional feature. 

Here’s how Brian Goetz, Java language architect at Oracle, answered a question about this subject on Stack Overflow. The question was “Should Java 8 getters return optional types?” Here is Brian Goetz’s answer: 
The full discussion may be read at http://mng.bz/Rkk1. 
Of course, people will do what they want. But we did have a clear intention when adding this feature, and it was not to be a general purpose Maybe or Some type, as much as many people would have liked us to do so. Our intention was to provide a limited mechanism for library method return types where there needed to be a clear way to represent “no result” and using null for such was overwhelmingly likely to cause errors.

For example, you probably should never use it for something that returns an array of results, or a list of results; instead return an empty array or list. You should almost never use it as a field of something or a method parameter. I think routinely using it as a return value for getters would definitely be over-use. There’s nothing wrong with Optional that it should be avoided, it’s just not what many people wish it were, and accordingly we were fairly concerned about the risk of zealous over-use.

(Public service announcement: NEVER call Optional.get unless you can prove it will never be null; instead use one of the safe methods like orElse or ifPresent. In retrospect, we should have called get something like getOrElse-ThrowNoSuch-Element-Exception or something that made it far clearer that this was a highly dangerous method that undermined the whole purpose of Optional in the first place. Lesson learned.)

This is a very important answer that deserves some reflection. First of all, and this might be the most important part, “people will do what they want.” Nothing to add here. Just do what you want. This doesn’t mean you should do whatever you want without thinking. But feel free to try every solution that comes to mind. You shouldn’t refrain from using Optional in a particular way just because it wasn’t intended to be used that way. Imagine the first man who ever thought about grabbing a stone to hit something with more strength. He had two options (pun intended!): refraining from doing it because stones had obviously not been intended to be used as hammers, or just trying it. 

Second, Goetz says that get shouldn’t be called unless you can prove it will never be null. Doing this would completely ruin any benefit of using Option. But you don’t need to give get a very long name. getOrThrow would do the job. Note that returning an empty list to indicate the absence of a result doesn’t by itself solve the problem. Forgetting to test whether the list is empty will produce an IndexOutOfBoundException instead of a NullPointerException. Not much better! 

When to use getOrThrow 
The correct advice is to avoid getOrThrow as much as possible. As a rule of thumb, each time you find yourself using this method outside of the Option class, you should consider whether there’s another way to go. Using getOrThrow is exiting the functional safety of the Option class. 

The same thing is true for the head and tail methods of the List class. If possible, these methods shouldn’t be used outside of the List class. Directly accessing the value(s) contained in classes like List or Option always brings the risk of a NullPointerException if this is done on the None or Nil subclasses. It may not be possible to avoid in library classes, but it should be avoided in business classes. That’s why the best solution is to make this method protected, so that it can only be called from inside the Option class. 

But the most important point is the original question: should getters return Option (or Optional)? Generally, they shouldn’t, because properties should be final and initialized at declaration or in constructors, so there’s absolutely no need for getters to return Option. (I must admit, however, that initializing fields in constructors doesn’t guarantee that access to properties is impossible before they’re initialized. This is a marginal problem that’s easily solved by making classes final, if possible.

But some properties might be optional. For example, a person will always have a first name and a last name, but they might have no email. How can you represent this? By storing the property as an Option. In such cases, the getter will have to return an Option. Saying that “routinely using it as a return value for getters would definitely be over-use” is like saying that a property without a value should be set to null, and the corresponding getter should return null. This completely destroys the benefit of having Option

What about methods that take Option as their argument? In general, this should not occur. To compose methods returning Option, you shouldn’t use methods that take Option as their argument. For example, to compose the three following methods, you don’t need to change the methods to make them accept Option as their argument: 
  1. Option getName () {  
  2.   ...  
  3. }  
  4.   
  5. Option validate(String name) {  
  6.   ...  
  7. }  
  8.   
  9. Option getToon(String name) {  
  10.   ...  
  11. }  
Given that the validate method is a static method of class Validate, and toonMap is an instance of Map with the get instance method, the functional way to compose these methods is as follows: 
  1. Option toon = getName()  
  2.                       .flatMap(Validate::validate)  
  3.                       .flatMap(toonMap::get)  
So there’s little use for methods taking Option as parameters in business code. 

There’s another reason why Option (or Optional) should probably be used rarely (if ever). Generally, the absence of data is the result of errors that you should often handle by throwing an exception in imperative Java. As I said previously, returning Option.None instead of throwing an exception is like catching an exception and swallowing it silently. Usually it’s not a billion-dollar mistake, but it’s still a big one. You’ll learn in the next chapter how to deal with this situation. After that, you’ll hardly ever need the Option data type again. But don’t worry. All you’ve learned in this chapter will still be extremely useful. 

The Option type is the simplest form of a kind of data type that you’ll use again and again. It’s a parameterized type, it has a method to make an Option from an A, and it has a flatMap method that can be used to compose Optioninstances. Although it’s not very useful by itself, it has acquainted you with very fundamental concepts of functional programming. 

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...