0

我正在尝试使用也在 IO monad 中的 Map 来转换一些封装在cats.effect.IO 中的数据。我将 http4s 与 blaze 服务器一起使用,当我使用以下代码时,请求超时:

  def getScoresByUserId(userId: Int): IO[Response[IO]] = {
    implicit val formats = DefaultFormats + ShiftJsonSerializer() + RawShiftSerializer()
    implicit val shiftJsonReader = new Reader[ShiftJson] {
      def read(value: JValue): ShiftJson = value.extract[ShiftJson]
    }
    implicit val shiftJsonDec = jsonOf[IO, ShiftJson]

    // get the shifts
    var getDbShifts: IO[List[Shift]] = shiftModel.findByUserId(userId)

    // use the userRoleId to get the RoleId then get the tasks for this role
    val taskMap : IO[Map[String, Double]] = taskModel.findByUserId(userId).flatMap {
      case tskLst: List[Task] => IO(tskLst.map((task: Task) => (task.name -> task.standard)).toMap)
    }

    val traversed: IO[List[Shift]] = for {
      shifts <- getDbShifts
      traversed <- shifts.traverse((shift: Shift) => {
        val lstShiftJson: IO[List[ShiftJson]] = read[List[ShiftJson]](shift.roleTasks)
          .map((sj: ShiftJson) =>
            taskMap.flatMap((tm: Map[String, Double]) =>
              IO(ShiftJson(sj.name, sj.taskType, sj.label, sj.value.toString.toDouble / tm.get(sj.name).get)))
          ).sequence

        //TODO: this flatMap is bricking my request
        lstShiftJson.flatMap((sjLst: List[ShiftJson]) => {
          IO(Shift(shift.id, shift.shiftDate, shift.shiftStart, shift.shiftEnd,
            shift.lunchDuration, shift.shiftDuration, shift.breakOffProd, shift.systemDownOffProd,
            shift.meetingOffProd, shift.trainingOffProd, shift.projectOffProd, shift.miscOffProd,
            write[List[ShiftJson]](sjLst), shift.userRoleId, shift.isApproved, shift.score, shift.comments
          ))
        })
      })

    } yield traversed

  traversed.flatMap((sLst: List[Shift]) => Ok(write[List[Shift]](sLst)))
}

正如你可以看到 TODO 评论。我已将此方法缩小到 TODO 注释下方的平面图。如果我删除该 flatMap 并仅将“IO(shift)”返回给遍历的变量,则请求不会超时;但是,这对我没有多大帮助,因为我需要使用具有转换后的 json 的 lstShiftJson 变量。

我的直觉告诉我我正在以某种方式滥用 IO monad,但我不太确定如何。

感谢您花时间阅读本文!

4

1 回答 1

1

因此,在 Luis 评论的指导下,我将代码重构为以下内容。我不认为它是最佳的(即最后的 flatMap 似乎没有必要,但我不知道如何删除它。但它是我所拥有的最好的。

  def getScoresByUserId(userId: Int): IO[Response[IO]] = {
    implicit val formats = DefaultFormats + ShiftJsonSerializer() + RawShiftSerializer()
    implicit val shiftJsonReader = new Reader[ShiftJson] {
      def read(value: JValue): ShiftJson = value.extract[ShiftJson]
    }
    implicit val shiftJsonDec = jsonOf[IO, ShiftJson]

    // FOR EACH SHIFT
    // - read the shift.roleTasks into a ShiftJson object
    // - divide each task value by the task.standard where task.name = shiftJson.name
    // - write the list of shiftJson back to a string
    val traversed = for {
      taskMap <- taskModel.findByUserId(userId).map((tList: List[Task]) => tList.map((task: Task) => (task.name -> task.standard)).toMap)
      shifts <- shiftModel.findByUserId(userId)
      traversed <- shifts.traverse((shift: Shift) => {
        val lstShiftJson: List[ShiftJson] = read[List[ShiftJson]](shift.roleTasks)
          .map((sj: ShiftJson) => ShiftJson(sj.name, sj.taskType, sj.label, sj.value.toString.toDouble / taskMap.get(sj.name).get ))
        shift.roleTasks = write[List[ShiftJson]](lstShiftJson)
        IO(shift)
      })
    } yield traversed

    traversed.flatMap((t: List[Shift]) => Ok(write[List[Shift]](t)))
  }
  1. Luis 提到将我的 List[Shift] 映射到 Map[String, Double] 是一个纯操作,所以我们想使用 map 而不是 flatMap。
  2. 他提到我将来自数据库的每个操作都包装在 IO 中,这会导致大量的重新计算。(包括数据库事务)
  3. 为了解决这个问题,我将所有数据库操作移到我的 for 循环中,使用“<-”运算符对每个返回值进行 flatMap,允许使用的变量在 IO monads 中主持,从而防止之前经历的重新计算。

我确实认为必须有更好的方法来返回我的返回值。flatMapping“遍历”变量以返回 IO monad 内部似乎是不必要的重新计算,所以请任何人纠正我。

于 2021-01-03T18:24:37.550 回答