Scala 抽象一个同步函数及其未来的对应项

Scala 抽象一个同步函数及其未来的对应项,scala,future,Scala,Future,我编写了一个非常简单的机制,它只允许在给定的秒数内调用max个数的函数。将其视为基本利率限制 它将执行限制作为参数,并返回原始执行的返回值 问题是执行可以是同步的(类型为=>A)或异步的(类型为=>Future[A]),这会导致两种极其相似的功能: case class Limiter[A](max: Int, seconds: Int) { private val queue = Queue[Long]() def limit(value: => A): Option[A] =

我编写了一个非常简单的机制,它只允许在给定的
秒数
内调用
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)
    }
  }
}
我如何重构这些方法以避免重复?以后的需要可能包括其他参数类型,而不仅仅是直接类型+
未来的
d类型,因此如果可能,我想保留我的选项

到目前为止,我唯一能想到的“解决方案”是替换
限制

def limit(value: => A): Option[A] = {
  Await.result(limitFuture(Future.successful(value)), 5.seconds)
}
好吧,这很管用,但感觉很落后。我宁愿让
=>A
作为其他方法扩展的基础版本,或者更好的是,让
limit
limitFuture
都可以扩展的通用(私有)方法。
实际上,如果一个
limit
函数不管参数如何都能处理这个问题,那就更好了,但我怀疑这是可能的。

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

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)})
  }
}

您可以将其浓缩为一种方法,使用隐式参数处理差异:

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)})
  }
}

您可以使用
cats
scalaz
中的
Applicative
typeclass。除其他外,Applicative允许您将值提升到某个上下文
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)
}
这和

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

您可以使用
cats
scalaz
中的
Applicative
typeclass。除其他外,Applicative允许您将值提升到某个上下文
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)
}
这和

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

您是否计划在多线程环境中使用您的代码?如果是,则您具有比赛条件。如果否,则可以在异步变量中将
排队
移动到future.map之前。然后,您就可以在一个单独的泛型函数中提取所有通用逻辑。我不打算在多线程环境中运行它,但因为这对我来说是一个学习练习,我很高兴能够改进并发现如何在需要的时候避免犯同样的错误:)我无法从
未来的
中提取
排队
。map
,因为只有成功的运行才应该排队。在上面的示例中,如果
sendmail
由于某种原因失败,则该尝试不应计入限制。有意义吗?如果您只有一个线程,那么根本不清楚为什么要使用异步api。无论如何,目前您的方法在语义上是不同的。在第一种方法中,
now
在计算lazy
value
之前排队,而在第二种方法中,它在future成功之后排队,正如您所描述的。“如果您只有一个线程,那么根本不清楚为什么要使用异步api。”>Fair point。2个原因:
sendmail
正在使用外部库发送电子邮件,它返回一个
未来
,我决定将其传递给大家。虽然我可以在
sendmail
中阻止并处理它。其他原因:这最初是我学习如何做这些事情的一个练习。我可以四处转转,但这会在我真正需要它时教会我:D“无论如何,目前你的方法在语义上是不同的。”>这是非常正确的,谢谢。我需要替换
queue.enqueue(现在);部分(值)
带有
val选项=部分(值);排队。排队(现在);选项
。这看起来更好吗?您是否计划在多线程环境中使用您的代码?如果是,则您具有比赛条件。如果否,则可以在异步变量中将
排队
移动到future.map之前。然后,您就可以在一个单独的泛型函数中提取所有通用逻辑。我不打算在多线程环境中运行它,但因为这对我来说是一个学习练习,我很高兴能够改进并发现如何在需要的时候避免犯同样的错误:)我无法从
未来的
中提取
排队
。map
,因为只有成功的运行才应该排队。在上面的示例中,如果
sendmail
由于某种原因失败,则该尝试不应计入限制。有意义吗?如果您只有一个线程,那么根本不清楚为什么要使用异步api。无论如何,目前您的方法在语义上是不同的。在第一种方法中,
now
在计算lazy
value
之前排队,而在第二种方法中,它在future成功之后排队,正如您所描述的。“如果您只有一个线程,那么根本不清楚为什么要使用异步api。”>Fair point。2个原因:
sendmail
正在使用外部库发送电子邮件,它返回一个
未来
,我决定将其传递给大家。虽然我可以在
sendmail
中阻止并处理它。其他原因:这最初是我学习如何做这些事情的一个练习。我可以四处走走,但当我真的需要它时,这会教会我:D“不管怎样,现在你的冰毒