Search code examples
scalarecursioncombinators

Is there a combinator for this?


I have a list of strings

val allLines = List("James Bond", "secret agent", "", "Martin Odersky")

I then want "map" that to e.g. a List[Person] where case class Person(name: String, moreDetails: List[String] using several elements at once.

val people = allLines.someCombinatorFunction { lines => 
   val name = lines.head
   val (moreDetails, remainingLines) = lines.span(_ != "")
   val person = Person(name, moreDetails)

   (person, remainingLines)
}

this should give me:

List(Person("James Bond", List("secret agent")), Person("Martin Odersky", Nil))

Ie. I want to take a variable number lines, combine them to a Person, and "hand off" the remaining lines. List[String] => List[Person]. This is trivial with recursion:

def linesToPeople(lines: List[String]): List[Person] = { lines => 
   val name = lines.head
   val (moreDetails, remainingLines) = lines.span(_ != "")
   val person = Person(name, moreDetails)

   person :: linesToPeople(remainingLines)
}

... but!, recursion is expensive, unless you make it tail-recursive..:

def linesToPeople(lines: List[String], acc: List[Person] = Nil): List[Person] = { lines => 
   val name = lines.head
   val (moreDetails, remainingLines) = lines.span(_ != "")
   val person = Person(name, moreDetails)

   linesToPeople(remainingLines, person :: acc)
}

^ This is where it becomes a little too cumbersome imo. You also need to do a .reverse in the end to get the order right. A combinator would be nice here

So basicly, I have a list, I want to "consume" & combine a variable number of its elements, and return the remains. Is there a way to do this without resorting to recursion?


Solution

  • Scalaz has a function selectSplit for that:

    import scalaz._
    import Scalaz._
    
    def getPeople(lines: List[String]): List[Person] = 
      lines.selectSplit(_ != "").map(l => Person(l.head, l.tail))
    

    And then:

    scala> getPeople(List(
      "James Bond", "secret agent", "", 
      "Martin Odersky", "", 
      "Arnold Schwarzenegger", "Terminator", "governor"))
    res8: List[Person] = List(Person(James Bond,List(secret agent)), Person(Martin Odersky,List()), Person(Arnold Schwarzenegger,List(Terminator, governor)))