Why is Optional<> better then throwing an exception or returning a null?
The way I see it it's like this:
Optional<Integer> num = getSomeRiskyNumber();
if (!num.isPresent())
return ... code to bubble up a blank optional
vs
Integer num = getSomeRiskyNumber();
if (num == null)
... throw exception or return null
I get that the author has adopted a more functional programming methodology for dealing with their data but for some tasks this isn't acceptable. To just return 1 value you've allocated at least one object (Optional) and make at least 2 function calls on it (isPresent() and get()).
You get `if (value == null)` for free. Throwing exceptions is very heavy and I'd place Optional<> above that but there isn't any way to signify the error that you acctually got. You'd need to make an Optional<Maybe<T>> where Maybe<T> supports error/exceptions. Maybe Maybe<T> holds data and you can extend it with BadMaybe<T> who extends exception or something so you can put that in instead of your value to signal your exception.
I don't see the benifit. Maybe I'm just crazy but `if (v == null)` has all the features Optional has for less of an overhead and less cognitive load. (If you're afraid of NPEs then just document all the return states of your methods and use @nullable to show when you need to check. IIRC IntellJ catches that kind of mistake).
The point of a static type system is to have the language help you out as much as possible to write type-error free code. It can't guarantee that you haven't messed up, but it's supposed to help.
Null ruins that. It's a known source of runtime exceptions, and it is not a compilation error to return null or forget the null-check. It is a compilation error to pretend that an Optional<T> is just a T. The Java compiler is not smart enough to prevent you from doing something silly like calling get() on Optional<T> without checking isPresent() first, but at least it gives you something.
Also Optional has some really nice creature comforts. It gives you the ability to map to compose an Optional<T> with functions that couldn't care less about the Optionality, and it lets you use flatMap to chain successive calls that might fail together, avoiding an arbitrary number of null checks (that you might forget!) in your code.
Frankly arguing against Optional<T> is like arguing against map on lists/streams and saying that for is good enough for you. You can do it, but I think you'd be nuts to work against yourself like that.
Kotlin does it more efficiently. But you wouldn't want it to be an error to use get without calling ifPresent. Sometimes you know that the type system is wrong and that in fact, although this thing is optional, it will in your case always be there. So being able to rapidly convert optionality into an error (we might call it a NullPointerException) is important. Kotlin has the !! operator for that.
If something is wrong, throw an exception. If it's the kind of error that must be handled, throw a checked exception. If there's no value to return, return Optional.empty.
The code receiving that Optional<Foo> is now free to do what the author is suggesting: map, filter, all without worrying if the value is actually there or not. The code is cleaner, easier to read, etc.
But no one should ever have to check for null anymore.
Obviously, because nulls exist in java, this guarantee isn't provable on compilation the way it is in e.g. Scala, but if your entire team writes code this way, at least you can rely on that for internal libraries. Like generics, this is one of those ideas that's ok in java 1.X, but doesn't shine as much until a backwards incompatible java 2.X
Nullable types provide Optional already. I find the use of a custom wrapper type to be a massive wart, and Optional is a common enough thing that it's worth adding a bit of syntax for it.
Which is why I like Kotlin's approach. Same guarantees, one letter instead of ten. If you're writing Java, though, I can see why Optional has some use.
Often when you're dealing with a value that may be null, you might want to do a series of transformations to it that may change its value or type, and may even return null themselves. Optional lets you chain these actions together without concerning yourself at every single step whether or not the value is there until the very end.
Let's take this back to map/filter on lists. Remember the bad old days when you didn't have map and filter on lists? If you had some code that iterated heavily and then you needed to add another step you often ended up refactoring a lot of manual for code that explicitly handled iteration logic. Now with streams you can just chain another call onto the same stream and as long as the types match up you're ready to go test it. Optional gives you the same thing, but instead of working with lists it handles values that might be missing.
Because null is overloaded with meaning - it could be an IO error leading to an empty result, or IO could have produced an actually empty result, or ...
Yes, you could argue that you will only use null for an actually empty result. But two things: 1) You won't keep to it because the compiler won't force you to do so, and 2) potential contributors will not comply. When using optionals/either/enums, compiler will not allow a wildcard result (such as null).
.empty is just more semantically specific, and if you read a method's API for the first time, semantics helps you grasp it a lot faster. That's the reason I'm using enums in Swift all over the place now. Compared to the nilling out of Objective-C, I can be way more confident that method results really are what they claim they are.
The biggest reason- I very rarely need to check if my optional is empty or not. I can write optional-chaining code like in the article above and it implicitly handles both cases.
The code I've written using optionals instead is much shorter, much clearer in its intent, and generally has fewer bugs.
That's anecdotal experience, not proofs. All I can really recommend is that you try it with an open mind.
That is sometimes just not an option performance wise yet.
"If it's the kind of error that must be handled, throw a checked exception. If there's no value to return, return Optional.empty."
Again, why is Optional.empty() better then null? What makes it better? What do you get from throwing an exception? What is the benifit. You can't just say "my way is better" when we have years and years of Java development flying contrary to your statement. What has changed that makes null a non-viable practice?
"The code receiving that Optional<Foo> is now free to do what the author is suggesting: map, filter, all without worrying if the value is actually there or not. The code is cleaner, easier to read, etc."
The code being cleaner is a subjective, or at least extremely difficult to prove, statment. For instance
List<Integer> nums = getNumbers();
if (nums == null) {
S.o.p("Failed to load numbers");
return;
}
for (Integer i : nums)
System.out.println(i);
Is far better then
Optional<List<Integers>> nums = getNumbers();
if (!nums.isPresent()) {
S.o.p("Failed to load numbers");
return;
}
nums.forEach(S.o::p);
Or even better yet
for (int i : nums)
if (i < 10)
S.o.p(i);
VS
nums.filter((n) -> n < 10).forEach(S.o::p);
I don't think that's more readable. It think that's more compressed. HEre's another example. Suppose we have a magical language that I'm sure you'll pick up. It's a very compressed (or as you'd say expressive) language. This is that same code written in it
pa(i i nums < 10)
Which expands to "print all the ints `i` in nums that are less then 10" in english. That's far less readable. It is more compressed. I don't think compression is the goal of a language as much as it is a goal of a zip program. We're supposed to be writing software, not liturature to submit to the shortest story fully told contest. Readability is a function of correct verbosity.
In my opinion. Just compressing your logic doesn't make it more readable. I think some verbosity is needed to preserve simplicity.
"But no one should ever have to check for null anymore. "
I mean that just doesn't make sense. If you're suggesting that there are some times the state of a program should never contain a null value is just ridiculous. Some things are correctly modled by null and some things are also too performance dependant to not use null.
I think some things benifit from using Optional<> but the case doesn't exist to completely remove null. Even just by the creation of a new container object wrapping your already expensive return object there exists a case for null to exist.
Just saying "Don't do it it's bad" is not proof. Saying "the code is cleaner, easier to read, etc" is not proof or even an example of a design that is simpler to pickup and get going with. You'd have to write some code with the Java/OOP paradigms and write a version (that is feature complete) with the FP paradigms and see which is easier to understand for a new programmer. I'd be hard pressed to belive that the FP implementation would be simpler. Maybe to you and me but no to someone without the domain specific knowladge required to understand what's going on. Even when I use map, zip, and list comprehensions in my python code it scares off some of my coworkers.
public List<Integer> add2(File file) {
List<Integers> nums = file.getNumbers(); // Show where the data comes from
if(nums == null) { // Show the invariant provided for the rest of the code
return Collections.<Integer>emptyList(); // Show the default return value if there has been an error
}
List<Integer> resultList = List<>(); // Apply your logic
for ( i : nums) {
resultList.append(i + 2);
}
return resultList; // Return your list
}
Then you document that behavior in a javadoc. Throw in @Nullable where applicable and move on. Or you can do this
public List<Integer> add2(File file) {
// Show data source
List<Integers> nums = file.getNumbers();
// Show case where data is supplimented
if(nums == null)
nums = Collections.<Integer>emptyList(); // Show supplimnetal
// Show transformation
List<Integer> resultList = List<>();
for ( i : nums) {
resultList.append(i + 2);
}
return resultList;
}
This is important to unify because nums won't always be null. We also won't always be supplimenting with an empty set. I don't see what value is recived from compressing that all into a single line. I don't see that as being more readable. I do see this very simple, step by step, explination of what's happening as being dead simple that no one can misunderstand.
> Again, why is Optional.empty() better then null?
Because with Optional the type tells the caller that it might not return a value. With null, you have no idea if you need to check for null or not.
Being able to chain maps, filters, etc. is convenient as well, but I'd argue that's just a side benefit.
Oh, one other thing Optional can do that null can't: nest. For example, if you call get() on a Map, and it returns null, you can't tell if the key wasn't in the map, or if it's value was null. If get() returned an Optional, then for a Map<String,Optional<String>>, get() would return Optional<Optional<String>>.
Your very first method has a return buried in the middle of your code. That is a gigantic red flag.
Look, what you're trying to do is simple.
getNumbers.fold(println("Failed to load numbers"))(nums => nums.foreach(println))
That's the Scala version, but I'm sure there's a Java version of the same code. This code assumes that getNumbers returns Option[List[Int]], which your compiler can guarantee. If you want something that's a little bit more readable, try this:
getNumbers match {
case Some(nums) => nums.foreach(println)
case None => println("Failed to load numbers")
}
No null check, no loops, just very simple easy-to-read bulletproof code. This code cannot generate a NPE at all, ever.
The first option ins completely unreadable to me. I cannot imagine that ever scaling well.
The second option, as I have stated many times in this thread, is far preferable to null. Java does not support it but when it does I will like it.
"* No null check, no loops, just very simple easy-to-read bulletproof code. This code cannot generate a NPE at all, ever.*"
The NPE isn't the illness, it's the symptom. It's the symptom of a far larger problem in your program: unhandled or otherwise unexpected state. That's why I like the match example. It forces you to expect all of the states of your program. The first example does neither. It hides flow and operation in a single (ever-expanding) line. Far from ideal in any case in my book.
As soon as Match is supported in Java I'll change my opinion. Until then I'm stuck. You get nothing better from the Java version in terms of readability.
In Scala, the debate between using fold on an Option or using map and getOrElse is almost as old as the language. The method signature for this is:
def fold[B](ifEmpty: => B)(f: (A) => B): B
That is to say, fold always returns type B, and takes two arguments. The first takes a function returning type B if the option is "empty" (None) and the second takes a function that is called with the contents of the Some. As you can see, it's syntactic sugar for map and getOrElse.
def map[B](f: (A) => B): B
def getOrElse[B](f: => B): B
If it's syntactic sugar for map and getOrElse what is really gained? If the definition of fold is mapAndGetOrElse why add an additional concept to burden programmers minds with? GetOrElse is independently useful and everyone needs to know map anyway. Using fold just seems like a lost opportunity to teach someone getOrElse with no real benefit.
You make the Understanding This Codebase 101 curriculum some percent longer without making your programmers any better.
If it's terseness, I don't really think one symbol is any verbosity benefit over two. Same order of magnitude, same cost. It's a rounding error in brevity. People way overvalue terseness.
And the cost of people missing chances to learn getOrElse has got to be massive.
It's quite simple: null is not a valid value for any type. It completely breaks the entire point of a type system. Null isn't an Integer, nor a String.
Simple example that proves my point: You can't call .length on null, but you can for every string. Therefore null isn't a string. So why in hell should you be able to write code that states (falsely) that null is a string? You can't say an int is a string.
Removing null from a language adds a tremendous amount of safety.
you shouldn't really be running `optional.isPresent()`, but rather, use a map operation, or a flatMap operation, all the way until you need to interface an outside api (in which case, you use a `getOrElse(defaultValue)`).
You're right about code that branches based on `isPresent()` is semantically the same as null checks - and if you only did that and didn't use any functional paradigm like mapping on the types, you don't really get any real benefit!
To my knowladge the Java library does not although it is fully capable of supporting this. I guess this is just an example of poor implementation. It could have been easily implemented like this
Optional<X> x = ...;
switch (status(x)) {
case SOME: return x.get() * 10;
case NONE: .... send error ...
}
Again I really like the matching functionality but as it stands, since it's not included, it's not worth much without it in my opinion.
With match, that cleans up a LOT of code and if the compiler can guarantee no speed or memory hits for doing this it becomes very valuable very quick.
Optional<Integer> num = getSomeRiskyNumber();
return num.map(x -> ...).orElseGet(return error or whatever);
Which is just as type-safe. There are a number of variants, including flatMap for when the map operation itself returns an Optional (the monadic bind operator) and a number of options for how to deal with the empty case.
A few things that need responding to. First, optional is the same as a maybe. They're different names for the same thing.
If you want to return a status instead of nothing, you use an Either type (as mentioned in the post). In an Either, you populate left or right, but not both. You can let left be your status and right be your template type. You could codify this as StatusOr<T>.
In a StatusOr land, you return if there's an error; otherwise you continue the computation. This gives a few advantages, but one is that you control the flow of control. (This is also done decently with exceptions in Java, iirc)
However, all of these - maybes, either - they make your functions composable.
There's a good talk... Railroad oriented programming... that dives into this.
In Scala, you're encouraged to write more bulletproof code by avoiding exceptions at all time, simply because it's easier to realize that (in your case) getSomeRiskyNumber returns an option instead of an integer.
Reading this code you know you're always going to generate a json response to the user, and depending upon what getSomeRiskyNumber() returns, the json response will either look like { "risk": 23 } or { "error": "Error retrieving number." }. No exceptions needed.
This is something that can be paralleled easily with @Nullable. Your IDE will even tell you, before compiling, that you should expect a null case.
Also, currently, Java doesn't have a match {} expression that works with Some() and None. I belive that would completely change my opinion and the name of the game.
The match is just my personal preference to deal with option types, because I personally think it's cleaner to read. This reduces the number of flatMap chains in my code, but again, personal preference.
Option(JavaClassFactory.staticMethodThatCanReturnNull()).map(i => i + 1).getOrElse(0)
This is a very readable function, imo, and is how 99% of Scala programmers deal with legacy Java libraries. Scala even has the Try() object, where you can do something like this:
Try(OldJavaClass.methodThatThrowsExceptions()).toOption.map(i => i + 1).getOrElse(0)
It makes creating service-layer code so much simpler because you aren't caught in that rut of "well now I have to deal with this exception at the controller level". You can head off all that garbage very quickly. I suspect that a lot of Java Optional coders will start acting like this.
> To just return 1 value you've allocated at least one object (Optional)
May be nitpicking, but in your other example, you've allocated at least one object (Integer) to just return 1 value, which means that supertight performance is not important in getSomeReskyNumber
That construct is more readable to me than an if(drawing != null) check. The cognitive load issue is a bit of a red herring. I'm used to optionals now and they're no more of a cognitive load than an if construct. But that's eye of the beholder stuff, so let's park the cognitive load issue for now.
Getting down to the meat of your argument, why is optional better than an if not null check or an exception?
I think anyone can see why it's better than throwing an exception. Optional makes it obvious to the caller that (a) there can be no response, and (b) what happens exactly when there's no response. With exceptions, either they're unchecked, in which case you don't know what exception to catch without peeking, and it's really easy to forget to add a try/catch handler at all; or it's checked, but even in that case the handling code is uglier than an equivalent optional construct.
The more interesting question is why it is better than deliberately returning null. Again, it's about signaling. A library author can signal to the caller that they should expect not to get a value sometimes. Yes, it's possible to return null from a method that returns an optional, and this will cause an NPE, but in that case the error is the library's, not the callers. In making API's, you want to encourage correct use, and optionals help do that.
Optionals in java suffer from the same problem as most API's in java do though. They're uglier than they need to be, and some common use patterns are awkward. For example, in my spark code in scala I'll use constructs like this one to convert a stream between types where the conversion might fail for some elements (and I don't care about the ones where it does):
val someIntermediate = someStream.flatMap(convertMaybe)
That works because Option can be handed to flatMap directly.
But in Java streams, you can't do that, and you end up with constructs like this:
Any way, I think optional has its uses in Java. It's definitely something that helps when building out robust API's. But where optionals would be most valuable, the combination with streams and futures, is where they're painful to work with. That's par for the course in Java though. Oracle's not actually all that good at designing API's. Usually the first attempt at something turns out so broken it has to be replaced wholesale later (like java.util.Date), or it's so ugly it feels bad to use it (CompletionStage? seriously?). But, to each their own, I'm sure my attempt at it would be even worse, and despite the warts I still like Java 8 overall.
The way I see it it's like this:
vs I get that the author has adopted a more functional programming methodology for dealing with their data but for some tasks this isn't acceptable. To just return 1 value you've allocated at least one object (Optional) and make at least 2 function calls on it (isPresent() and get()).You get `if (value == null)` for free. Throwing exceptions is very heavy and I'd place Optional<> above that but there isn't any way to signify the error that you acctually got. You'd need to make an Optional<Maybe<T>> where Maybe<T> supports error/exceptions. Maybe Maybe<T> holds data and you can extend it with BadMaybe<T> who extends exception or something so you can put that in instead of your value to signal your exception.
I don't see the benifit. Maybe I'm just crazy but `if (v == null)` has all the features Optional has for less of an overhead and less cognitive load. (If you're afraid of NPEs then just document all the return states of your methods and use @nullable to show when you need to check. IIRC IntellJ catches that kind of mistake).