【问题标题】:Scalaz functional composition with futureScalaz 功能组合与未来
【发布时间】:2016-07-15 01:46:26
【问题描述】:

我有 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。

【问题讨论】:

    标签: scala scalaz


    【解决方案1】:

    从 scalaz(或猫)组合返回一些 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(两次)的同时返回。

    我们可以通过在Kleisli(save) 上调用dimap(我们从Profunctor 得到的操作)来在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)
    

    对于猫来说,这看起来或多或少是一样的:

    • cats(目前)没有>>> 运算符,因此需要将其替换为andThen
    • dimap 被柯里化了,所以我们会写成 dimap(f)(g) 而不是 dimap(f, g)

    【讨论】:

    • 感谢 kleisli 的非常好的解释。我以前对它有点熟悉,现在对它有了更好的掌握。我真的想知道是否有一个操作员自动执行此操作,但看起来并不存在。顺便说一句,双增量是管道中多个操作的人为示例。我不会在我的项目中这样做。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多