我编写了一个非常简单的机制,它只允许在给定数量的max期间调用一个函数。把它看作是一个基本的限速器。
它将执行作为参数进行限制,并返回原始执行的返回值。
问题是,执行可以是同步的(类型为=> 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如何重构这些方法以避免重复?以后的需求可能包括其他参数类型,而不仅仅是直接类型+ Futured类型,因此如果可能的话,我希望保留我的选项。
到目前为止,我能想出的唯一“解决方案”是替换limit。
def limit(value: => A): Option[A] = {
Await.result(limitFuture(Future.successful(value)), 5.seconds)
}好吧,很管用,但感觉倒过来了。我更希望=> A是其他方法扩展的基本版本,或者更好的是limit和limitFuture都可以扩展的通用(私有)方法。实际上,如果一个limit函数可以处理这个问题,而不考虑争论,那就更好了,但我怀疑这是可能的。
发布于 2017-09-01 07:12:37
您可以使用一个处理差异的隐式参数将其压缩为一个方法:
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"))发布于 2017-09-01 07:38:38
您可以使用Applicative类型从cats或scalaz。除其他外,应用程序允许您将一个值提升到某个上下文F (使用pure),并且也是一个函子,因此您可以在F[A]上使用map。
目前,您希望它适用于Id和Future类型(未来应用程序的工作范围中需要ExecutionContext )。它将适用于诸如Vector或Validated之类的东西,因此在添加自定义集合类型时可能会遇到问题。
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]。这些助手在cats和scalaz中都是可用的,您需要它们,因为这里没有将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)
}https://stackoverflow.com/questions/45994184
复制相似问题