This. Especially the midas problem tells me that Option<T> is not at all a 
decent substitute for 'null'.

I personally feel like types need saner defaults, but they still have to be 
optional. For example, strings should default to "", lists should default 
to an immutable empty list, and so on and so forth. This should make it 
sufficiently less painful to work with variables that can never be null, 
that you can reasonably expect to default to 'never null' behaviour and use 
some sort of special marker to indicate that a value is allowed to contain 
null. It would then be necessary to initialize any field that is consider 
'never null', assuming its type has no default empty value.

Also, to do the concept underlying option right, AND introduce it to java 
(or at least introduce it so that you can interoperate with java), you need 
not 2 nullities (can-be-null and never-null) but 4 nullities:

* RAW - this basically warns on everything and errors on nothing and is 
analogous to using java.util.List instead of java.util.List<T> in java 
1.5+. Needed only for backwards compatibility.

* never-null. For example, if you have a List<[never-null] String>, then 
when you get strings out, you don't need to null-check them (in fact, if 
you do, the compiler should warn you that you're doing pointless work), and 
you cannot put strings into this list unless the compiler can confirm that 
the reference can't possibly be null at this juncture. That, or a runtime 
check is added which throws an NPE if you attempt to add a null to this 
thing.

* can-be-null. For example, if you have a List<[can-be-null] String>, then 
when you get strings out, you must null-check them if you try to pass them 
to things that want [never-null] Strings. On the plus side, you can shove 
whatever string you want in these, no need for null checks.

* dunno-null. This is a special case which is different from the previous 
case - it means that it is not known if nulls are allowed here or not. If 
you have a List<[dunno-null] String>, then when you get these out, you have 
to null-check, and you also can't put nulls in. The great advantage is that 
if you have a method with a parameter of type List<[dunno-null] String>, 
you can pass both List<[never-null] String> and List<[can-be-null] String> 
into it and that will work as expected without heap corruption. This solves 
Option's major downfall: it is not feasible to write a method, even one 
that never writes nulls in and always null-checks when reading, which 
accepts both a List<T> and a List<Option<T>> transparently, if it needs to 
do things to the inner T (if the inner T is not null, of course).


The above 4 kinds of null are roughly anologous to the way any given type 
can show up in 4 different styles in generics: List (raw), List<String>, 
List<? extends String>, List<? super String>.

Such a type system would eliminate all runtime bugs associated with NPEs 
and turn them into compiler errors. The downside is that this is very 
complicated: We have 4 separate nullities so how do we specify them for 
each type? Keywords? That might turn out rather wordy. Symbols, like 
"String?" for can-be-null, "String!" for never-null, and "String" for 
dunno-null, with raw types impossible (but files compiled on old versions 
are all raw?) - that's probably gonna look rather ugly, and if the goal is 
to turn most refs into never-null, "String!" should probably be the default 
and we need to come up with another symbol to indicate dunno-null.

I have no solution for this dilemma, other than introducing an IDE which 
exceeds the ascii character set for symbols, and which introduces certain 
keyboard shortcuts to change nullity. But that's an entirely different can 
of worms.

On Monday, June 4, 2012 4:43:48 AM UTC+2, Cédric Beust ♔ 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?
>
> 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).
>
> 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.
>
> 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: _
> }
>
> 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).
>
> 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.
>
> 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.
>
> 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 view this discussion on the web visit 
https://groups.google.com/d/msg/javaposse/-/t_ihunElI3MJ.
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.

Reply via email to