-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add ZIO#cached #1361
Add ZIO#cached #1361
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ import org.specs2.execute.Result | |
import scala.collection.mutable | ||
import scala.util.Try | ||
import zio.Cause.{ die, fail, interrupt, Both } | ||
import zio.duration._ | ||
|
||
class IOSpec(implicit ee: org.specs2.concurrent.ExecutionEnv) extends TestRuntime with GenIO with ScalaCheck { | ||
import Prop.forAll | ||
|
@@ -45,6 +46,7 @@ class IOSpec(implicit ee: org.specs2.concurrent.ExecutionEnv) extends TestRuntim | |
Check `absolve` method on IO[E, Either[E, A]] returns the same IO[E, Either[E, String]] as `IO.absolve` does. $testAbsolve | ||
Check non-`memoize`d IO[E, A] returns new instances on repeated calls due to referential transparency. $testNonMemoizationRT | ||
Check `memoize` method on IO[E, A] returns the same instance on repeated calls. $testMemoization | ||
Check `cached` method on IO[E, A] returns new instances after duration. $testCached | ||
Check `raceAll` method returns the same IO[E, A] as `IO.raceAll` does. $testRaceAll | ||
Check `firstSuccessOf` method returns the same IO[E, A] as `IO.firstSuccessOf` does. $testfirstSuccessOf | ||
Check `zipPar` method does not swallow exit causes of loser. $testZipParInterupt | ||
|
@@ -305,6 +307,21 @@ class IOSpec(implicit ee: org.specs2.concurrent.ExecutionEnv) extends TestRuntim | |
) | ||
} | ||
|
||
def testCached = { | ||
def incrementAndGet(ref: Ref[Int]): UIO[Int] = ref.modify(n => (n + 1, n + 1)) | ||
unsafeRun { | ||
for { | ||
ref <- Ref.make(0) | ||
cache <- incrementAndGet(ref).cached(100.milliseconds) | ||
a <- cache | ||
b <- cache | ||
_ <- clock.sleep(100.milliseconds) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will be flaky due to non-determinism. We could use with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, trying to test this is actually what led me to identify the issues with the |
||
c <- cache | ||
d <- cache | ||
} yield (a must_=== b) && (b must_!== c) && (c must_=== d) | ||
} | ||
} | ||
|
||
def testRaceAll = { | ||
val io = IO.effectTotal("supercalifragilisticexpialadocious") | ||
val ios = List.empty[UIO[String]] | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -147,6 +147,30 @@ sealed trait ZIO[-R, +E, +A] extends Serializable { self => | |
} | ||
)(use) | ||
|
||
/** | ||
* Returns an effect that, if evaluated, will return the cached result of | ||
* this effect. Cached results will expire after `timeToLive` duration. | ||
*/ | ||
final def cached(timeToLive: Duration): ZIO[R with Clock, Nothing, IO[E, A]] = { | ||
|
||
def get(cache: RefM[Option[Promise[E, A]]]): ZIO[R with Clock, E, A] = | ||
cache.update { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great suggestion! |
||
case Some(p) => | ||
ZIO.succeed(Some(p)) | ||
case None => | ||
for { | ||
p <- Promise.make[E, A] | ||
_ <- self.to(p) | ||
_ <- p.await.delay(timeToLive).flatMap(_ => cache.set(None)).fork | ||
} yield Some(p) | ||
}.flatMap(_.get.await) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks great! There are only two ways to improve this:
The first would necessitate the second, I think. In any case, looks good to me, 👍 to ship! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I updated it to deal with interruption by using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, yes, you did. I forgot you were using |
||
|
||
for { | ||
r <- ZIO.environment[R with Clock] | ||
cache <- RefM.make[Option[Promise[E, A]]](None) | ||
} yield get(cache).provide(r) | ||
} | ||
|
||
/** | ||
* Recovers from all errors. | ||
* | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using
update
instead ofmodify
, you won't have to repeatn + 1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes.