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:
- Option
map2(Option a, - Option b,
- Function> f) {
- return a.flatMap(ax -> b.map(bx -> f.apply(ax).apply(bx)));
- }
- Option
map2(Option a, - Option b,
- Function> f) {
- return a.isSome() && b.isSome()
- ? some(f.apply(a.get()).apply(b.getOrThrow()))
- : none();
- }
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:
- @Override
- public boolean equals(Object o) {
- return (this == o || o instanceof Some)
- && this.value.equals(((Some) o).value);
- }
- @Override
- public int hashCode() {
- return Objects.hashCode(value);
- }
- @Override
- public boolean equals(Object o) {
- return this == o || o instanceof None;
- }
- @Override
- public int hashCode() {
- return 0;
- }
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.
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:
- Option
getName () { - ...
- }
- Option
validate(String name) { - ...
- }
- Option
getToon(String name) { - ...
- }
- Option
toon = getName() - .flatMap(Validate::validate)
- .flatMap(toonMap::get)
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
沒有留言:
張貼留言