But there's one catch: Some(null) cannot be represented.
We could invent a new special subtype of Option (Maybe, anyone?) that is guaranteed to contain non null values only. Here are the core definitions:
abstract class Option[+T]abstract class Maybe[+T] extends Option[T]class Some[T](x: T) extends Option[T]class Just[T](x: T) extends Maybe[T] { require(x != null) }object None extends Maybe[Nothing]
On Mon, Jul 9, 2012 at 3:24 PM, martin odersky <martin....@epfl.ch> wrote:We could invent a new special subtype of Option (Maybe, anyone?) that is guaranteed to contain non null values only. Here are the core definitions:
abstract class Option[+T]abstract class Maybe[+T] extends Option[T]class Some[T](x: T) extends Option[T]class Just[T](x: T) extends Maybe[T] { require(x != null) }object None extends Maybe[Nothing]The below is from my opening post in the 2008 thread. The problem still seems the same to me: even if null is excluded, a single Option type offers no way to distinguish between different meanings of "optional". Some(null) is only one manifestation of the semantic mismatch between what is being asked by the caller and what is being answered by the callee.
Thus do I think "Option" should be of a higher kind, giving rise to multiple "Options" where the type constructor says something specific about what the extra bit is trying to say.Contrast Option#apply with Traversable#find, for instance. Both return Option[T]. But the meanings are far apart - and once we leave the caller's immediate vicinity, all possible distinction is gone.> That particular example is problematic only because Option is> overloaded with at least two meanings in common use: as an all-purpose> replacement for null ("this variable may or may not have a legal value> right now") and to layer a boolean on top of an arbitrary value ("the> answer to your question is either yes or no, and if yes, take this> too.") Then of course there are the multiple uses for null further> complicating the matter (unassigned, various forms of failure, end of> sequence, I shudder to imagine how many others.)>> So while Option[T] is a tolerable replacement for null in some> circumstances, it's a long ways from ideal. I am sure some people have> thought about this in more detail. If the two uses I named above were> separated into distinct containers (Option[T] and Maybe[T], say) could> Some(null) then be disallowed without breaking a lot of code? Are there> many more difficulties lying in wait?
There are always reasons to split types up into more fine-grained entities, but there are also strong reasons against. I.e. Alan Perlis: "It's better to have 100 functions operate on one data structure than 10 functions on 10 data structures." I don't subscribe to this unequivocally, but there's some truth in it insofar as every distinction of data structures comes at a cost.
On Mon, Jul 9, 2012 at 4:02 PM, martin odersky <martin....@epfl.ch> wrote:There are always reasons to split types up into more fine-grained entities, but there are also strong reasons against. I.e. Alan Perlis: "It's better to have 100 functions operate on one data structure than 10 functions on 10 data structures." I don't subscribe to this unequivocally, but there's some truth in it insofar as every distinction of data structures comes at a cost.Since they would all be derived from the same Option[M[_]], I think it could be arranged so the distinctions were only there for those who cared.It is interesting to consider that ifwere fixed, and we did unbox None to null, then multiple Option parents could be nested and still have no cost.
class PossibleMember[A]Found[A](x: A) extends PossibleMemberNotFound extends PossibleMemberclass PossibleNull[A]NotNull[A](x: A) extends PossibleNullIsNull extends PossibleNullWe can return Found(NotNull(x: AnyRef)) and it's the same as x. The various "Some" semantics survive in the type system.
No, if Found and NotNull are value classes thenFound(NotNull(x: AnyRef)) would be represented asNotNull(x: AnyRef). You have to box the arguments of parametric value classes.
We could invent a new special subtype of Option (Maybe, anyone?) that is guaranteed to contain non null values only. Here are the core definitions:
One solution to have this by 2.11 is to add another subclass of Option with a private constructor, that can only be instantiated with non-null values.
We could then redirect all Option construction to object Option.apply, thus getting either None or SomeNonNull, and we can make them value classes in 2.11:
The deprecation cycle would be:
- 2.10 - deprectate the Some constructor and redirect people to Option (Option would still extend AnyRef)
- 2.11 - remove the Some constructor, replace Some by SomeNotNull and make everything a case class
Did I miss anything, or could we actually do this?
Now that we have value classes, the next optimization frontier is Option. I have been looking at that lately. The idea is to unbox as follows:Option[T] ==> the boxed version of T (which is always a reference type)Some(x) ==> the boxed version of xNone ==> nullHere are some examples of unboxed representations:Option[String] ==> StringOption[Int] ==> IntegerOption[Option[Int]] ==> Option[Integer]Some(1) ==> new Integer(1)Some("abc") ==> "abc"Some(None) ==> NoneSome(Some("abc")) => Some("abc")It all works out beautifully. Almost all instances of Some objects would be eliminated. Scala maps could run at the same speed as Java maps because they would return exactly the same runtime values, but without the danger of NPEs.
But there's one catch: Some(null) cannot be represented. According to the translation scheme, Some(null) should translate to null, but that's already reserved for None. So Some(null) needs to be the same as None.
Construction is only half the story: Some would have to drop out of case class status and sprout an extractor, so that it continued to yield a value on both SomeNotNull(x) and OldSome(x). (Some and None still have to cover all values, except sigh null.) This might have unpopular effects on performance.On Mon, Jul 9, 2012 at 4:31 PM, Vlad Ureche <vlad....@gmail.com> wrote:One solution to have this by 2.11 is to add another subclass of Option with a private constructor, that can only be instantiated with non-null values.
We could then redirect all Option construction to object Option.apply, thus getting either None or SomeNonNull, and we can make them value classes in 2.11:
The deprecation cycle would be:
- 2.10 - deprectate the Some constructor and redirect people to Option (Option would still extend AnyRef)
- 2.11 - remove the Some constructor, replace Some by SomeNotNull and make everything a case class
Did I miss anything, or could we actually do this?
My suspicion-o-meter has elevated mercury at your proposal but I have a prior engagement so we will have to bear this cloud of suspicion for a time.
On Tue, Jul 10, 2012 at 1:38 AM, Paul Phillips <pa...@improving.org> wrote:Construction is only half the story: Some would have to drop out of case class status and sprout an extractor, so that it continued to yield a value on both SomeNotNull(x) and OldSome(x). (Some and None still have to cover all values, except sigh null.) This might have unpopular effects on performance.On Mon, Jul 9, 2012 at 4:31 PM, Vlad Ureche <vlad....@gmail.com> wrote:One solution to have this by 2.11 is to add another subclass of Option with a private constructor, that can only be instantiated with non-null values.
We could then redirect all Option construction to object Option.apply, thus getting either None or SomeNonNull, and we can make them value classes in 2.11:
The deprecation cycle would be:
- 2.10 - deprectate the Some constructor and redirect people to Option (Option would still extend AnyRef)
- 2.11 - remove the Some constructor, replace Some by SomeNotNull and make everything a case class
Did I miss anything, or could we actually do this?
Yes, we'd have to drop the case class from the beginning -- and that would equate to pushing Some(null) outside the legal boundary by redirecting everyone to object Option.apply:
2.10:
sealed abstract class Option[+T]
case object None extends Option[Nothing]
sealed abstract class Some[T] extends Option[T]
case class SomeNotNull[T](t: T) extends Some[T]
case object DreadedNull[T] extends Some[T]
object Some {
@deprecated("2.10", "The Some constructor Some(t) is being replaced by Option(t) for performance reasons. It will be removed in the next major release")
def apply[T](t: T) = if (t eq null) SomeNotNull(t) else DreadedNull
def unapply[T](opt: Option[T]): Option[T] = opt match {
case None => None
case SomeNotNull(t) => Some(t)
case DreadedNull => Some(null)
}
Hi,
I very much like the idea of having an Option-like type that can be optimized that way (be it Option or Maybe or whatever).
However, even if you go the Maybe version, keeping Option for pattern-matching and non-breaking changes, it seems to me there is still a catch. Consider a value x of type Maybe[Maybe[T]]. If, at runtime, x is null, does it mean that x == MaybeSome(MaybeNone) or that x == MaybeNone?
And this can be not apparent at compile time: e.g., a function working with Maybe[A], that is called with A =:= Maybe[T]. So I even see no way you can de-optimize the particular case of Maybe[Maybe[T]] to solve that issue.
If, at runtime, x is null, does it mean that x == MaybeSome(MaybeNone) or that x == MaybeNone?
On Mon, Jul 9, 2012 at 6:24 PM, martin odersky <martin....@epfl.ch> wrote:Now that we have value classes, the next optimization frontier is Option. I have been looking at that lately. The idea is to unbox as follows:Option[T] ==> the boxed version of T (which is always a reference type)Some(x) ==> the boxed version of xNone ==> nullHere are some examples of unboxed representations:Option[String] ==> StringOption[Int] ==> IntegerOption[Option[Int]] ==> Option[Integer]Some(1) ==> new Integer(1)Some("abc") ==> "abc"Some(None) ==> NoneSome(Some("abc")) => Some("abc")It all works out beautifully. Almost all instances of Some objects would be eliminated. Scala maps could run at the same speed as Java maps because they would return exactly the same runtime values, but without the danger of NPEs.
(Aside: this is not the only source of pain in Scala maps; there are, or were when I last checked, numerous instances of conversion back and forth between key-value tuples and separate keys and values. These were, for mutable HashMap, at least as expensive as Options.)
But there's one catch: Some(null) cannot be represented. According to the translation scheme, Some(null) should translate to null, but that's already reserved for None. So Some(null) needs to be the same as None.
Well, can we cheat? If you're making Some and None not-really-exist (i.e. the compiler makes it look to you like they do, but underneath it does a bunch of boxing/unboxing on its own), which is an optimization unavailable to library writers, you can go the whole way and make an object SomeNull extends Some[Null], which should then be able to stand in, formally, for any instances of Some(null). It will break your boxing/unboxing scheme--you'll have to lie about your types--but you're lying about them *anyway* if you're unboxing in that way, so all you need is an instanceof (or reference equality) guard on every access where the status is not already known, and you should be good to go. Instanceof checks are pretty cheap (as are equality checks), so although this would be a minor performance penalty--fixable with NotNull--it would be probably 80% of the way to overheadless options.
But that's still much better than the current solution which needs acast as well like any other generic operation which unpacks things
from a container.
Java maps do allow null keys and values (in general; some implementations forbid it), e.g. from the javadoc for HashMap: "Hash table based implementation of the Map interface. This implementation provides all of the optional map operations, and permits null values and the null key." If you want to distinguish between a found null value and a missing value, you need to do another lookup with containsKey().
- Java map wrappers that map a null result to Some(null) if the key exists. This one is is actually pretty dubious; Java treats null as missing, so why should the wrapper do it otherwise? Besides, this "feature" alone slows down get operations on Java maps that miss by a factor of 2! I would vote for changing the behavior here.
I'd say it's dubious but not explicitly forbidden. The javadocs for java.util.Properties do not specify the treatment of null values very well. Null is treated as a missing property but there's nothing stopping you from calling setProperty("foo", null) -- it does not check for null to throw a NPE.
- Property wrappers that map a null property to Some(null). Not sure whether this one is dubious or not.
-sz
Now that we have value classes, the next optimization frontier is Option.
If Option extends AnyVal (the premise of these optimizations I think), then it can no longer be assigned null.
You only have three possibilities:
Some(x)
Some(null)
None
While Some(null) has valid use cases, it seems assigning null to an Option variable is a scary thing to do.