Dinesh Babu K G
Dinesh Babu K G

Reputation: 498

Why doesn't Scala infer the right type of ValidationNel?

    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

Answers (2)

chengpohi
chengpohi

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

OlivierBlanvillain
OlivierBlanvillain

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

Related Questions