在Scala中将选项转换为Either

时间:2016-01-10 14:19:23

标签: scala type-conversion option either

假设我需要在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

3 个答案:

答案 0 :(得分:38)

不,如果你这样做,你就不能省去这种类型。

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")

答案 1 :(得分:5)

我不确定您当时使用的是哪个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显式提供type参数:

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方法外,什么也没做。您已经具有合适形状的功能。它需要x并返回Right(x)。这是apply方法。您可以直接引用(通过)。

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

答案 2 :(得分:1)

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

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

考虑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返回类型的函数

A => Left[String,Nothing]

但是我们提供的是Right返回类型的函数,因此它会因类型不匹配而出错

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]