在 Scala 中将选项转换为 Either

Convert Option to Either in Scala

假设我需要在 Scala 中将 Option[Int] 转换为 Either[String, Int]。我想这样做:

def foo(ox: Option[Int]): Either[String, Int] =
  ox.fold(Left("No number")) {x => Right(x)}

不幸的是,上面的代码无法编译,我需要显式添加类型 Either[String, Int]

ox.fold(Left("No number"): Either[String, Int]) { x => Right(x) }

是否可以通过这种方式将 Option 转换为 Either 而无需添加类型?
您建议如何将 Option 转换为 Either

不行,如果你这样做,你不能遗漏类型。

Left("No number") 的类型被推断为 Either[String, Nothing]。仅从 Left("No number") 编译器无法知道您希望 Either 的第二种类型为 Int,并且类型推断不会走得太远以至于编译器会查看整个方法并决定它应该是 Either[String, Int].

您可以通过多种不同的方式做到这一点。例如模式匹配:

def foo(ox: Option[Int]): Either[String, Int] = ox match {
  case Some(x) => Right(x)
  case None    => Left("No number")
}

或使用 if 表达式:

def foo(ox: Option[Int]): Either[String, Int] =
  if (ox.isDefined) Right(ox.get) else Left("No number")

Either.cond:

def foo(ox: Option[Int]): Either[String, Int] =
  Either.cond(ox.isDefined, ox.get, "No number")

我不确定您当时使用的是哪个版本的 Scala。目前,对于 Scala 2.12.6,您的代码没有编译问题,如下所示:

def foo(ox: Option[Int]): Either[String, Int] =
  ox.toRight("No number")

我想说的另一点是折叠(虽然这是我折叠任何有折叠方法的首选方法)经常需要类型参数的帮助。编译器可以通过两种方式对表达式进行类型检查,一种是推断类型参数,另一种是简单地找到它们的显式定义。

在您的示例中,如果您尝试像这样弃牌:

def foo(ox: Option[Int]): Either[String, Int] =
  ox.fold(Left("No number") : Either[String, Int])(x => Right(x))

您明确提供了有关第一个参数的类型信息,然后可以使用该信息推断 fold 的类型参数。您正在帮助类型推断机制。

另一方面,您可以简单地为 fold 显式提供类型参数,如下所示:

def foo(ox: Option[Int]): Either[String, Int] =
  ox.fold[Either[String, Int]](Left("No number"))(x => Right(x))

现在您的实际(值级)参数不会充斥着多余的类型信息,编译器查看时也不会进行类型推断,它可以立即判断出 fold 的类型参数是,因为它已明确提供。使用方括号明确指定类型参数。

还有一点,关于 x => Right(x),您实际上是在创建一个新的函数文字,它除了将 x 传递给 Right case apply 方法外什么都不做 class' s 伴生对象。您已经拥有合适形状的可用函数。它需要 x 和 returns 一个 Right(x)。这是 apply 方法。大家可以直接参考(pass)一下

def foo(ox: Option[Int]): Either[String, Int] =
  ox.fold[Either[String, Int]](Left("No number"))(Right.apply)

之所以需要类型注释,是因为类型推断在 Scala 2 中的工作方式 multiple parameter lists where

  • 一次考虑一个参数列表,并且
  • 第一个参数列表中累积的约束将应用于下一个参数列表。

考虑 Option#fold

的签名
def fold[B](ifEmpty: => B)(f: A => B): B

我们在其中看到类型参数 B 和两个参数列表。现在提供给第一个参数列表的参数类型是 Left[String,Nothing] 因为

scala> Left("No number")
val res0: scala.util.Left[String,Nothing] = Left(No number)

这意味着类型参数 B 被推断为 Left[String,Nothing],这反过来又将提供给第二个参数列表的参数的预期类型限制为 Left 的函数return类型

A => Left[String,Nothing]

但是我们提供了一个 Right return 类型的函数,因此它会出现类型不匹配的错误

Welcome to Scala 2.13.3 (OpenJDK 64-Bit Server VM, Java 1.8.0_252).
Type in expressions for evaluation. Or try :help.

scala> def foo(ox: Option[Int]): Either[String, Int] =
     |   ox.fold(Left("No number")) {x => Right(x)}
         ox.fold(Left("No number")) {x => Right(x)}
                                               ^
On line 2: error: type mismatch;
        found   : scala.util.Right[Nothing,Int]
        required: scala.util.Left[String,Nothing]

请注意,Scala 3 (Dotty) 带来了 improvements 类型推断,因此您的代码片段可以开箱即用,而无需提供显式类型注释

Starting dotty REPL...
scala> def foo(ox: Option[Int]): Either[String, Int] =
     |   ox.fold(Left("No number")) {x => Right(x)}
     |
def foo(ox: Option[Int]): Either[String, Int]