【问题标题】:How to implement Lazy Chain Pattern in Scala如何在 Scala 中实现惰性链模式
【发布时间】:2019-07-08 02:20:30
【问题描述】:

我正在尝试定义一组“LazyChains”,用于在未来处理传入的消息。我希望 LazyChains 的 API 与 Scala 集合 API(即:Seq、Stream 等)无法区分。这将允许我在消息到达之前提前声明性地定义过滤/转换/操作。这可能是一个众所周知的模式,我不知道它的名字,所以这让我很难找到任何结果。

这是我想要完成的一个例子:

val chainA = LazyChain()
 .filter(_ > 1)
 .map(x => x * 2)
 .foreach(println _)

val chainB = LazyChain()
 .filter(_ > 5)
 .flatMap(x => Seq(x, x))
 .foreach(println _)

 chainA.apply(2)  // should print "4"
 chainA.apply(1)  // should print nothing
 chainB.apply(10) // should print "10" - twice

Scala 集合 API 中是否已经存在这种模式?如果没有,我该如何实现这个类LazyChain

这是我目前的尝试。我似乎无法弄清楚如何让这些类型工作:

case class LazyChain[I, O](val filter : Option[I => Boolean],
                      val transform : I => O,
                      val action : Option[O => Unit]) {

  def filter(otherFilter : I => Boolean): LazyChain[I, O]  = {
      val newFilter = Some({ x : I => {
        filter.map(_.apply(x)).getOrElse(true) && otherFilter.apply(x)
      }})
      copy(filter = newFilter)
  }

  def map[X](otherTransform : O => X) : LazyChain[I, X] = {
    new LazyChain[I, X](
      filter = filter,
      transform = (x: I) => {
        otherTransform.apply(transform.apply(x))
      },
      /*
        type mismatch;
        [error]  found   : Option[O => Unit]
        [error]  required: Option[X => Unit]
      */
      action = action 
    )
  }

  def flatMap[X](otherTransform : O => Seq[X]) : LazyChain[I, X] = {
    new LazyChain[I, X](
      filter = filter,
      transform = (x: I) => {
        /**
           type mismatch;
             [error]  found   : Seq[X]
             [error]  required: X
        */
        otherTransform.apply(transform.apply(x))
      }
    )
  }

  def foreach(newAction : O => Unit) = {
    copy(action = Some(newAction))
  }

  def apply(element : I) = {
    if (filter.map(_.apply(element)).getOrElse(true)) {
      val postTransform = transform.apply(element)
      action.foreach(_.apply(postTransform))
    }
  }
}
object LazyChain {
  def apply[X]() : LazyChain[X, X] = {
    new LazyChain(filter = None, transform = x => x, action = None)
  }
}

【问题讨论】:

  • Scala 有Views 可以转换惰性集合中的任何集合。也许那里有一些有用的提示
  • akka-streams 将允许您执行此操作。但这对你来说可能太“重”了
  • Iteratees 让你很自然地做这样的事情:你可以建立转换(enumeratees)并将它们插入源(enumerators)和接收器(iteratees),而这些都只是值。
  • (完全披露:我维护an iteratee library for Scala。)
  • 感谢这些建议。我会看看这三个。我主要对我可以自己实现的轻量级解决方案感兴趣(也是我自己的知识)。我相信我会通过查看这些库的实现来学习。

标签: scala scala-collections


【解决方案1】:

你想要的只是用一些花哨的方法包装一个函数I => List[O]。您可以编写 implicit class 将这些方法添加到此类型,但 Kleisli 通过各种猫类型类(主要是 FilterFunctor)免费完成大部分工作。

  import cats.implicits._
  import cats.data.Kleisli

  type LazyChain[I, O] = Kleisli[List, I, O]
  def lazyChain[A]: LazyChain[A, A] = Kleisli[List, A, A](a => List(a))

  val chainA = lazyChain[Int]
    .filter(_ > 1)
    .map(x => x * 2)
    .map(println)

  val chainB = lazyChain[Int]
    .filter(_ > 5)
    .flatMapF(x => List(x, x))
    .map(println)

  chainA(2)  // should print "4"
  chainA(1)  // should print nothing
  chainB(10) // should print "10" - twice

它可能看起来有点太神奇了,所以这里是一个手工版本:

case class LazyChain[A, B](run: A => List[B]) {

  def filter(f: B => Boolean): LazyChain[A, B] = chain(_.filter(f))

  def map[C](f: B => C): LazyChain[A, C] = chain(_.map(f))

  def flatMap[C](f: B => List[C]): LazyChain[A, C] = chain(_.flatMap(f))

  def chain[C](f: List[B] => List[C]): LazyChain[A, C] = LazyChain(run andThen f)
}
object LazyChain {
  def apply[I]: LazyChain[I, I] = new LazyChain(a => List(a))
}

链接转换是一个常见问题,正如 cmets 所说,使用 monix.Observable、iteratees 等是解决此问题的正确方法(而不是简单的 List 和流自然是惰性的。

【讨论】:

    猜你喜欢
    • 2011-05-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-15
    相关资源
    最近更新 更多