Nocebo
Nocebo

Reputation: 2017

Scala adding elements to seq and handling futures, maps, and async behavior

I'm still a newbie in scala and don't quite yet understand the concept of Futures/Maps/Flatmaps/Seq and how to use them properly.

This is what I want to do (pseudo code):

 def getContentComponents: Action[AnyContent] = Action.async {
        contentComponentDTO.list().map( //Future[Seq[ContentComponentModel]] Get all contentComponents
          contentComponents => contentComponents.map( //Iterate over [Seq[ContentComponentModel]
            contentComponent => contentComponent.typeOf match { //Match the type of the contentComponent
              case 1 => contentComponent.pictures :+ contentComponentDTO.getContentComponentPicture(contentComponent.id.get) //Future[Option[ContentComponentPictureModel]] add to _.pictures seq
              case 2 => contentComponent.videos :+ contentComponentDTO.getContentComponentVideo(contentComponent.id.get) //Future[Option[ContentComponentVideoModel]] add to _.videos seq
            }
          )
            Ok(Json.toJson(contentComponents)) //Return all the contentComponents in the end
        )
    }

I want to add a Future[Option[Foo]] to contentComponent.pictures: Option[Seq[Foo]] like so:

case 2 => contentComponent.pictures :+ contentComponentDTO.getContentComponentPicture(contentComponent.id.get) //contentComponent.pictures is Option[Seq[Foo]]

and return the whole contentComponent back to the front-end via json in the end.

I know this might be far away from the actual code in the end, but I hope you got the idea. Thanks!

Upvotes: 1

Views: 301

Answers (2)

Mario Galic
Mario Galic

Reputation: 48420

My answer will attempt to help with some of the conceptual sub-questions which form parts of your overall larger question.

flatMap and for-yield

One of the points of flatMap is to help with the problem of the Pyramid of Doom. This happens when you have structures nested within structures nested within structures ...

doA().map { resultOfA =>
  doB(resultOfA).map { resolutOfB =>
    doC(resultOfB).map { resultOfC =>
      ...
    }
  }
}

If you use for-yield you get flatMap out of the box and it allows you to

flatten the pyramid

so that your code looks more like a linear structure

for {

  resultOfA <- doA
  resultOfB <- doB(resultOfA)
  resultOfC <- doC(resultOfB)
...
} yield {...}

There is a rule of thumb in software engineering that deeply nested structures are harder to debug and reason about, so we strive to minimise the nesting. You will hit this issue especially when dealing with Futures.

Mapping over Future vs. mapping over sequence

Mapping is usually first thought in terms of iteration over a sequence, which might lead to understanding of mapping over a Future in terms of iterating over a sequence of one. My advice would be not to use the iteration concept when trying to understand mapping over Futures, Options etc. In these cases it might be better to think of mapping as a process of destructing the structure so that you get at the element inside the structure. One could visualise mapping as

breaking the shell of a walnut so you get at the delicious kernel inside and then rebuilding the shell.

Futures and monads

As you try to learn more about Futures and when you begin to deal with types like Future[Option[SomeType]] you will inevitably stumble upon documentation about monads and its cryptic terminology might scare you away. If this happens, it might help to think of monads (of which Future is a particular instance) as simply

something you can stick into a for-yield so that you can get at the delicious walnut kernels whilst avoiding the pyramid of doom.

Upvotes: 1

Frederic A.
Frederic A.

Reputation: 3514

I'll ignore your code and focus on what is short and makes sense:

I want to add a Future[Option[Foo]] to contentComponent.pictures: Option[Seq[Foo]] like so:

Let's do this, focusing on code readability:

// what you already have
val someFuture: Future[Option[Foo]] = ???
val pics: Option[Seq[Foo]] = contentComponent.pictures
// what I'm adding
val result: Future[Option[Seq[Foo]]] = someFuture.map {
  case None => pics
  case Some(newElement) => 
    pics match {
      case None => Some(Seq(newElement)) // not sure what you want here if pics is empty...
      case Some(picsSequence) => Some(picsSequence :+ newElement)
    }
}

And to show an example of flatMap let's say you need the result of result future in another future, just do:

val otherFuture: Future[Any] = ???
val everything: Future[Option[Seq[Foo]]] = otherFuture.flatmap { otherResult =>
  // do something with otherResult i.e., the code above could be pasted in here...
  result
}

Upvotes: 1

Related Questions