简体   繁体   中英

How do I get the type signature to agree with the scala compiler when extracting a value out of an Option[A]?

Let's say I have a variable x that is of type Option[Double] . I want to get that double out of the variable, so I would suspect that x.getOrElse(None) would be the solution.

The type signature of getOrElse is as follows:

def getOrElse[B >: A](default: => B): B

None is simply an Option[Nothing] . If I write the following:

def mean(xs: Seq[Double]): Option[Double] =
   if (xs.isEmpty) None
   else Some(xs.sum / xs.length)

val avg = mean(xs) getOrElse(None) // compiles
val theAvg: Double = mean(xs) getOrElse(None) // doesn't compile

What is going on here with the types? The REPL tells me(using :t) that avg is of type Any . How can this be? Why does the type match in the first place when None is of type Option[Nothing] ?

What am I not understanding about the types here?

As the type signature says, getOrElse returns a B which is a super type of A (which is the type inside the Option ) .
That makes sense since, if you have a Some(a: A) , then you return that a , which is of type A , which given it is a subtype of B , it can be upcasted to B without any problem. And, if it is a None , then you return the default which is of type B .

In your case, you are using None for your default, as such the compiler has to figure out what is the least weak upper bound between Double & Option[Nothing] , which, as you can see, it is Any (because Any is the supertype of every type, and there is not other relation between Double & Option in the type hierarchy) .

Now, I believe you are misunderstanding how getOrElse works, and what is the purpose of None .
I think this is what you really want.

// Or any other double that makes sense as a default for you.
val avg: double = mean(xs).getOrElse(default = 0.0d)

How can this be?

The inferred type Any here is a result of the compiler trying to find a common ancestor of Double and Option[Nothing] . The first common ancestor for both these types is Any , and that is why the compiler is inferring that.

Why does the type match in the first place when None is of type Option[Nothing]

Great question. Let's inspect the signature for getOrElse :

final def getOrElse[B >: A](default: => B): B

Let's zoom in on the constraint B >: A , what does that mean? It means that we can specify any type B that is a supertype of A . Why do we want it to be a super type of A to begin with? Why not A itself? If the type parameter forced us to use A here, we'd be constrained to Double and your example wouldn't compile. To answer that question, we need to look at the definition of Option[A] :

sealed abstract class Option[+A]

We see that A has a + next to it. That plus indicates the presence of covariance. In short, covariance allows us to preserve the "is subtype relation" between types which are themselves defined inside other "container" types, making the following relation hold:

A <: B <=> Option[A] <: Option[B]

Which means if A is a subtype of B , we can treat Option[A] as a subtype of Option[B] . Why does this matter and what does this have to do with the method signature of getOrElse ? Well, covariance imposes restrictions on type parameters! Covariant type parameters cannot be used as input parameters, or in input parameter position (a position is contravariant if it occurs under an odd number of contravariant type constructors, but the explanation for that is too lengthy so I'll abbreviate), they can only be placed as output parameters in the underlying type containing the type parameter. Thus, the following definition is illegal due to variance laws:

final def getOrElse(default: => A): A

Because A appears here both in the input type and the output type.

Luis did a good job explaining why you got an Any for your result type. I just wanted to add that if you wanted to use the Double for further processing, but still correctly handle a None , you would typically do that with a map , to be able to stay inside the Option until you actually have a useful value to replace None with, like:

val message: Option[String] = avg map {x => s"The average is $x."}
println(message getOrElse "Can't take average of empty sequence.")

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM