scalatail-recursionfoldleft

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


In my code, I very often need to process a list by performing operations on an internal model. For each processed element, the model is returned and then a 'new' model is used for the next element of the list.

Usually, I implement this by using a tail recursive method:

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)
  }

Since I keep repeating this kind of pattern, I am searching for ways to make it more concise and more functional (i.e., the Scala way). I have looked into foldLeft, but cannot get it to work with Either:

recordsList.foldLeft(myModel) { (m, r) =>
      // Do car stuff...           
      Right(m)
}

Is foldLeft a proper replacement? How can I get it to work?


Solution

  • Following up on my earlier comment, here's how to unfold() to get your result. [Note: 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
    

    The advantage here is:

    1. early termination - Iterating through the records stops after the 1st Left is returned or after all the records have been processed, whichever comes first.
    2. memory efficient - Since we're building a LazyList, and nothing is holding on to the head of the resulting list, every element except the last should be immediately released for garbage collection.