问题
I have the following code snippet, that does not get compiled:
trait Environment[F[_]] {
def get(v: EnvVariable): F[Option[EnvValue]]
}
final class LiveBadEnvironment[F[_] : Sync] extends Environment[F] {
override def get(v: env.EnvVariable): F[Option[env.EnvValue]] = None.pure[F]
}
the compiler complains:
[error] found : F[None.type]
[error] required: F[Option[io.databaker.env.EnvValue]]
[error] (which expands to) F[Option[io.databaker.env.EnvValue.Type]]
[error] Note: None.type <: Option[io.databaker.env.EnvValue], but type F is invariant in type _.
[error] You may wish to define _ as +_ instead. (SLS 4.5)
[error] override def get(v: env.EnvVariable): F[Option[env.EnvValue]] = None.pure[F]
What am I doing wrong?
回答1:
I have changed to override
def get(v: env.EnvVariable): F[Option[env.EnvValue]] = F.pure(None)
and got the error messagenot found: value F
. What am I doing wrong?
Consider how the name F
is used in implicit F: Applicative[F]
def foo[F[_]](implicit F: Applicative[F]): F[Option[String]] = F.pure(None)
| | | |
type value type "type as value"
Note how the value parameter F
has the same name as the type parameter F
. Now calling a method on value F
looks like as if we are calling a method on a type
F.pure(None)
Invoking a method on a type using dot syntax is not possible in Scala, but conceptually that is what we are doing - we wish to convey the idea of invoking a "type-level" function. This naming convention works because values and types live in two separate universes so we can reuse the same name without clashes. For example, consider why the following is legal
scala> object f { def f[f](f: f): f = f }
| val Int: Int = 42
object f
val Int: Int = 42
Now when using context bound :
notation
def foo[F[_]: Applicative]: F[Option[String]] = Applicative[F].pure(None)
we do not have the name of implicit value parameter to work with, so we cannot use the above convention trick and call
F.pure(None)
because, again, dot notation on types is strictly speaking illegal, so instead we use the companion object with main method trick
Applicative[F].pure(None)
This works because Applicative
companion has something like
Applicative {
def apply[F[_]](implicit instance: Applicative[F]): Applicative[F] = instance
}
so calling
Applicative.apply[F]
or shorter
Applicative[F]
returns the implicit instance
in scope. At this point we do have our value to work with and so dot notation becomes legal
Applicative[F].pure(None)
|
ok because invoked on a value
Therefore, you have to call with Sync[F].pure(None)
instead of F.pure(None)
, because in your particular case, you are using context bounds.
回答2:
Here's a minimised example of the issue that shows the import that brings in the pure
extension method:
scala> import cats.Applicative, cats.implicits._
import cats.Applicative
import cats.implicits._
scala> def foo[F[_]: Applicative]: F[Option[String]] = None.pure[F]
^
error: type mismatch;
found : F[None.type]
required: F[Option[String]]
Note: None.type <: Option[String], but type F is invariant in type _.
You may wish to define _ as +_ instead. (SLS 4.5)
The problem is that the type of None.pure[F]
is inferred to be F[None.type]
, without the expected return type influencing inference. If you desugar the context bound and extension method, type inference will work as you expect:
scala> def foo[F[_]](implicit F: Applicative[F]): F[Option[String]] = F.pure(None)
def foo[F[_]](implicit F: cats.Applicative[F]): F[Option[String]]
You could also write out something like Option.empty[String].pure[F]
, etc.—there are a lot of ways to run into this kind of issue (too-precise type inference), and a lot of ways to work around it, and which you should choose is mostly a matter of taste.
来源:https://stackoverflow.com/questions/62495683/optionio-databaker-env-envvalue-but-type-f-is-invariant-in-type