Created
May 2, 2014 21:07
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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