摘要与Future对应的同步函数

时间:2017-09-01 06:09:38

标签: scala future

我写了一个非常简单的机制,在给定数量的max期间只允许seconds个函数调用。将其视为基本速率限制器。

它将执行限制为参数并返回原始执行的返回值。

问题是执行可以是同步的(类型=> A)或异步的(类型=> Future[A]),这会导致两个非常相似的函数:

case class Limiter[A](max: Int, seconds: Int) {
  private val queue = Queue[Long]()

  def limit(value: => A): Option[A] = {
    val now = System.currentTimeMillis()
    if (queue.length == max) {
      val oldest = queue.head
      if (now - oldest < seconds * 1000) return None
      else queue.dequeue()
    }
    queue.enqueue(now)
    Some(value)
  }

  def limitFuture(future: => Future[A]): Future[Option[A]] = {
    val now = System.currentTimeMillis()
    if (queue.length == max) {
      val oldest = queue.head
      if (now - oldest < seconds * 1000) return Future(None)
      else queue.dequeue()
    }
    future.map { x =>
      queue.enqueue(now)
      Some(x)
    }
  }
}

(我实际上并没有使用Option,而是我定义的一组类型,为简单起见仅使用Option

执行的例子:

// Prevent more than 5 runs/minute. Useful for example to prevent email spamming
val limit = Limit[Boolean](5, 60)
val result = limitFuture { sendEmail(...) } // `sendEmail` returns a future

// Prevent more than 1 run/hour. Useful for example to cache HTML response
val limit = Limit[String](1, 3600)
val limit { getHTML(...) } // `getHTML` returns the HTML as a string directly

如何重构这些方法以避免重复?以后的需求可能包括其他参数类型,而不仅仅是直接类型+ Future d类型,所以我想保留我的选项如果可能的话,打开。

到目前为止,我能提出的唯一“解决方案”是替换limit

def limit(value: => A): Option[A] = {
  Await.result(limitFuture(Future.successful(value)), 5.seconds)
}

嗯,它有效,但感觉倒退了。我希望=> A是其他方法扩展的基本版本,或者更好的是limitlimitFuture可以扩展的通用(私有)方法。 实际上,如果单个limit函数可以处理这个问题而不管参数如何,那就更好了。但我怀疑它是否可能。

2 个答案:

答案 0 :(得分:2)

您可以将此缩减为一个方法,并使用隐式参数处理差异:

trait Limitable[A, B] {
  type Out
  def none: Out
  def some(b: B, f: () => Unit): Out
}

implicit def rawLimitable[A]: Limitable[A, A] = new Limitable[A, A] {
  type Out = Option[A]
  def none = None
  def some(a: A, f: () => Unit): Out = {
    f()
    Some(a)
  }
}
implicit def futureLimitable[A]: Limitable[A, Future[A]] = new Limitable[A, Future[A]] {
  type Out = Future[Option[A]]
  def none = Future(None)
  def some(future: Future[A], f: () => Unit): Out = future.map { a =>
    f()
    Some(a)
  }
}

case class Limiter[A](max: Int, seconds: Int) {
  private val queue = Queue[Long]()

  def limit[B](in: => B)(implicit l: Limitable[A, B]): l.Out = {
    val now = System.currentTimeMillis()
    if (queue.length == max) {
      val oldest = queue.head
      if (now - oldest < seconds * 1000) return l.none
      else queue.dequeue()
    }
    l.some(in, {() => queue.enqueue(now)})
  }
}

并使用它:

val limit = Limit[String](1, 3600)
limit.limit("foo")
limit.limit(Future("bar"))

答案 1 :(得分:2)

您可以使用Applicativecats中的scalaz类型类。除其他外,应用程序允许您将值提升到某个上下文F(使用pure)并且也是一个仿函数,因此您可以在map上使用F[A]。< / p>

目前,您希望它用于IdFuture类型(您需要ExecutionContext,以便Future applicative工作)。它适用于VectorValidated之类的内容,因此您可能在添加自定义集合类型时遇到问题。

import cats._, implicits._
import scala.concurrent._
import scala.collection.mutable.Queue

case class Limiter[A](max: Int, seconds: Int) {
  private val queue = Queue[Long]()

  def limitA[F[_]: Applicative](value: => F[A]): F[Option[A]] = {
    val now = System.currentTimeMillis()
    if (queue.length == max) {
      val oldest = queue.head
      if (now - oldest < seconds * 1000) return none[A].pure[F]
      else queue.dequeue()
    }
    value.map { x =>
      queue.enqueue(now)
      x.some
    }
  }

  // or leave these e.g. for source compatibility
  def limit(value: => A): Option[A] = limitA[Id](value)
  def limitFuture(future: => Future[A])(implicit ec: ExecutionContext): Future[Option[A]] = limitA(future)
}

注意: 我使用none[A]代替None: Option[A]a.some代替Some(a): Option[A]。这些帮助程序在catsscalaz都可用,您需要它们,因为此处F[_]未定义为协变。

您必须明确指定Id作为类型,例如.limitA[Id](3)。但是Future不是这种情况。

map电话很奇怪。它被解析为:

future.map {
  queue.enqueue(now) // in current thread
  x => Some(x)
}

相同
queue.enqueue(now) // in current thread
future.map {
  x => Some(x)
}