问题描述
我正在尝试使用同样在 IO monad 中的 Map 来转换一些封装在 cat.effect.IO 中的数据。我在 blaze 服务器上使用 http4s,当我使用以下代码时,请求超时:
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)”返回给遍历的变量,则请求不会超时;但是,这对我没有多大帮助,因为我需要使用 lstShiftJson 变量,它具有我转换后的 json。
我的直觉告诉我我正在以某种方式滥用 IO monad,但我不太确定如何。
感谢您花时间阅读本文!
解决方法
因此,在 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)))
}
- Luis 提到将我的 List[Shift] 映射到 Map[String,Double] 是一个纯粹的操作,所以我们想使用 map 而不是 flatMap。
- 他提到我将来自数据库的每个操作都包装在 IO 中,这会导致大量的重新计算。 (包括数据库事务)
- 为了解决这个问题,我将所有数据库操作移到了 for 循环中,使用“
我确实认为必须有更好的方法来返回我的返回值。平面映射“遍历”变量以返回 IO monad 似乎是不必要的重新计算,所以请任何人纠正我。