【问题标题】:Inspection error in scala method / play framework / restscala方法/播放框架/休息中的检查错误
【发布时间】:2018-01-02 10:49:16
【问题描述】:

我还在学习 scala,所以这可能是一个很容易回答的问题,但是我几乎一天都被困在一遍又一遍地编写一个方法上,无法编译这段代码。

我正在使用 Play 框架和响应式 mongo 模板来了解 Scala 和 Play 的工作原理。 我有一个控制器,它有几个方法,一个 REST 服务的端点。

问题在于以下方法,它接受 json 对象列表并使用 mongo 反应式驱动程序更新这些对象。该类有一个成员citiesFuture,其类型为Future[JSONCollection]。 我添加这个方法的原始类代码可以在这里找到上下文:CityController on github

def updateAll() = Action.async(parse.json) { request =>
  Json.fromJson[List[City]](request.body) match {
    case JsSuccess(givenCities, _) =>
      citiesFuture onComplete[Future[Result]] { cities =>
        val updateFutures: List[Future[UpdateWriteResult]] = for {
          city <- givenCities
        } yield cities.get.update(City.getUniqueQuery(city), Json.obj("$set" -> city))

        val promise: Promise[Result] = Promise[Result] {
          Future.sequence(updateFutures) onComplete[Result] {
            case s@Success(_) =>
              var count = 0
              for {
                updateWriteResult <- s.value
              } yield count += updateWriteResult.n
              promise success Ok(s"Updated $count cities")
            case Failure(_) =>
              promise success InternalServerError("Error updating cities")
          }
        }
        promise.future
      }
    case JsError(errors) =>
      Future.successful(BadRequest("Could not build a city from the json provided. " + Errors.show(errors)))
  }
}

我已经通过大量的试验和错误成功地走到了这一步,但我开始了解 scala 和 Futures 的一些机制是如何工作的,我想 :) 我想我已经接近了,但我的 IDE仍然在promise.future 行上方的单个右大括号处给我一个检查错误。

错误内容为:Unit 类型的表达式不符合预期的Nothing 类型。 我检查了 Promise 和 onComplete 代码块的预期返回值,但我不认为他们期望 Nothing 作为返回类型。

有人可以向我解释我缺少什么,而且,我相信这可以做得更好,所以如果你有任何可以学习的技巧,请告诉我!

【问题讨论】:

  • 为什么在已经有Future 的地方使用Promise.future?似乎没有意义。
  • 因为 Action 需要 Future[Result],所以我认为这是构建一个新的 Future 并在所有更新完成后完成的唯一方法。

标签: scala playframework


【解决方案1】:

您有点走在正确的轨道上,但正如@cchantep 所说,一旦您在Future-land 运营,需要使用Promise.future 创建您自己的将是非常不寻常的。

此外,看到onComplete 被使用实际上是很不寻常的——惯用的Scala 通常更喜欢mapping 的“更高级别”抽象而不是Futures。我将尝试演示如何在 Play 控制器中编写您的函数:

首先,“端点”只处理一件事 - 与外部世界的接口 - 即 JSON 解析部分。如果一切正常,它会调用一个私有方法 (performUpdateAll) 来实际完成工作:

def updateAll() = Action.async(parse.json) { request =>
  Json.fromJson[List[City]](request.body) match {
    case JsSuccess(givenCities, _) =>
      performUpdateAll(givenCities)
    case JsError(errors) =>
      Future.successful(BadRequest("Could not build a city from the json provided. "))
   }
}

接下来,我们有执行多个城市更新的私有函数。同样,为了遵守Single Responsibility Principle(在功能意义上——一个函数应该做一件事),我提取了updateCity,它知道如何更新恰好一个城市并返回一个Future[UpdateWriteResult]。这样做的一个很好的副作用是代码重用;你可能会发现你可以在其他地方使用这样的功能。

private def performUpdateAll(givenCities:List[City]):Future[Result] = {

  val updateFutures = givenCities.map { city =>
    updateCity(city)
  }

  Future.sequence(updateFutures).map { listOfResults =>
    if (listOfResults.forall(_.ok)) {
      val count = listOfResults.map(_.n).sum
      Ok(s"Updated $count cities")          
    } else {
      InternalServerError("Error updating cities")
    }
  }
}

据我所知,这将与您希望的工作方式完全相同。但是通过使用Future.map 而不是其较低级别的对应物Future.onComplete 并匹配SuccessFailure,您将获得更简洁的代码,其中(在我看来)更容易看到意图 em> 因为它周围的样板更少。

我们仍然检查每个更新是否有效,如下:

if (listOfResults.forall(_.ok)) 

我认为这读起来很不错 - 所有结果都必须没问题!

我整理的另一个小技巧是将使用可变变量的“计数”逻辑替换为单行:

var count = 0
for {
  updateWriteResult <- s.value
} yield count += updateWriteResult.n 

变成:

val count = listOfResults.map(_.n).sum

即将结果列表转换为整数列表(UpdateWriteResult 中的 n),然后使用列表中提供的内置 sum 函数来完成剩下的工作。

【讨论】:

  • 感谢您的广泛回答!你解释它的方式,解决方案更有意义:) 我正在努力让它工作,我已经添加了 updateCity 函数,所以它至少可以编译,现在我在解析 json 时遇到了问题,但我'确定一旦解决,这将起作用,再次感谢。编辑:json问题是我使用了错误的位置:),这就像一个魅力!
猜你喜欢
  • 1970-01-01
  • 2014-12-30
  • 1970-01-01
  • 2014-01-28
  • 2015-08-03
  • 2013-09-22
  • 2015-06-09
  • 2013-07-11
  • 2016-08-03
相关资源
最近更新 更多