Some quick googling found me: http://fernandocejas.com/2016/02/20/how-to-use-optional-on-a... ...
One thing I've found is that once you start using Optionals, they tend to infect (I don't mean this in a bad way) the rest of the code. You start to want Optionals everywhere, and start writing maps/orElses everywhere, which I personally love. The error-handling paradigm changes a little bit (to errors-as-values, which I personally appreciate as well), but some are turned off by having optionals everywhere.
An Optional in Java is just another value, and nothing in practice stops that from being null. I.e. a method that returns an Optional can still return an actual null if poorly implemented. So the major benefit Optionals give you in Java is a typed declaration that the method may return an "empty" value, which is certainly helpful for knowing what cases your client code needs to handle, but it does not guarantee that the method will never return null. It reduces the probability of unhandled null pointers, but it does not eliminate them.
On the other hand, Swift & co. guarantee at compile time that a function which says it returns an Optional will always return an an Optional, never a null, and, better still, if a function says it returns some non-optional value, it still is guaranteed to never return null.
It's great that Java is pushing for this style of code, but I'm afraid without compile-time guarantees of return values it will always feel less powerful there.
However I tend to think that what's important about Optionals is the spreading of the notion of thinking critically about failure modes around improper input/output. "Defensive" coding is often considered a mid-range skill (at least I think), and IMO it's because a lot of junior programmers just assume if some method get ObjectThing a, it's going to be there, and not null. I think merely seeing Optional<ObjectThing> a encourages movement in the right direction as far as program correctness, a road that will almost surely tour through concepts and languages that take that kind of correctness more (ex. Swift, Haskell).
Also, Optionals are a light introduction to functors which is nice.
For more concrete actionable stuff please see other comment (https://news.ycombinator.com/item?id=13433335), there are tools that can do this at compile time for java
Why are optionals good and checked exceptions bad? (I'm certainly not implying that one replaces the other - i'm just saying that it they both have an origin of forcing programmers to think about errors)
Exceptions are exceptional; the whole point is that you aren't supposed to have to think of them in normal program flow. Go gets this wrong too, forcing three extra lines of if err != null { for pretty much every single function call -- and with the added bonus of destroying stack context with each return.
I love checked expressions, hate Optionals.
I'll be happy when the language pimps stop trying to make Java look and act like a scripting language.
Edit: I apologize if I've offended anyone with my opinions. I hope this helps. http://bit.ly/1S1943H
They generally also break encapsulation (though you can kind of work around/sidestep this by making top-level visible errors more opaque)
Do you have a good reason you'd like to share for liking checked expressions? It seems like the reason you like it is because it's enforced by the compiler -- but I don't think this makes them the right choice, as the compiler could easily also enforce exhaustive handling of an Optional (or sum types like other languages).
If we limit ourselves to the case of Java 1.8, it is a fact that optionals are not checked by the compiler, so there is a solid benefit of using checked exceptions, solely that there is a compile time check of whether the exception was accounted for.
Because they're the best solution for the problem. In Java.
The problem with Java's checked exceptions is misuse; turgid overwrought frameworks which obfuscate. Work closer to the metal. Eschew Spring, JPA, Annotations, aspects (AOP). All terrible ideas (making Java more dynamic) implemented terribly. (Quoting myself: Spring is an exception obfuscation framework.)
If you have to catch an exception you can't handle, revisit the design.
Your linked comment references sum types (Rust, Haskell) and multiple return types (Go). I haven't used those, so I can't comment.
The only solution better than checked try/catch/finally might be proper state machines. Experimenting with that is (far down) on my to do list. I'm also curious about Erlang/Elixir (and CSP in general), which uses a completely different strategy for error handling.
*"... Java 1.8, it is a fact that optionals are not checked by the compiler..."
Java's Optionals are a bad idea implemented badly.
The Correct Answer is using the Null Object pattern. Or just use LISP/Scheme. Also good ideas are intrinsic path expressions (vs method chaining) and pattern matching.
---
Top-thread, someone shared the "composition over inheritance" heuristic. This is more correct. Null Object and composition are like peas and carrots.
You mention "breaking encapsulation". So? Choose composition, make most objects dumb, implement the smart/active parts as yet another Interpreter. Problem solved.
Optional is not a functor, in fact it violates the functor laws quite blatantly.
Also, when I wrote "functor" I was thinking of https://wiki.haskell.org/Functor, is that what you're thinking of? In that case, which of the rules does it break? if not, what definition of functor were you thinking of?
Optional.of(whatever).map(identityfunction) will definitely give you back an Optional<Whatever>... Am I missing something fundamental? Also Optional.of(whatever).map(f).map(y) is equivalent in return to Optional.of(whatever).map(f . y)... (of course that's not the java syntax for composing functions but I digress)
val str: Function[String, String] = s => if (s.length > 3) s else null
val num: Function[String, Integer] = s => if (s == null) -1 else s.length
With Optional, you receive different results depending on whether you call map twice, or combine the functions first: scala> Optional.of("Foo").map[String](str).map[Integer](num)
res12: java.util.Optional[Integer] = Optional.empty
scala> Optional.of("Foo").map[Integer](str.andThen(num))
res15: java.util.Optional[Integer] = Optional[-1]
This is incorrect and violates the functor law.You may also enjoy a language that supports the generalization of Optionals, which are Algebraic Data Types (ADTs). Optionals are a single, limited application of ADTs.
Optionals allow you to shift null pointers (and null pointer exceptions) to the type level. So instead of having to worry about a function returning null, you just deal with Optionals whenever a function returns one.
There's another ADT, usually called "Either" or "Result", that allows you to shift exceptions to the type level. You can see from the type of the function what kind of exception it might return, and you deal with exceptions like any other value instead of via a special exception handling mechanism.
True, but perhaps not practically relevant. The pattern matching in Swift isn't much better than what you would get with a tagged C union, which is why no one really uses it very heavily. The Optional type in Swift gets a lot of special compiler support, which is indicative of the fact that the broader language isn't very friendly towards using ADTs to structure data.
But you're right, I should have clarified in my comment that Swift does have a basic degree of support for ADTs.
In what way? Pattern matching in Swift is quite powerful; definitely better than C's switch statement. This blog post is a good overview of its capabilities. https://appventure.me/2015/08/20/swift-pattern-matching-in-d...
enum in Swift has pattern matching support, can have properties, adopt protocols, etc.
I would disagree with your assessment that "nobody uses them".
> The Optional type in Swift gets a lot of special compiler support
Only the ?. optional chaining operator (and technically the ! postfix operator too, though that could have been implemented in the standard library instead of being special syntax and in fact I'm not really sure why it isn't just a stdlib operator). And probably some logic in the switch exhaustiveness checker so it knows nil is the same as Optional.none. Everything else (including the actual pattern matching against the "nil" keyword) is done in a way that can be used by any type, not just Optionals (nil for pattern matching specifically evaluates to a stdlib type called _OptionalNilComparisonType which implements the ~= operator so it can be compared against any Optional in a switch, and you could do the same for other ADTs too, assuming they have a reasonable definition for nil).
For example, in Haskell, by default you can't compare an Optional with the value it wraps: `5 == Just 5` fails. But in Swift this works like you would want.
All that is to say that Options in Swift are a bit nicer than what you could get with pure ADTs. It's a similar story for Swift's syntax-level error handling vs type-level monadic error handling. (The downside of course is that the compiler has to be specially taught about these - but maybe you want that anyways, e.g. for Rust's null-pointer optimization.)
Haskell has bind (>>=) and do-syntax, rust has `and_then`. This is pretty standard with any ADT-supporting language.
> if-let
Most languages with ADT support have good pattern matching that subsumes if-let syntax and allows you to do other things as well. Swift's pattern matching, such as it is, is a bit of a mess.
> guard-let unwrapping
Haskell has `guard` and `MonadFail`, which address the use cases for this in a more principled way. `guard` for boolean matching (like equality) and `MonadFail` for structural matching (like pattern matching on a constructor).
Rust has (?) and `try`, which are syntactic sugar around a simple match/return statement that addresses most use cases of guard-let.
Obviously there are going to be small differences between this implementations, but Swift doesn't really excel here.
> `5 == Just 5` fails.
As it probably should. Supporting such implicit casting could lead to some obvious confusion and buggy behavior. Ideally, the fact that "a == b" typechecks should indicate that "a" and "b" are of the same type.
In Haskell, you would just do `Just 5 == x` if you want to check that something is, in fact, `Just 5`. If that really wasted a lot of space somehow, you can define something like `x ==: y = Just x == y`.
edit: FWIW, there's some overlap between '?' and 'guard let', but not that much. Using hypothetical Rust syntax, they'd overlap in this case:
guard let Some(x) = foo else {
return Err(GettingFoo);
}
better expressed as let x = foo.ok_or(GettingFoo)?;
…but if you want to return something other than a Result, or the ADT being matched on is something custom rather than Option/Result, there's no good way to make '?' do the job.`context(e?)` becomes `e >>= \x -> context(x)`
I should have been clear about CPSing things first so I didn't need to eta-expand :)
Rust copied 'if let' and made it far nicer and more capable by allowing arbitrary fallible pattern matches to be the condition. Then Swift 2 copied that improvement back with 'if case'.
It's totally fair to point out that Haskell is more principled - users can build Haskell's Maybe, but not Swift's Optional.
> Rust has (?) and `try`, which are syntactic sugar around a simple match/return statement that addresses most use cases of guard-let
They aren't really comparable. Rust's `try!` addresses one case: you have a Result and want to propagate any error to the caller. This is closest to Swift's `try`. But Swift's `guard` is much more flexible: it allows destructuring, boolean tests, multiple assignment, etc., and it also allows arbitrary actions on failure: return, exit(), throw, etc., with the compiler checking that all paths result in some sort of exit.
In practice this is used for all sorts of early-outs, not just error handling. It neatly avoids `if` towers-of-indenting-doom. I think the best analog is Haskell's do-notation.
There's the same tradeoff here. Rust's try! is a macro that anyone can build, while Swift's `guard` is necessarily built into the language. But any Swift programmer will tell you how much they appreciate this feature, unprincipled though it may be. Use it for a while and you become a believer!
> As it probably should. Supporting such implicit casting could lead to some obvious confusion and buggy behavior. Ideally, the fact that "a == b" typechecks should indicate that "a" and "b" are of the same type.
The principled stand! Both languages make the right decision for their use case. Swift's Optionals are much more commonly encountered than Haskell's Maybe (because of Cocoa), and so the language's syntax design optimizes for dealing with Optional. They're more central to Swift than its ADTs.
-- Probably nothing like in Swift
instance (Num a , Integral a) => Num (Maybe a) where
fromInteger x = Just (fromIntegral x)
main = print $ 5 == (Just 5) check :: Int -> Bool
check x = x == (Just x)
main = print (check 5)
I get a compile error: Main.hs:7:17: error:
• Couldn't match expected type ‘Int’ with actual type ‘Maybe Int’
• In the second argument of ‘(==)’, namely ‘(Just x)’
In the expression: x == (Just x)
In an equation for ‘check’: check x = x == (Just x)Why in the world would you want those two to be equal when they obviously don't represent the same thing?
That doesn't make sense, not even if they have the exact same memory representation, in which case I'm pretty sure it has been a compromise, which would mean you're still dealing with `null` with some lipstick on it, making that type behave inconsistently with other types in the language.
This kind of misguided convenience is exactly why equality tests in most languages are in general a clusterfuck.
This is the difference between normal people and theoretical computer scientists, summarized in one sentence.
Because I care for intended use, not ontology.
And they are different because the types say so. By allowing them to be equal, you're implicitly converting one into the other. That's weak typing by definition, a hole in the type system that can be abused.
So why have types at all? Dynamic typing is much more convenient and Clojure deals with nulls by conventions just fine.
Again, it's the use that makes it a safe guard, not its ontology.
>And they are different because the types say so. By allowing them to be equal, you're implicitly converting one into the other.
Which is fine sometimes, when you explicitly need to do it a lot.
>That's weak typing by definition, a hole in the type system that can be abused.
Any examples of how A = Just A can be abused in any meaningful way?
>So why have types at all?
Because I don't believe in the Slippery Slope fallacy, and some types are better than no types at all, but exceptions can be OK too.
Not having used them much in the past, I rather don't like them, but I'm aware of the fact they could simply take getting used to.
Anyone chime in on whether or not Optionals are really the 'future of good syntax'? Or they are quirk? Or good in some cases, not for others?
I love the compile-time optional support now and severely miss it when using languages that don't have the feature. They are brilliant when you are working with all-Swift code or Objective-C code that has been decorated with nullability keywords.
However optionals become a burden when you are working with old Objective-C code that has not been annotated.
Optionals have lots of syntactic sugar because Swift has to deal with reams of native and objective-c code which is ubiquitously nullable, dealing with that without the syntactic sugar would be nothing short of horrendous. This is also an issue with ported/converted API (which is most of them at this point) as the underlying system tends to leverage nullability extensively (it's a very common Obj-C pattern as nil is a message sink), which is usually left shining through by simple conversion rather than wrap every historical API in a converted Swift version.