问题描述
在我的代码中,我经常需要通过对内部模型执行操作来处理列表。对于每个处理过的元素,都会返回模型,然后将“新”模型用于列表的下一个元素。
通常,我使用尾递归方法来实现:
def createCar(myModel: Model,record: Record[Any]): Either[CarError,Model] = {
record match {
case c: Car =>
// Do car stuff...
val newModel: Model = myModel.createCar(record)
Right(newModel)
case _ => Left(CarError())
}
}
@tailrec
def processCars(myModel: Model,records: List[Record[Any]]): Either[CarError,Model] =
records match {
case x :: xs =>
createCar(myModel,x) match {
case Right(m) => processCars(m,xs)
case e@Left(_) => e
}
case Nil => Right(myModel)
}
由于我不断重复这种模式,我正在寻找使其更简洁和更实用的方法(即 Scala 方式)。
我已经研究过 foldLeft
,但无法让它与 Either
一起使用:
recordsList.foldLeft(myModel) { (m,r) =>
// Do car stuff...
Right(m)
}
foldLeft
是合适的替代品吗?我怎样才能让它工作?
解决方法
根据我之前的评论,下面是如何unfold()
获得结果。 [注:Scala 2.13.x]
def processCars(myModel: Model,records: List[Record[_]]
): Either[CarError,Model] =
LazyList.unfold((myModel,records)) { case (mdl,recs) =>
recs.headOption.map{
createCar(mdl,_).fold(Left(_) -> (mdl,Nil),m => Right(m) -> (m,recs.tail))
}
}.last
这里的优点是:
-
提前终止 - 在返回第一个
records
或处理完所有记录后(以先到者为准)循环访问Left
停止。 -
内存效率 - 由于我们正在构建一个
LazyList
,并且没有任何东西占据结果列表的头部,因此除了last
之外的每个元素应该立即释放以进行垃圾收集。
您可以像这样使用 fold
来做到这一点:
def processCars(myModel: Model,records: List[Record[Any]]): Either[CarError,Model] = {
records.foldLeft[Either[CarError,Model]](Right(myModel))((m,r) => {
m.fold(Left.apply,{ model =>
createCar(model,r).fold(Left.apply,Right.apply)
})
})
}