2016-07-14 8 views
0

У меня есть 2 элемента данных itemDto и itemEntity, и они выглядят так.Функциональная композиция Scalaz с будущим

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) 
    } 
} 

функцию, которая получает приращение идентификатора хозяйствующего субъекта

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) 
} 

После того, как метод сохранения называется в цепи, я должен разорвать в другую функцию. Мой вопрос: есть ли способ извлечь выгоду из будущего и вернуть ее, чтобы мой конвейер выглядел примерно так?

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

Где ?!? является гипотетическим оператором.

Это может быть и библиотека scala. Это не должно быть от scalaz.

ответ

1

Наиболее полезная концепция от сказаса (или кошек) для составления функций, возвращающих F[_] (например, Future), является стрелкой Keisli.

Kleisli позволяет легко создавать несколько функций A => F[B]. Здесь у нас есть только одна функция ItemEntity => Future[ItemEntity].

Мы можем создать Kleisli[Future, ItemDto, ItemEntity] из первых 3-х функций, а затем карту над ним, используя две последние:

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, остальное преобразование из Dto в Entity и обратно при увеличении 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 is curried, поэтому мы будем писать dimap(f)(g) вместо dimap(f, g).
+0

Спасибо за действительно хорошее объяснение kleisli. Раньше я был знаком с ним и теперь лучше разбираюсь в этом. Мне было действительно интересно, есть ли оператор, который сделал это автоматически, и он не выглядит так, как есть. Кстати, двойной прирост был надуманным примером нескольких операций в конвейере. Я не делаю этого в своих проектах. – decapo