我写了一个非常简单的机制,在给定数量的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
是其他方法扩展的基本版本,或者更好的是limit
和limitFuture
可以扩展的通用(私有)方法。
实际上,如果单个limit
函数可以处理这个问题而不管参数如何,那就更好了。但我怀疑它是否可能。
答案 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)
您可以使用Applicative
或cats
中的scalaz
类型类。除其他外,应用程序允许您将值提升到某个上下文F
(使用pure
)并且也是一个仿函数,因此您可以在map
上使用F[A]
。< / p>
目前,您希望它用于Id
和Future
类型(您需要ExecutionContext,以便Future applicative工作)。它适用于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)
}