Skip to content

Commit

Permalink
Fixes #771 RefM should permit errors (#774)
Browse files Browse the repository at this point in the history
* Fixes #771 RefM should permit errors

* Moved error type parameter from `RefM` to corresponding methods
  • Loading branch information
GodPlaysChess authored and jdegoes committed Apr 21, 2019
1 parent fae5d19 commit 1d1dd37
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 25 deletions.
70 changes: 61 additions & 9 deletions core/jvm/src/test/scala/scalaz/zio/RefMSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,20 @@ class RefMSpec(implicit ee: org.specs2.concurrent.ExecutionEnv) extends TestRunt
`read` returns the current value. $e1
`write` puts the new value correctly. $e2
`update` changes the value and returns the updated value. $e3
`updateSome` changes a given type State in some cases and returns the updated value. $e4
`updateSome` returns the old value for an undefined State. $e5
`modify` changes the value and returns another value computed from the modification. $e6
`modifySome` changes a given type State in some cases and returns a value computed from the modification. $e7
`modifySome` returns a default value without modifying the State. $e8
`update` returns an error if update effect failed $e4
`updateSome` changes a given type State in some cases and returns the updated value. $e5
`updateSome` returns the old value for an undefined State. $e6
`updateSome` returns an error if update effect failed $e7
`modify` changes the value and returns another value computed from the modification. $e8
`modify` returns a error if modification effect failed $e9
`modifySome` changes a given type State in some cases and returns a value computed from the modification. $e10
`modifySome` returns a default value without modifying the State. $e11
`modifySome` returns a default value if modification effect failed $e12
`modifySome` returns a error if modification effect failed $e13
"""

val (current, update) = ("value", "new value")
val fail = "fail"

sealed trait State
case object Active extends State
Expand Down Expand Up @@ -47,6 +53,14 @@ class RefMSpec(implicit ee: org.specs2.concurrent.ExecutionEnv) extends TestRunt
)

def e4 =
unsafeRun(
(for {
refM <- RefM.make[String](current)
value <- refM.update(_ => IO.fail(fail))
} yield value).flip.map(_ must beTheSameAs(fail))
)

def e5 =
unsafeRun(
for {
refM <- RefM.make[State](Active)
Expand All @@ -58,15 +72,27 @@ class RefMSpec(implicit ee: org.specs2.concurrent.ExecutionEnv) extends TestRunt
} yield (value1 must beTheSameAs(Changed)) and (value2 must beTheSameAs(Closed))
)

def e5 =
def e6 =
unsafeRun(
for {
refM <- RefM.make[State](Active)
value <- refM.updateSome { case Closed => IO.succeed(Active) }
} yield value must beTheSameAs(Active)
)

def e6 =
def e7 =
unsafeRun(
(for {
refM <- RefM.make[State](Active)
_ <- refM.updateSome { case Active => IO.fail(fail) }
value2 <- refM.updateSome {
case Active => IO.succeed(Changed)
case Changed => IO.succeed(Closed)
}
} yield value2).flip.map(_ must beTheSameAs(fail))
)

def e8 =
unsafeRun(
for {
refM <- RefM.make(current)
Expand All @@ -75,7 +101,15 @@ class RefMSpec(implicit ee: org.specs2.concurrent.ExecutionEnv) extends TestRunt
} yield (r must beTheSameAs("hello")) and (value must beTheSameAs(update))
)

def e7 =
def e9 =
unsafeRun(
(for {
refM <- RefM.make[String](current)
r <- refM.modify(_ => IO.fail(fail))
} yield r).flip map (_ must beTheSameAs(fail))
)

def e10 =
unsafeRun(
for {
refM <- RefM.make[State](Active)
Expand All @@ -92,12 +126,30 @@ class RefMSpec(implicit ee: org.specs2.concurrent.ExecutionEnv) extends TestRunt
))
)

def e8 =
def e11 =
unsafeRun(
for {
refM <- RefM.make[State](Active)
r <- refM.modifySome("State doesn't change") { case Closed => IO.succeed("active" -> Active) }
value <- refM.get
} yield (r must beTheSameAs("State doesn't change")) and (value must beTheSameAs(Active))
)

def e12 =
unsafeRun(
for {
refM <- RefM.make[State](Active)
r <- refM.modifySome("State doesn't change") { case Closed => IO.fail(fail) }
value <- refM.get
} yield (r must beTheSameAs("State doesn't change")) and (value must beTheSameAs(Active))
)

def e13 =
unsafeRun(
(for {
refM <- RefM.make[State](Active)
_ <- refM.modifySome("State doesn't change") { case Active => IO.fail(fail) }
value <- refM.get
} yield value).flip.map(_ must beTheSameAs(fail))
)
}
32 changes: 16 additions & 16 deletions core/shared/src/main/scala/scalaz/zio/RefM.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import scalaz.zio.Exit.Cause
* } yield ()
* }}}
*/
final class RefM[A] private (value: Ref[A], queue: Queue[RefM.Bundle[A, _]]) extends Serializable {
final class RefM[A] private (value: Ref[A], queue: Queue[RefM.Bundle[_, A, _]]) extends Serializable {

/**
* Reads the value from the `Ref`.
Expand All @@ -57,24 +57,24 @@ final class RefM[A] private (value: Ref[A], queue: Queue[RefM.Bundle[A, _]]) ext
* Atomically modifies the `RefM` with the specified function, returning the
* value immediately after modification.
*/
final def update[R](f: A => ZIO[R, Nothing, A]): ZIO[R, Nothing, A] =
final def update[R, E](f: A => ZIO[R, E, A]): ZIO[R, E, A] =
modify(a => f(a).map(a => (a, a)))

/**
* Atomically modifies the `RefM` with the specified partial function.
* if the function is undefined in the current value it returns the old value without changing it.
*/
final def updateSome[R](pf: PartialFunction[A, ZIO[R, Nothing, A]]): ZIO[R, Nothing, A] =
final def updateSome[R, E](pf: PartialFunction[A, ZIO[R, E, A]]): ZIO[R, E, A] =
modify(a => pf.applyOrElse(a, (_: A) => IO.succeed(a)).map(a => (a, a)))

/**
* Atomically modifies the `RefM` with the specified function, which computes
* a return value for the modification. This is a more powerful version of
* `update`.
*/
final def modify[R, B](f: A => ZIO[R, Nothing, (B, A)]): ZIO[R, Nothing, B] =
final def modify[R, E, B](f: A => ZIO[R, E, (B, A)]): ZIO[R, E, B] =
for {
promise <- Promise.make[Nothing, B]
promise <- Promise.make[E, B]
ref <- Ref.make[Option[Cause[Nothing]]](None)
env <- ZIO.environment[R]
bundle = RefM.Bundle(ref, f.andThen(_.provide(env)), promise)
Expand All @@ -90,14 +90,14 @@ final class RefM[A] private (value: Ref[A], queue: Queue[RefM.Bundle[A, _]]) ext
* otherwise it returns a default value.
* This is a more powerful version of `updateSome`.
*/
final def modifySome[R, B](default: B)(pf: PartialFunction[A, ZIO[R, Nothing, (B, A)]]): ZIO[R, Nothing, B] =
final def modifySome[R, E, B](default: B)(pf: PartialFunction[A, ZIO[R, E, (B, A)]]): ZIO[R, E, B] =
for {
promise <- Promise.make[Nothing, B]
promise <- Promise.make[E, B]
ref <- Ref.make[Option[Cause[Nothing]]](None)
env <- ZIO.environment[R]
bundle = RefM.Bundle(
bundle = RefM.Bundle[E, A, B](
ref,
pf.andThen(_.provide(env)).orElse[A, UIO[(B, A)]] { case a => IO.succeed(default -> a) },
pf.andThen(_.provide(env)).orElse[A, IO[E, (B, A)]] { case a => IO.succeed(default -> a) },
promise
)
b <- (for {
Expand All @@ -108,16 +108,16 @@ final class RefM[A] private (value: Ref[A], queue: Queue[RefM.Bundle[A, _]]) ext
}

object RefM extends Serializable {
private[RefM] final case class Bundle[A, B](
private[RefM] final case class Bundle[E, A, B](
interrupted: Ref[Option[Cause[Nothing]]],
update: A => UIO[(B, A)],
promise: Promise[Nothing, B]
update: A => IO[E, (B, A)],
promise: Promise[E, B]
) {
final def run(a: A, ref: Ref[A], onDefect: Cause[Nothing] => UIO[Unit]): UIO[Unit] =
final def run(a: A, ref: Ref[A], onDefect: Cause[E] => UIO[Unit]): UIO[Unit] =
interrupted.get.flatMap {
case Some(cause) => onDefect(cause)
case None =>
update(a).sandbox.foldM(onDefect, {
update(a).foldM(e => onDefect(Cause.fail(e)) <* promise.fail(e), {
case (b, a) => ref.set(a) <* promise.succeed(b)
})
}
Expand All @@ -129,11 +129,11 @@ object RefM extends Serializable {
final def make[A](
a: A,
n: Int = 1000,
onDefect: Cause[Nothing] => UIO[Unit] = _ => IO.unit
onDefect: Cause[_] => UIO[Unit] = _ => IO.unit
): UIO[RefM[A]] =
for {
ref <- Ref.make(a)
queue <- Queue.bounded[Bundle[A, _]](n)
queue <- Queue.bounded[Bundle[_, A, _]](n)
_ <- queue.take.flatMap(b => ref.get.flatMap(a => b.run(a, ref, onDefect))).forever.fork
} yield new RefM[A](ref, queue)

Expand Down

0 comments on commit 1d1dd37

Please sign in to comment.