如何将尾递归方法转换为更像 Scala 的函数?

How to convert tail recursive method to more Scala-like function?

在我的代码中,我经常需要通过对内部模型执行操作来处理列表。对于每个处理过的元素,都会返回模型,然后 'new' 模型用于列表的下一个元素。

一般我都是用尾递归的方式实现的:

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 是合适的替代品吗?我怎样才能让它工作?

您可以像这样使用 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)
    })
  })
}

根据我之前的评论,以下是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

这里的好处是:

  1. 提前终止 - 在第一个 Left 返回后或在处理完所有记录后,迭代 records 停止,以先到者为准。
  2. 高效内存 - 由于我们正在构建一个 LazyList,并且结果列表的头部没有任何内容,除了 [=15] =] 应该立即释放以进行垃圾收集。