在 Scala 中将 Option 转换为 Either

问题描述 投票:0回答:4

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

scala type-conversion either scala-option
4个回答
49
投票

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

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

17
投票

我不确定您当时使用的是哪个版本的 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
方法之外什么也不做。您已经有了可用的适当形状的函数。它接受
x
并返回
Right(x)
。这就是
apply
方法。可以直接参考(通过)。

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

2
投票

类型注释之所以必要,是因为 Scala 2 中类型推断的工作方式:多个参数列表,其中

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

考虑

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) 为类型推断带来了改进,因此您的代码片段可以开箱即用,无需提供显式类型注释

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]

0
投票

最简单的方法是使用

Option.toRight
:

  val ox: Option[Int] = ...
  ox.toRight("No number")
© www.soinside.com 2019 - 2024. All rights reserved.