Эффективный способ создания абстрактной коллекции с кошками
У меня есть код, который использует Monix Observable
для потоковой обработки файла. Чтобы проверить этот код, я хотел бы выполнить операции, которые я выполняю на Observable
быть независимым от типа, поэтому я также могу выполнять их на любой другой структуре данных, как List
, Вот почему я написал следующий код для абстрагирования базовой структуры данных:
def permutations[F[_] : Applicative : FunctorFilter : SemigroupK](chars: F[Char]): F[F[Char]] = {
Range.inclusive('a', 'z').map(_.toChar)
.map { c ⇒
FunctorFilter[F].filter(chars)(Character.toLowerCase _ andThen (_ != c))
}
.map(Applicative[F].pure)
.reduceLeft(SemigroupK[F].combineK)
}
Меня беспокоит то, что этот код создает много промежуточных структур данных. Есть ли класс типов, который я мог бы использовать, чтобы сделать этот процесс более эффективным? То, что поднимает одну структуру данных в другую без лишних затрат, например LiftIO
а для коллекций предметов?
1 ответ
Не похоже, что кошкам есть что предложить для этого. И monix не лучше, он реализует лишь несколько классов типов от кошек.
Итак, мое лучшее предположение было бы определение таких классов типов самостоятельно:
import monix.execution.Scheduler.Implicits.global
import cats._
import cats.implicits._
import monix.reactive._
object Test {
def main(args: Array[String]): Unit = {
println(permutations(List('a', 'b', 'c')))
permutations(Observable('a', 'b', 'c')).foreach{c =>
print("Observable(")
c.foreach(c1 => print(c1 + " "))
print(") ")
}
}
def permutations[F[_] : Applicative](chars: F[Char])(implicit seq: Sequence[F], fil: Filter[F]): F[F[Char]] = {
val abc = seq.fromIterable(
Range.inclusive('a', 'z').map(_.toChar)
)
abc.map(c => fil.filter(chars)(_ != c))
}
trait Sequence[F[_]] {
def fromIterable[A](f: Iterable[A]): F[A]
}
implicit val listSequence: Sequence[List] = new Sequence[List] {
def fromIterable[A](f: Iterable[A]): List[A] = f.toList
}
implicit val observableSequence: Sequence[Observable] = new Sequence[Observable] {
def fromIterable[A](f: Iterable[A]): Observable[A] = Observable.fromIterable(f)
}
trait Filter[F[_]] {
def filter[A](fa: F[A])(f: A => Boolean): F[A]
}
implicit val observableFilterFunctor: Filter[Observable] = new Filter[Observable] {
def filter[A](fa: Observable[A])(f: A => Boolean): Observable[A] =
fa.filter(f)
}
implicit val listFilterFunctor: Filter[List] = new Filter[List] {
def filter[A](fa: List[A])(f: A => Boolean): List[A] =
fa.filter(f)
}
}
Результат:
List(List(b, c), List(a, c), List(a, b), List(a, b, c), List(a, b, c), List(a, b, c), List(a, b, c), List(a, b, c), List(a, b, c), List(a, b, c), List(a, b, c), List(a, b, c), List(a, b, c), List(a, b, c), List(a, b, c), List(a, b, c), List(a, b, c), List(a, b, c), List(a, b, c), List(a, b, c), List(a, b, c), List(a, b, c), List(a, b, c), List(a, b, c), List(a, b, c), List(a, b, c))
Observable(b c ) Observable(a c ) Observable(a b ) Observable(a b c ) Observable(a b c ) Observable(a b c ) Observable(a b c ) Observable(a b c ) Observable(a b c ) Observable(a b c ) Observable(a b c ) Observable(a b c ) Observable(a b c ) Observable(a b c ) Observable(a b c ) Observable(a b c ) Observable(a b c ) Observable(a b c ) Observable(a b c ) Observable(a b c ) Observable(a b c ) Observable(a b c ) Observable(a b c ) Observable(a b c ) Observable(a b c ) Observable(a b c )
К сожалению, я не мог заставить это работать на скафиддле или скасти, потому что оба не предлагают правильных кошек (1.5.0
) и моникс (3.0.0-M3
) версии.
Я все еще надеюсь, что это поможет.
Хотя создание повторно используемых функций полезно, вы можете легко протестировать Observable
без этого.
Я бы рекомендовал разделить логическую обработку и побочного потребителя
object StreamProcessing {
def processItems(obs: Observable[Input]): Observable[Result] = ???
}
В продукте вы бы сделали
val eventsStream: Observable[Input] = ???
val eventsConsumer: Consumer[Input, Output] = ???
StreamProcessing(myEventsStream).consumeWith(eventsConsumer)
Затем в своем тесте вы можете просто имитировать свои тестовые данные, утверждая результат списка. Кроме того, тестируя Observable, вы получаете возможность контролировать время с помощьюTestScheduler
, что упрощает тестирование.
implicit val sc = TestScheduler()
val testData: List[Input] = ???
val expected: List[Output] = ???
val res = StreamProcessing(Observable.fromIterable(testData))
.toListL
.runToFuture
sc.tick()
assert(res.value, Some(Success(expected))