未来的Scalaz功能组合

时间:2016-07-14 13:38:04

标签: scala scalaz

我有2个数据结构itemDto和itemEntity,它们看起来像这样。

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

我想设置一个看起来像这样的管道

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

我也有一些映射器功能

object ItemMapper {

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

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

增加实体ID

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

并且有一个存储库来保存实体

object ItemRepository {

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

最后,我的方法结合了所有这些功能来做这样的事情

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

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

在链中调用save方法后,我必须进入另一个函数。我的问题是,有没有办法将价值提升到未来并重新投入使用,以便我的管道看起来像这样?

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

?!?是假设的运营商。

它也可以来自scala库。它不需要来自scalaz。

1 个答案:

答案 0 :(得分:1)

来自scalaz(或者cat)的最有用的概念是组合返回一些F[_]的函数(如Future这里)Keisli箭头。

Kleisli可以轻松编写多个A => F[B]函数。这里我们实际上只有一个ItemEntity => Future[ItemEntity]函数。

我们可以从前3个函数创建Kleisli[Future, ItemDto, ItemEntity],然后使用最后两个函数映射它:

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)

您要执行的实际操作是save,其余的是从DtoEntity的转换,然后在递增ID(两次)时返回。

我们可以通过在save上调用dimap(我们从Profunctor获得的操作)来Kleisli(save)之前和之后进行转换和增量:

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

或者两次使用dimap

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

所有这些都给出了相同的结果:

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)

对于猫,这看起来或多或少相同:

  • 猫没有(目前)拥有>>>运算符,因此需要将其替换为andThen
  • dimap是curry,所以我们会写dimap(f)(g)而不是dimap(f, g)