理解内的Either值是否与模式匹配?

时间:2019-01-02 23:34:04

标签: scala pattern-matching for-comprehension either

我有这样的理解力:

for {
      (value1: String, value2: String, value3: String) <- getConfigs(args)
      // more stuff using those values
}

getConfigs返回一个Either[Throwable, (Seq[String], String, String)],当我尝试编译时出现此错误:

value withFilter is not a member of Either[Throwable,(Seq[String], String, String)]

如何在理解中使用此方法(返回Either)?

4 个答案:

答案 0 :(得分:3)

赞:

for {
   tuple <- getConfigs()
} println(tuple)

开个玩笑,我认为这是一个有趣的问题,但它的名字被误命名了。 问题(参见上文)不是因为无法理解,而是无法在Either内进行for理解内的模式匹配。

documentation how for comprehensions are translated,但它们不能涵盖所有情况。据我所知,这里并没有涵盖。因此,我在“ Scala编程”(第二版)实例中进行了查找(因为这是我站在枯树旁的那个版本)。

第23.4节-for-expressions的翻译

有一个子章节“生成器中的翻译模式”,这就是如上所述的问题。它列出了两种情况:

案例一:元组

正是我们的情况:

for ((x1, …, xn) <- expr1) yield expr2

应翻译为expr1.map { case (x1, …, xn) => expr2)。 当您选择代码并执行“理解的Desugar”操作时,IntelliJ正是这样做的。好极了! …但这使它在我眼中变得更加奇怪,因为经过解密的代码实际上运行没问题。

因此,这种情况是(imho)匹配的情况,但不是正在发生的情况。至少不是我们观察到的。嗯?!

情况二:任意模式

for (pat <- expr1) yield expr2

翻译为

expr1 withFilter {
  case pat => true
  case _ => false
} map {
  case pat => expr2
}

现在有一个withFilter方法! 这种情况完全说明了错误消息,以及为什么无法在Either中进行模式匹配。

本章最终指的是the scala language specification(尽管是较旧的),这是我现在要停止的地方。

抱歉,我不能完全回答这个问题,但希望我能在这里暗示问题根源是什么。

直觉

那么Either为何有问题,却没有提出withFilterTry的{​​{1}}方法呢? 由于Option从“容器”(可能是“全部”)中删除了元素,因此我们需要一些表示“空容器”的东西。

这对于filter很容易,而显然是Option。也很容易例如None。对于List来说并不是那么容易,因为有多个Try,每个人都可以容纳一个特定的异常。但是,这个地方有多个失败之处:

  • Failure
  • NoSuchElementException

,这就是为什么UnsupportedOperationException运行,而Try[X]没有运行的原因。 这几乎是同一件事,但并非完全相同。 Either[Throwable, X]知道TryLeft,库作者可以从中受益。

然而,在Throwable(现在是右偏)上,“空”情况是Either情况;这是通用的。因此,用户可以确定它是哪种类型,因此库作者无法为每种可能的左值选择通用实例。

我认为这就是为什么Left不提供现成的Either以及表达失败的原因。

顺便说一句。

withFilter

案例是可行的,因为它在调用堆栈上抛出了expr1.map { case (x1, …, xn) => expr2) } 并引起了问题的恐慌,而问题本身可能是一个更大的问题。

哦,对于那些足够勇敢的人:到目前为止,我还没有使用过“ Monad”一词,因为Scala没有用于它的数据结构,但是理解就可以了。但是也许引用不会对您造成伤害:Additive Monads拥有这个“零”值,这正是MatchError在这里遗漏的东西,也是我试图在“直觉”部分赋予一些含义的东西。

答案 1 :(得分:2)

我想您希望仅在值为Right时才运行循环。如果是Left,则不应运行。这可以很容易实现:

for {
  (value1, value2, value3) <- getConfigs(args).right.toOption
  // more stuff using those values
}

边注::我不知道您的确切用例是什么,但是scala.util.Try更适合您遇到结果或失败(异常)的情况。
只需写Try { /*some code that may throw an exception*/ },您将拥有Success(/*the result*/)Failure(/*the caught exception*/)
如果您的getConfigs方法返回的是Try而不是Either,那么您的上述操作无需任何更改即可工作。

答案 2 :(得分:1)

我认为您可能会感到惊讶的部分是,Scala编译器会发出此错误,因为您在原位解构了元组。令人惊讶的是,这迫使编译器检查withFilter方法,因为它看起来像编译器那样隐式检查容器内值的类型,并使用withFilter实现对值的检查。如果您将代码编写为

  for {
    tmp <- getConfigs(args)
    (value1: Seq[String], value2: String, value3: String) = tmp
    // more stuff using those values
  } 

它应该编译没有错误。

答案 3 :(得分:1)

您可以使用Oleg的better-monadic-for编译器插件来完成此操作:

build.sbt

addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.2.4")

然后:

object Test {
  def getConfigs: Either[Throwable, (String, String, String)] = Right(("a", "b", "c"))

  def main(args: Array[String]): Unit = {
    val res = for {
      (fst, snd, third) <- getConfigs
    } yield fst

    res.foreach(println)
  }
}

收益:

a

之所以有用,是因为该插件在删除关键字时删除了不必要的withFilterunchecked并使用了.map调用。因此,我们得到:

val res: Either[Throwable, String] = 
  getConfigs
    .map[String](((x$1: (String, String, String)) => x$1 match {
      case (_1: String, _2: String, _3: String)
    (String, String, String)((fst @ _), (snd @ _), (third @ _)) => fst
}));