scalafor-comprehension

Map over result of right-hand side in for comprehension before assigning to value


I love Scala's for comprehensions! I would love them even more if I could find a way to map over the right-hand side result before assigning it to a value. Say I have the following code:

case class Person(name: String)
def getPersonById(id: Long): Future[Option[Person]] = ???

case class Dog(color: String)
def getDogById(id: Long): Future[Dog] = ???

def addStringToFile(name: Seq[String]): Future[Unit] = ???
val id: Long = ???

for {
  person <- getPersonById(id)
  name = person.map(_.name)

  dog <- getDogById(id)
  color = dog.color

  _ <- addNamesToFile(Seq(name, color).flatten)
} yield name

I don't actually need person or dog as an intermediate value; the are only valuable to the extent that I can extract a property from them on the very next line. Ideally what I'd like to do would look something vaguely like this:

for {
  name <- getPersonById(id))>>.map(_.name)
  color <- getDogById(id))>>.color
  _ <- addNamesToFile(Seq(name, color).flatten)
} yield name

But obviously this isn't valid Scala. Outside of a for-comprenshion I would do:

val name = getPersonById(id).map(_.map(_.name))
val color = getDogById(id).map(_.color)

But the neat thing about the for-comprehension is that I don't need to explicitly perform the outer .map, and the "Future" part is hidden away.

Is it possible to map over the result of a right-hand side expression in a for-comprehension before assigning it to a value?


Solution

  • Ideally what I'd like to do would look something vaguely like this:

    for {
      name <- getPersonById(id))>>.map(_.name)
      color <- getDogById(id))>>.color
      _ <- addNamesToFile(Seq(name, color).flatten)
    } yield 
    

    Note that you can actually write exactly that by simply replacing your cryptic )>>. with map :)

      for {
         name <- getPersonBy(id).map(_.map(_.name))
         color <- getDogById(id).map(_.color)
         _ <- addNamesToFile(name.toSeq :+ color)
      } yield name
    

    The only difference between this and what you wrote is .map vs. >> If you like your syntax more for some reason, make an implicit:

    object FunnySyntax { 
        implicit class FS[T](val f: Future[T]) extends AnyVal {
           def >>[P] (m: T => P): Future[P] = f.map(m)
         }
    }
    

    Now, you can do

    name <- getPersonById(id) >> { _.map(_.name) }
    color <- getDogById(id) >> { _.color }
    

    This is almost exactly what you had - just a couple of braces and an underscore added ... 🤷