为什么Scala不会推断出正确的ValidationNel类型?

时间:2017-05-18 06:16:05

标签: scala type-inference scalaz

    def isEven(x: Int) = 
          if (x % 2 == 0) x.successNel else "not even".failureNel

在上面的代码中,isEven的返回类型被推断为scalaz.Validation[scalaz.NonEmptyList[_ <: String],Int]

明确指定返回类型有效,

    def isEven(x: Int): ValidationNel[String, Int] = 
          if (x % 2 == 0) x.successNel else "not even".failureNel

有人能解释幕后发生的事情吗?这是Scala的类型推理系统的限制吗?

我试图在以下表达式中使用上述函数,

(isEven(4) |@| isEven(6) |@| isEven(8) |@| isEven(10)) {_ + _ + _ + _}

并且只有第二个变体(具有显式返回类型)有效。

2 个答案:

答案 0 :(得分:3)

scalaz.NonEmptyList[String]scalaz.NonEmptyList[_ <: String]的子类型,表示scalaz.ValidationNEL[String,Int]scalaz.Validation[scalaz.NonEmptyList[_ <: String],Int]的子类型。在这种情况下,编译器只会为您提供更精确的类型...

修改 raisin bread 运算符需要Applicative个实例。在这种情况下,我认为scalaz为ValidationNel[String, ?]提供了这样的实例,但不是为isEven推断的更精确的类型。

使用Option

可以观察到更简单的表现形式
val some = Some(1)
val none = None
(some |@| none)(_ + _) // Cannot find implicit Applicative[Some] :(

您可以将其视为本地类型推断的交叉限制。你也可以说FP并不总能与OO很好地配合。我建议遵循良好实践并使用显式类型注释所有公共方法和含义。

答案 1 :(得分:1)

我将尝试解释有关的更多细节:

为什么scalaz.Validation[scalaz.NonEmptyList[_ <: String],Int]这不能与|@|一起使用?

isEven(4) |@| isEven(6) ...

首先isEven(4)尝试implicitlyValidation[+E, +A]类型转换为ApplyOps[F0.M,F0.A]类型,并使用|@|运算符。作为ApplySyntax中的implicit方法:

  implicit def ToApplyOpsUnapply[FA](v: FA)(implicit F0: Unapply[Apply, FA]) =
    new ApplyOps[F0.M,F0.A](F0(v))(F0.TC)

如上面的代码所示,对于此implicit转换,我们还需要隐式 F0: Unapply[Apply, FA]变量。对于UnApply implicits,它位于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
    }

作为Validation类型,它使用unapplyMFA implicits变量。在那里,我们还发现它正在寻找另一个含义 TC0: TC[M0[F0, ?]]变量。如前ToApplyOpsUnapplyTC0Apply类型,也可以是Applicative类型。所以它会尝试查找ValidationApplicative 暗示,它位于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
    }

代表Semigroup定义:

trait Semigroup[F]  { self => //F is invariant, unlike Option[+A]

问题是:F 类型不变,与Option[+A]不一样,编译器找不到合适的适用暗示输入Validaiton

有一个关于如何为此启用类型变体的小型演示:

  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
    }

在那里,我们只是从L 取消绑定 Semigroup,所以现在这是类型变体。只是为了好玩;)。