Skip to content

Instantly share code, notes, and snippets.

@Mortimerp9
Created May 2, 2014 21:07
Show Gist options
  • Save Mortimerp9/294f8e9223a181d3eecd to your computer and use it in GitHub Desktop.
Save Mortimerp9/294f8e9223a181d3eecd to your computer and use it in GitHub Desktop.
Sort of a lazy val, but that refreshes itselfs every so often; or a temporary cache for a value T, will be refreshed at a minimun at the specified interval.
import com.twitter.util.{Duration, Time}
/**
* Sort of a lazy val, but that refreshes itselfs every so often; or a temporary cache for a value T,
* will be refreshed at a minimun at the specified interval.
* In practice, the refresh is done only when the value is accessed, so there are no guarantees
* to when it will actually be refreshed. You can just be sure that it won't be refreshed if two calls
* are made within `every`.
*/
class RefreshEvery[T](every: Duration)(refresh: => T) {
private case class TimeStamped(value: T, at: Time)
private var cached: Option[TimeStamped] = None
private def doRefresh(): T = synchronized {
val newVal = TimeStamped(refresh, Time.now)
cached = Some(newVal)
newVal.value
}
/**
* when was this last refreshed
* @return
*/
def lastRefresh: Option[Time] = synchronized {
cached.map(_.at)
}
/**
* get the value contained in this, maybe will refresh it if the last refresh was
* too long ago
* @return
*/
def apply(): T = synchronized {
cached match {
case Some(TimeStamped(value, at)) if at.untilNow < every =>
value
case _ => doRefresh()
}
}
/**
* flatMap that value, keeping this.every as the refresh duration
* not a proper monad because of the every parameter.
* Note that the RefreshEvery returned by the map will be linked to this
* one an refresh the value stored in this.
* @param fn
* @tparam A
* @return
*/
def flatMap[A](fn: (T) => RefreshEvery[A]): RefreshEvery[A] =
RefreshEvery(every) {
val t: T = doRefresh()
fn(t)()
}
/**
* map that value, keeping this.every as the refresh duration
* not a proper monad because of the every parameter.
* Note that the RefreshEvery returned by the map will be linked to this
* one an refresh the value stored in this.
* @param fn
* @tparam A
* @return
*/
def map[A](fn: (T) => A): RefreshEvery[A] = RefreshEvery(every) {
val t: T = doRefresh()
fn(t)
}
}
object RefreshEvery {
def apply[T](every: Duration)(refresh: => T) = new RefreshEvery[T](every)(refresh)
}
import com.twitter.util.Time
import com.twitter.conversions.time._
import org.scalatest.FunSuite
import org.scalatest.Matchers
class RefreshEveryTest extends FunSuite with Matchers {
test("should not recompute the value if the duration hasn't expired") {
Time.withCurrentTimeFrozen { tc =>
var cnt = 0
val value = RefreshEvery(1 minutes) {
cnt += 1
cnt
}
value() should be (1)
val ref = Time.now
value.lastRefresh should be(Some(ref))
tc.advance(10 seconds)
value() should be(1)
value.lastRefresh should be(Some(ref))
}
}
test("should refresh after the duration") {
Time.withCurrentTimeFrozen {
tc =>
var cnt = 0
val value = RefreshEvery(1 minute) {
cnt += 1
cnt
}
value() should be (1)
value.lastRefresh should be(Some(Time.now))
tc.advance(2 minutes)
value() should be(2)
value.lastRefresh should be(Some(Time.now))
}
}
test("should map") {
val value = RefreshEvery(1 minute) {
10
}
val double = value.map(_*2)
double() should be (20)
}
test("should flatMap. identity") {
val value = RefreshEvery(1 minute) {
10
}
val identity = value.flatMap(v => RefreshEvery(1 minute)(v))
identity() should be(10)
}
test("should flatMap. unit") {
val value = RefreshEvery(1 minute) {
10
}
val unit = value.flatMap(v => RefreshEvery(1 minute)(v*2))
unit() should be (20)
}
test("should flatMap. associativity") {
val value = RefreshEvery(1 minute) {
10
}
def f(v: Int) = RefreshEvery(1 minute)(v*2)
def g(v: Int) = RefreshEvery(1 minute)(v+5)
val ass1 = value.flatMap(f).flatMap(g)
val ass2 = value.flatMap { v =>
g(f(v)())
}
ass1() should be(ass2())
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment