比方说,有一个名为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
}
我想知道,这可能吗?我为什么需要它?我只是好奇,如果可能的话。
答案 0 :(得分: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
。
或者你可以隐藏可变状态。例如,您可以使用actor:
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
或可变集合),但存在隐藏的可变状态 - actor行为。
答案 1 :(得分: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
函数中的内容,但这不是一个理想的情况,因为我们需要separation of concerns。
所以我们要做的是创建一个通用的Cacher对象,它可以缓存任何值。我们希望能够做到这样的事情:
cached_id = Cacher(get_id)
cached_id.get()
然后获取缓存ID或询问用户该号码,具体取决于该号码是否已缓存/最近更新。
Cacher
对象需要保存数据和数据的到期时间。在此ID情况下,转换为cacher中的两个字段。您可能希望在真实世界的缓存对象中使用map / dict,但我试图保持简单。
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中的actor实现,我只想展示原理。