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?
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.