decapo
decapo

Reputation: 839

Scalaz functional composition with future

I have 2 data structures itemDto and itemEntity and they look like this.

trait ItemDto{
  def id: Long
  def name: String
}

trait ItemEntity {
  def id: Long
  def name: String
}

case class Entity(id: Long, name: String) extends ItemEntity

case class Dto(id: Long, name: String) extends ItemDto

I'd like to set up a pipe line that looks like this

ItemDto => ItemEntity => ItemEntity => Future[ItemEntity] => Future[ItemEntity] => Future[ItemDto]

I have some mapper functions as well

object ItemMapper {

  def mapDtoToEntity(dto: ItemDto): ItemEntity = {
    Entity(dto.id, dto.name)
  }

  def mapEntityToDto(entity: ItemEntity): ItemDto = {
    Dto(entity.id, entity.name)
  }
}

a function that increments the entity's id

object ItemEntity{
  def incrementId(entity: ItemEntity) = Entity(entity.id + 1, entity.name)
}

and there is a repository to save the entity

object ItemRepository {

    def save(entity: ItemEntity): Future[ItemEntity] = {
        Future{entity}
    }
}

finally my method that combines all these functions to do something looks like this

import ItemMapper._
import ItemRepository.save
import ItemEntity.incrementId

def addItem(dto: ItemDto) = {
    (mapDtoToEntity _ >>> incrementId >>> save >>> {_ map (incrementId _ >>> mapEntityToDto) })(dto)
} 

After the save method is called in the chain, I have to break into another function. My question, is there a way to lift the value out of the future and put it back in so that my pipeline looks something like this?

(mapDtoToEntity _ >>> incrementId >>> save ?!? incrementId ?!? mapEntityToDto)(dto)

Where ?!? is the hypothetical operator.

It could be from the scala library as well. It doesn't need to be from scalaz.

Upvotes: 0

Views: 116

Answers (1)

Peter Neyens
Peter Neyens

Reputation: 9820

The most useful concept from scalaz (or cats) to compose functions which return some F[_] (like Future here) is the Keisli arrow.

Kleisli makes it easy to compose multiple A => F[B] functions. Here we actually only have one ItemEntity => Future[ItemEntity] function.

We can create a Kleisli[Future, ItemDto, ItemEntity] from the first 3 functions and then map over it using the two last :

import scalaz._, Scalaz._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

val composition1: Kleisli[Future, ItemDto, ItemDto] =
  Kleisli(mapDtoToEntity _ >>> incrementId >>> save)
    .map(incrementId _ >>> mapEntityToDto)

The actual operation you want to do is save, the rest is a transformation from Dto to Entity and back while incrementing the id (twice).

We can do the transformation and the incrementation before and after save by calling dimap (an operation we get from Profunctor) on Kleisli(save) :

val composition2: Kleisli[Future, ItemDto, ItemDto] =
  Kleisli(save).dimap(
    mapDtoToEntity _ >>> incrementId,
    incrementId _ >>> mapEntityToDto)

Or by using dimap twice :

val composition3: Kleisli[Future, ItemDto, ItemDto] =
  Kleisli(save).dimap(incrementId, incrementId)
               .dimap(mapDtoToEntity, mapEntityToDto)

Which all give the same result :

import scala.concurrent.Await
import scala.concurrent.duration._

val futureDto: Future[ItemDto] = composition3(Dto(1L, "test"))
Await.result(futureDto, 1.second)
// ItemDto = Dto(3,test)

With cats this would look more or less the same :

  • cats doesn't (currently) have the >>> operator, so it would need to be replaced by andThen.
  • dimap is curried, so we would write dimap(f)(g) instead of dimap(f, g).

Upvotes: 1

Related Questions