用猫创建抽象收藏的有效方法

时间:2018-12-05 09:50:10

标签: scala scala-cats monix

我有一些使用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,但用于项目集合?

2 个答案:

答案 0 :(得分:1)

看起来猫没有为此提供任何东西。 monix并不更好,它仅实现了cats的少数类型类。

因此,我最好的猜测是自己定义此类类型类:

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 ) 

可悲的是,我无法在scalafiddle或scastie上使用它,因为它们都没有提供正确的cat(1.5.0)和monix(3.0.0-M3)版本。

我仍然希望这会有所帮助。

答案 1 :(得分:0)

尽管创建可重用的功能很有用,但是您可以轻松测试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))