首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >抽象一个同步函数及其未来对应函数

抽象一个同步函数及其未来对应函数
EN

Stack Overflow用户
提问于 2017-09-01 06:09:37
回答 2查看 99关注 0票数 1

我编写了一个非常简单的机制,它只允许在给定数量的max期间调用一个函数。把它看作是一个基本的限速器。

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

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

代码语言:javascript
运行
复制
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 )

执行实例:

代码语言:javascript
运行
复制
// 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

代码语言:javascript
运行
复制
def limit(value: => A): Option[A] = {
  Await.result(limitFuture(Future.successful(value)), 5.seconds)
}

好吧,很管用,但感觉倒过来了。我更希望=> A是其他方法扩展的基本版本,或者更好的是limitlimitFuture都可以扩展的通用(私有)方法。实际上,如果一个limit函数可以处理这个问题,而不考虑争论,那就更好了,但我怀疑这是可能的。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2017-09-01 07:12:37

您可以使用一个处理差异的隐式参数将其压缩为一个方法:

代码语言:javascript
运行
复制
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)})
  }
}

并把它当作:

代码语言:javascript
运行
复制
val limit = Limit[String](1, 3600)
limit.limit("foo")
limit.limit(Future("bar"))
票数 2
EN

Stack Overflow用户

发布于 2017-09-01 07:38:38

您可以使用Applicative类型从catsscalaz。除其他外,应用程序允许您将一个值提升到某个上下文F (使用pure),并且也是一个函子,因此您可以在F[A]上使用map

目前,您希望它适用于IdFuture类型(未来应用程序的工作范围中需要ExecutionContext )。它将适用于诸如VectorValidated之类的东西,因此在添加自定义集合类型时可能会遇到问题。

代码语言:javascript
运行
复制
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电话很奇怪。它被分析为:

代码语言:javascript
运行
复制
future.map {
  queue.enqueue(now) // in current thread
  x => Some(x)
}

这和

代码语言:javascript
运行
复制
queue.enqueue(now) // in current thread
future.map {
  x => Some(x)
}
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/45994184

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档