0

My question: is it possible to simplify this nested expression with for-comprehension ?

run(dbAction).flatMap(insertedJobs => {
  Future.sequence(insertedJobs.map { job =>
    recordingBean.findStreamsForInterval(job.mediaSource, job.begin, job.end) map { stream =>
      if (stream.nonEmpty) {
        recordingBean.findRecordingLocationsForInterval(stream.head.stream, job.begin, job.end).map(recordingLocations =>
          recordingLocations.map(_.size.getOrElse(0L)).sum).flatMap { expectedSizeInBytes =>
          updateSize(job.id.get, expectedSizeInBytes)
        }
        job
      } else {
        job
      }
    }
  })
})
Slow Harry
  • 1,857
  • 3
  • 24
  • 42

1 Answers1

2

For comprehensions are powerful but they are not an almighty nesting-killer you think they are.

for-comprehension are useful till you are not mixing SeqLike monads with non-SeqLike monads. But once you mix them, you are back to nesting world. And it will be even worse than before because for-comprehensions hide away the details.

Lets take some examples,

val listOfListOfInt = List(List(1, 2, 3), List(2, 3, 4))

val listOfInt = for {
  loi <- listOfListOfInt
  i <- loi
} yield i + 4

// This will work just fine
// listOfInt: List[Int] = List(5, 6, 7, 6, 7, 8) 

Now lets mix List with Future,

val futureOfList = Future({ List(1, 2, 3) })

val iWillNotCompile = for {
  l <- futureOfList
  i <- list
} yield i + 4

// the above is equivalent to writing,
val iWillNotCompile = futureOfList.flatMap(l => l.map(i => i + 4))

// would have been following but does not make sense
// `Success(5, 6, 7)`

The above code will not compile and actually it should not compile. As Future is a non-SeqLike monad. I mean, if the above code worked it would have been a Success(5, 6, 7), which does not make sense.

Similarly, following code will not work

val listOfFuture = List(Future({ 1 }), Future({ 2 }), Future({ 3 }) )

val iWillNotCompile = for {
  f <- listOfFuture
  i <- f
} yield i + 4

// this is equivalent to
val iWillNotCompile = listOfFuture.flatMap(f => f.map(i => i + 4)

// should have been following and it makes sense
// List(Success(5), Success(6), Success(7))

This case is a lot different from the previous one, as it confirms to common sense but still it will not work. The reason is that SeqLike-Monads and Futures have very very different implementations of flatMap and flatten and map.

So... if are dealing with a mix of List, Future, Option, Try etc, stay away from for-comprehension. Just try to write your code in a more clever way.

sarveshseri
  • 13,738
  • 28
  • 47
  • To expand: Futures actually aren't Monads. They only have monad-like properties. This has a good discussion http://stackoverflow.com/questions/27454798/is-future-in-scala-a-monad – Scott Shipp Aug 18 '16 at 17:28
  • Yes. Mathematically Scala `Future`s can violate the monad definition because they allow side effects. But the whole pure-monad discussion belongs to pure functional programming where side-effects are treated as a mortal sin. So if a functional purist uses `Future`s he won't use side-effects and hence `Future`s won't violate monad laws. – sarveshseri Aug 18 '16 at 17:54
  • is it possible some how express Future{}.map(a => a.map(b => b)) end so on, using some kind of syntactic sugar ? – Slow Harry Aug 19 '16 at 08:43
  • 1
    @SlowHarry `Syntactic Sugar` is exactly what I am advising you to avoid. The problem with syntactic sugars is that these have implementations which are a mix of generic + special handling for some cases. And Scala syntactic sugars have a reputation of being unpredictable (have too many special cases). Remember that magical code is not the most easily understood code. – sarveshseri Aug 19 '16 at 08:50