On 4 June 2012 03:43, Cédric Beust ♔ <[email protected]> wrote: > I just listened to the latest podcast and I'm a bit surprised by the > explanation that Dick gave about Scala's Option type, so I thought I'd make > a few comments (and congratulations to Tor and Carl for pressing Dick to > clarify some of the things he said). > > First of all, Option's effect is 100% runtime, not static. It's nothing > like @Nonnull or @Nullable. Dick, can you explain why you said that Option > added static checking? >
It's *everything* like @Nullable, insofar as it tells you at compile time (i.e. statically) that "Here is a thing which might not have a value". Nulls, on the other hand, work on the basis of a type system where *any* object might not have a value (note: primitives are not objects), you therefore have to risk null pointer exceptions anywhere. The important part here is whether the declaration of possible emptiness is static or dynamic, not the whether the actual representation of an empty value is static or dynamic. Option has the advantage of being universally supported within Scala whereas @Nullable/@NotNull support is far less widespread. Of course, if you use @NotNull or Option then you must also have the discipline that you won't put a null value into such an entity[1], because there's nothing at the bytecode level to protect you here. [1] It can sometimes make sense in rare cases to have a Some(null). For example, if a map contains the key-value pair "a" -> null then a lookup against "a" would correctly return Some(null), None would be the correct return value if the key "a" wasn't present at all. In reality, you should never design data structures to allow for such a possibility, but it could well happen if dealing with a map produced by some java library. flatMap makes it possible to still keep such designs very clean, less so with @Nullable and the elvis operator. If you want to get an intuition for Option, it's described in details in > the GOF book (1994!) under the name "Null Object Design Pattern" (here is > the wikipedia entry <http://en.wikipedia.org/wiki/Null_Object_pattern>, > although it doesn't mention the GOF book so maybe I'm misremembering). > Not so. A "Null Object" is treated like any other instance of the same type. An Optional value still requires some form of explicit handling in your code path, although this is typically very lightweight and with minimal ceremony thanks to methods such as map and flatMap. It's a great deal less intrusive than explicit runtime checks for null values. The idea behind Option is for the program to continue working even if a > method is invoked on a "null" (called "None" in Scala and "Nothing" in > Haskell) object. Obviously, null checks are still present but since they > are hidden inside the Option object, code that operates on these objects > can be oblivious to them. In other words, if f is an Option of any type, > f.foo() will never crash. Either it will actually call foo if there is an > object inside the Option or it will do nothing if there is no object. > It's not a null check. At the bytecode level it normally boils down to an isInstanceOf check. Different mechanism, but with the same ultimate effect. f.foo() is also invalid syntax, foo() isn't defined on Options. Instead, you would: f map { _.foo() } > Here are some of the problems with this approach. > > Let's say that you have an HTTP request object and you want to see if a > GET parameter is present. Since this parameter could not be there, it's > reasonable to use an Option[String]: > > class HttpRequest { > def getParameter(val name: String) : Option[String] { ... > > > Now that you have this parameter wrapped in an option, you want to pass it > to another method. Either that method takes a String or an Option[String]: > > 1) def process(val param: String) { ... } > 2) def process(val param: Option[String]) { ... } > > In the first case, you have two choices: 1a) either you extract that > string from the option or, if that process() method belongs to you and you > can change it, 1b) you can modify its signature to accept an Option[String] > instead of a String (and obviously, you need to change its implementation > as well). > > The 1a) case is basically checking against null, like Java. You will do > something like: > > Option[String] p = req.getParameter("foo") > p match { > case Some(s) : process(s) > case None: _ > } > > So process only gets called if the foo parameter is present? I'd use: req.getParameter("foo") foreach { process(_) } or, in the so-called "point free" style: req.getParameter("foo") foreach { process } Only if it makes sense to call process with no value would I define it to accept an optional value. Or, if defined in java to make use of nulls, I'd call it as: process(req.getParameter("foo").orNull) Look ma! No explicit checks! > You have gained nothing (I touched on this particular scenario in this > blog > post<http://beust.com/weblog/2010/07/28/why-scalas-option-and-haskells-maybe-types-wont-save-you-from-null/> > ). > > If 1b) is an option (no pun intended) to you, you go ahead and modify your > process() method to accept an Option, and your modified code will probably > have some matching code as shown above. And if you don't do it at this > level, you will, eventually, have to extract that value from the Option. > > Scenario 2) is the best of both worlds: you have two methods that > communicate with each other in terms of Option, and this buys you some nice > properties (composability). > Again, why? If it makes no sense for process to be called with no value, then why define it so it can accept no value? It's disingenuous to anyone using the method and it goes against the principles of self-documenting code. > As you can see from this short description, the benefit of Option is > directly proportional to how many of your API's support Options. This is > often referred to as the "Midas effect", something that used to plague the > C++ world with the `const` keyword. The direct consequence is that you end > up reading API docs with method signatures that contain a lot of Options in > them. > > The problem with this rosy scenario is that you can't ignore the Java > world, and as soon as you try to interoperate with it, you will be dealing > with raw (non Option) values). So the scenario 1) above is, sadly, common. > So you either use "foreach" or "orNull" as appropriate to handle this interop, which is still typically very lightweight. > Another dark side of Options is that they tend to sweep errors under the > rug. You definitely see a lot less NPE's when you use options, because what > is happening is that your code is happily working on nonexistent objects > (by doing nothing) instead of blowing up. That's fine if your logic is > expected to sometimes return nonexistent objects but there are times when > you are dealing with Options that should never contain None. The correct > thing to do here would be to leave the Option world, extract the underlying > object and keep passing this, but then you are back to the interop problems > described above between raw objects and boxed ones. > > Debugging applications that have misbehaving Option objects is quite > challenging because the symptoms are subtle. You put a break point into > your code, look into the Option and realize that it's a None while you > would expect a Some. And now, you have to figure out what part of your code > returned a None instead of a Some, and Option doesn't have enough > contextual data to give you this information (some third party libraries > attempt to fix that by providing improved Options, e.g. scalaz's > Validation). > > This is why I think of Options as having a tendency to "sweep errors under > the rug". > > From a practical standpoint, I think that the "Elvis" approach which I > first saw in Groovy and which is also available in Fantom and Kotlin is the > best of both worlds. It doesn't offer the nice monadic properties that > Option gives you, but it comes at a much cheaper price and less boiler > plate. > > Finally, Option is still an interesting beast to study since it's a > gateway drug to monads. You probably guessed it by now but Option is a > monad, and understanding some of the properties that it exhibits (e.g. you > can chain several options) is a good first step toward getting a good > understanding of the appeal of monads, and from then on, functors and > applicatives are just a short block away. > And this is the real power of Options! imagine you have multiple params to be used in the preciously mentioned process method. I'll also assume that req.getParameters returns a map of params to values. val params = req.getParameters for{ foo <- params get "foo" bar <- params get "bar" baz <- params get "baz" } { process(foo,bar,baz) } Easy peasy, process now only gets called if all three params are present. Still no explicit checks required. > Dick, would love to get more details on what you were explaining during > that podcast! > > -- > Cédric > > -- You received this message because you are subscribed to the Google Groups "Java Posse" group. To post to this group, send email to [email protected]. To unsubscribe from this group, send email to [email protected]. For more options, visit this group at http://groups.google.com/group/javaposse?hl=en.
