Reputation: 498
def isEven(x: Int) =
if (x % 2 == 0) x.successNel else "not even".failureNel
In the above code, the return type of isEven
is inferred as scalaz.Validation[scalaz.NonEmptyList[_ <: String],Int]
Whereas explicitly specifying the return type works,
def isEven(x: Int): ValidationNel[String, Int] =
if (x % 2 == 0) x.successNel else "not even".failureNel
Can someone explain what's going on behind the scenes here? Is this a limitation of Scala's type inference system?
I'm trying to use the above function in the following expression,
(isEven(4) |@| isEven(6) |@| isEven(8) |@| isEven(10)) {_ + _ + _ + _}
and only the second variant (with explicit return type) works.
Upvotes: 1
Views: 193
Reputation: 14217
I will try to explain more details about :
why scalaz.Validation[scalaz.NonEmptyList[_ <: String],Int]
this can not work with |@|
?
isEven(4) |@| isEven(6) ...
First of all isEven(4)
is trying to implicitly
convert it from Validation[+E, +A]
type to ApplyOps[F0.M,F0.A]
type with |@|
operator. as the implicit
method in ApplySyntax:
implicit def ToApplyOpsUnapply[FA](v: FA)(implicit F0: Unapply[Apply, FA]) =
new ApplyOps[F0.M,F0.A](F0(v))(F0.TC)
As the above code, for this implicit
conversion, we also need an implicitly F0: Unapply[Apply, FA]
variable. for UnApply
implicits
, it's in the UnApply:
implicit def unapplyMFA[TC[_[_]], M0[_[_], _], F0[_], A0](implicit TC0: TC[M0[F0, ?]]): Unapply[TC, M0[F0, A0]] {
type M[X] = M0[F0, X]
type A = A0
} =
new Unapply[TC, M0[F0, A0]] {
type M[X] = M0[F0, X]
type A = A0
def TC = TC0
def leibniz = refl
}
As the Validation
type, it's using the unapplyMFA
implicits
variable. In there we also found it's looking for another implicits TC0: TC[M0[F0, ?]]
variable. as the before ToApplyOpsUnapply
, In there TC0
will be Apply
type, that's also can be Applicative
type. so it will try to look for the Validation
to Applicative
implicits, it's in the Validation.scala
implicit def ValidationApplicative[L: Semigroup]: Applicative[Validation[L, ?]] =
new Applicative[Validation[L, ?]] {
override def map[A, B](fa: Validation[L, A])(f: A => B) =
fa map f
def point[A](a: => A) =
Success(a)
def ap[A, B](fa: => Validation[L, A])(f: => Validation[L, A => B]) =
fa ap f
}
for the Semigroup
definition:
trait Semigroup[F] { self => //F is invariant, unlike Option[+A]
So the problem is: F
is type invariant, not like the Option[+A]
, compiler could not find an appropriate Applicative implicits for this type Validaiton
.
There is a small demo for how to enable type variant for this:
def isEven(x: Int): Validation[NonEmptyList[_ <: String], Int] =
if (x % 2 == 0) x.successNel else "not even".failureNel
val res: String = foo(isEven(4))
def foo[FA](v: FA)(implicit F0: Unapply[Apply, FA]): String = "foo bar"
implicit def ValidationApplicative[L]: Applicative[Validation[L, ?]] =
new Applicative[Validation[L, ?]] {
override def map[A, B](fa: Validation[L, A])(f: A => B) =
fa map f
def point[A](a: => A) =
Success(a)
def ap[A, B](fa: => Validation[L, A])(f: => Validation[L, A => B]) =
//fa ap f
null
}
In there we just unbind L
from Semigroup
, so now this is type variant. just for fun;).
Upvotes: 1
Reputation: 7768
scalaz.NonEmptyList[String]
is a sub type of scalaz.NonEmptyList[_ <: String]
, meaning scalaz.ValidationNEL[String,Int]
is a sub type of scalaz.Validation[scalaz.NonEmptyList[_ <: String],Int]
. The compiler just give you a more precise type in this case...
Edit: the raisin bread operator requires an Applicative
instance. In this case I suppose that scalaz provide such instance for ValidationNel[String, ?]
, but not for the more precise type inferred for your isEven
.
A simpler manifestation of that can be observed with Option
:
val some = Some(1)
val none = None
(some |@| none)(_ + _) // Cannot find implicit Applicative[Some] :(
You can view that as an intersect limitation of local type inference. You could also say that FP doesn't always play well with OO. I would suggest following good practices and annotate all your public method and implicits with explicit types.
Upvotes: 3