1

比方说,有一个类叫做RemoteIdGetter. 它从服务器返回一个密钥。但它仅在密钥不够“新鲜”时才向服务器发出请求,这意味着上次请求它的时间大于或等于 5 分钟(300 秒)。否则,它返回键的本地“缓存”值。

我需要在没有 var)改变内部状态 RemoteIdGetter或使用纯函数方法的情况下做到这一点。

它可能看起来像这样:

class RemoteIdGetter {
  def key = {
    if (!needToAskServer) // return the local "cached" value of the key
    else makeRequest

  }

  def makeRequest = // make a request to a remote server to get the key
  def time = // current date-time
  def lastUpdatedTime = // the last date-time a key has been updated 
                        // (requested from the server)
  def needToAskServer = time - lastUpdatedTime >= 300
}

我想知道,这可能吗?为什么我需要它?我只是好奇这是否可能。

4

2 回答 2

6

每次使用相同的参数调用纯函数时,它都应该返回相同的结果,因此,如果您想在完全没有可变状态的情况下执行此操作,则RemoteIdGetter每次获得这样的键时都必须生成新的:

case class RemoteIdGetter(cachedKey: Option[KeyType] = None, lastUpdatedTime: Option[DateTime] = None) {
  def getKey(time: DateTime) = {
    val (key, t) = (for {
      k <- cachedKey
      lt <- lastUpdatedTime
      if (time - lt < cachePeriod)
    } yield k -> lt).getOrElse(makeRequest -> time)
    key -> RemoteIdGetter(Some(key), Some(t))
  }
}

用法:

val (key, newGetted) = oldGetter.getKey(currentDateTime)

RemoteIdGetter您每次都必须使用最新生成的。

或者,您可以隐藏可变状态。例如,您可以使用演员:

import akka.actor.ActorDSL._
val a = actor(new Act {
  become {
    case GetKey => replyAndBecome(sender)
  }

  def replyAndBecome(sender: ActorRef): {
    val key = makeRequest
    sender ! key
    become getState(key, time)
  }

  def getState(key: KeyType, lastUpdatedTime: DateTime): Receive = {
    case GetKey =>
      if (time - lastUpdatedTime < cachePeriod)
        sender ! key
      else 
        replyAndBecome(sender)
  }
})

没有可见的可变状态(例如var,或可变集合),但有一个隐藏的可变状态 - 演员行为。

于 2013-06-08T09:15:20.443 回答
4

最简单的方法是RemoteIdGetter返回一个由 1. 请求的键和 2. 自身的新实例组成的对,并缓存了请求的键。针对RemoteIdGetter. 实例的自动线程可以使用 monad 来完成。

在 Scala 中没有特别需要它的原因,因为您可以通过在类中更改缓存映射来实现相同的目的。

有点有趣的是,您可以编写一个RemoteIdGetter从在线获取密钥的普通函数,然后编写一个通用缓存函数来包装任何昂贵的计算。这与 memoization 的概念非常相似,只是您保留了一些有关何时要丢弃缓存结果的元数据。没有真正的理由RemoteIdGetter应该自己做缓存,当它可以被卸载到其他一些专门用于缓存的通用函数时。


通用缓存器

因此,假设您有一个功能可以做一些昂贵的事情。在这种情况下,为了简单起见,我只是让它接收用户输入。

def get_id():
    try:
        return int(input("Enter an id: "))
    except ValueError:
        return get_id()

这要求用户输入一个整数。如果用户没有输入一个,它只是再次询问。我们不想一直用整数打扰用户,所以我们想缓存用户输入的值以供进一步使用。我们可以在函数中缓存东西get_id,但这不是理想的情况,因为我们想要分离关注点

所以我们要做的是创建一个通用的缓存对象,它可以缓存任何值。我们希望能够做这样的事情:

cached_id = Cacher(get_id)
cached_id.get()

然后获取缓存的 ID 或向用户询问号码,具体取决于号码是否被缓存/最近更新。

Cacher对象需要保存数据和数据的过期时间。在这种 ID 情况下,这将转换为缓存器中的两个字段。您可能希望在现实世界的缓存对象中使用地图/字典,但我试图保持简单。

class Cacher:
    value = None
    expires = 0

如果值未缓存,则使用要调用的函数初始化缓存对象,所以这只是

    def __init__(self, function):
        self.external = function

然后有趣的是get()方法,它最简单的形式看起来像

    # Get a value and in case it is retrieved, make it good for 30 seconds
    def get(self, ttl=30):
        if not self.value or datetime.now() > self.expires:
            # Get a new value from the external source
            self.value = self.external()
            # and update the new expiration time to ttl seconds from now
            self.expires = datetime.now() + timedelta(seconds=ttl)

        return self.value

然后,当您cached_id.get()第一次调用时,您必须输入一个整数,但 30 秒内的任何连续调用都将检索最后输入的整数。

这个可变的 Python 对象当然可以在 Scala 中实现为演员,我只是想展示其原理。

于 2013-06-08T09:05:31.843 回答