只执行一次函数并在scala中缓存值

时间:2017-10-11 19:30:20

标签: scala

我有一个像这样的功能

def runOnce(request: Request): Future[Result] = {
}

当我调用此runOnce函数时,如果它尚未运行,我希望它运行一些方法并返回该结果。如果它已经运行,我只是想让它返回原始结果(进入的请求将是相同的)。

如果我没有像这样的参数

,我可以这样做
lazy val hydratedModel = hydrateImpl(request)

future for efficient filtering
def fetchHydratedModel(): Future[HydratedModelRequest] = {
   hydratedModel
}

第一种情况怎么做?

2 个答案:

答案 0 :(得分:4)

这个问题的一般解决方案是功能记忆;对于纯函数(一个没有副作用的函数 - 它不适用于非纯函数),对于同一组参数值,函数调用的结果应始终相同。因此,优化是在第一次调用时缓存该值并将其返回以用于后续调用。

您可以使用以下内容实现此目的(具有单个参数的纯函数的memoization类,更新 - 请参阅下面的注释 - 以使其成为线程安全的):

/** Memoize a pure function `f(A): R`
 *
 *  @constructor Create a new memoized function.
 *  @tparam A Type of argument passed to function.
 *  @tparam R Type of result received from function.
 *  @param f Pure function to be memoized.
 */
final class Memoize1[A, R](f: A => R) extends (A => R) {

  // Cached function call results.
  private val result = scala.collection.mutable.Map.empty[A, R]

  /** Call memoized function.
   *
   *  If the function has not been called with the specified argument value, then the
   *  function is called and the result cached; otherwise the previously cached
   *  result is returned.
   *
   *  @param a Argument value to be passed to `f`.
   *  @return Result of `f(a)`.
   */
  def apply(a: A) = synchronized(result.getOrElseUpdate(a, f(a)))
}

/** Memoization companion */
object Memoize1 {

  /** Memoize a specific function.
   *
   *  @tparam A Type of argument passed to function.
   *  @tparam R Type of result received from function.
   *  @param f Pure function to be memoized.
   */
  def apply[A, R](f: A => R) = new Memoize1(f)
}

假设您正在记忆的功能是hydrateImpl,您可以按如下方式定义和使用runOnce(请注意,它变为val而不是def }):

val runOnce = Memoize1(hydrateImpl)
runOnce(someRequest) // Executed on first call with new someRequest value, cached result subsequently.

更新:关于线程安全。

在回复user1913596的评论时,答案是" no&#34 ;; scala.collection.mutable.Map.getOrElseUpdate不是线程安全的。但是,同步访问权限相当简单,我相应地更新了原始代码(将调用嵌入sychronized(...))。

锁定访问的性能损失应该通过改进的执行时间来抵消(假设f是非常重要的)。

答案 1 :(得分:0)

根据您的设置,可能有更好的方法来执行此操作,但一个简单的解决方案是执行以下操作

private var model: Option[Future[HydratedModelRequest]] = None

def runOnce(request: Request): Future[Request] = {
  if (model.isEmpty) {
    model = hydrateImpl(request)
  }

  model.get
}

如果request对于每次调用确实相同,则另一种选择是隐式地要求请求并且懒惰地补充。

implicit val request: Request
lazy val hydratedRequest: Future[HydratedModelRequest] = hydrateImpl(request)