Skip to content

Commit

Permalink
Error to signal array index out of bounds
Browse files Browse the repository at this point in the history
  • Loading branch information
LGLO committed Aug 2, 2019
1 parent e72a0c5 commit 97a7d65
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 32 deletions.
50 changes: 39 additions & 11 deletions core-tests/jvm/src/test/scala/zio/stm/TArraySpec.scala
Expand Up @@ -21,7 +21,7 @@ final class TArraySpec(implicit ee: org.specs2.concurrent.ExecutionEnv) extends
def is = "TArraySpec".title ^ s2"""
apply:
happy-path $applyHappy
returns None when index is out of bounds $applyOutOfBounds
fails with ArrayIndexOutOfBounds when index is out of bounds $applyOutOfBounds
collect:
is atomic $collectAtomic $collectAtomic
is safe for empty array $collectEmpty
Expand All @@ -42,9 +42,13 @@ final class TArraySpec(implicit ee: org.specs2.concurrent.ExecutionEnv) extends
transformM:
updates values atomically $transformMAtomically
updates all or nothing $transformMTransactionally
update`:
update:
happy-path $updateHappy
returns None when index is out of bounds $updateOutOfBounds
fails with ArrayIndexOutOfBounds when index is out of bounds $updateOutOfBounds
updateM:
happy-path $updateMHappy
fails with ArrayIndexOutOfBounds when index is out of bounds $updateMOutOfBounds
updateM failure $updateMFailure
"""

def applyHappy =
Expand All @@ -53,15 +57,15 @@ final class TArraySpec(implicit ee: org.specs2.concurrent.ExecutionEnv) extends
tArray <- makeTArray(1)(42)
value <- tArray(0).commit
} yield value
) mustEqual Some(42)
) mustEqual 42

def applyOutOfBounds =
unsafeRun(
for {
tArray <- makeTArray(1)(42)
value <- tArray(-1).commit
} yield value
) mustEqual None
result <- tArray(-1).commit.either
} yield result.fold(_.getMessage, _ => "unexpected")
) mustEqual "Array index out of range: -1"

def collectAtomic =
unsafeRun(
Expand Down Expand Up @@ -183,15 +187,39 @@ final class TArraySpec(implicit ee: org.specs2.concurrent.ExecutionEnv) extends
tArray <- makeTArray(1)(42)
v <- tArray.update(0, a => -a).commit
} yield v
) mustEqual Some(-42)
) mustEqual -42

def updateOutOfBounds =
unsafeRun(
for {
tArray <- makeTArray(1)(42)
value <- tArray.update(-1, identity).commit
} yield value
) mustEqual None
result <- tArray.update(-1, identity).commit.either
} yield result.fold(_.getMessage, _ => "unexpected")
) mustEqual "Array index out of range: -1"

def updateMHappy =
unsafeRun(
for {
tArray <- makeTArray(1)(42)
v <- tArray.updateM(0, a => STM.succeedLazy(-a)).commit
} yield v
) mustEqual -42

def updateMOutOfBounds =
unsafeRun(
for {
tArray <- makeTArray(10)(0)
result <- tArray.updateM(10, STM.succeed(_)).commit.either
} yield result.fold(e => e.fold(_ => "unexpected", _.getMessage), _ => "unexpected")
) mustEqual "Array index out of range: 10"

def updateMFailure =
unsafeRun(
for {
tArray <- makeTArray(n)(0)
result <- tArray.updateM(0, _ => STM.fail(boom)).commit.either
} yield result.fold(e => e.fold(_.getMessage, _ => "unexpected"), _ => "unexpected")
) mustEqual ("Boom!")

private val N = 1000
private val n = 10
Expand Down
42 changes: 21 additions & 21 deletions core/shared/src/main/scala/zio/stm/TArray.scala
Expand Up @@ -21,12 +21,12 @@ package zio.stm
* */
class TArray[A] private (val array: Array[TRef[A]]) extends AnyVal {

/** Extracts value from ref in array. Returns None when index is out of bounds. */
def apply(index: Int): STM[Nothing, Option[A]] =
if (0 <= index && index < array.size) array(index).get.map(Some(_))
else STM.succeed(None)
/** Extracts value from ref in array. */
final def apply(index: Int): STM[ArrayIndexOutOfBoundsException, A] =
if (0 <= index && index < array.size) array(index).get
else STM.fail(new ArrayIndexOutOfBoundsException(index))

def collect[B](pf: PartialFunction[A, B]): STM[Nothing, TArray[B]] =
final def collect[B](pf: PartialFunction[A, B]): STM[Nothing, TArray[B]] =
this
.foldM(List.empty[TRef[B]]) {
case (acc, a) =>
Expand All @@ -36,12 +36,12 @@ class TArray[A] private (val array: Array[TRef[A]]) extends AnyVal {
.map(l => TArray(l.reverse.toArray))

/* Atomically folds [[TArray]] with pure function. */
def fold[Z](acc: Z)(op: (Z, A) => Z): STM[Nothing, Z] =
final def fold[Z](acc: Z)(op: (Z, A) => Z): STM[Nothing, Z] =
if (array.isEmpty) STM.succeed(acc)
else array.head.get.flatMap(a => new TArray(array.tail).fold(op(acc, a))(op))

/** Atomically folds [[TArray]] with STM function. */
def foldM[E, Z](acc: Z)(op: (Z, A) => STM[E, Z]): STM[E, Z] =
final def foldM[E, Z](acc: Z)(op: (Z, A) => STM[E, Z]): STM[E, Z] =
if (array.isEmpty) STM.succeed(acc)
else
for {
Expand All @@ -51,44 +51,44 @@ class TArray[A] private (val array: Array[TRef[A]]) extends AnyVal {
} yield res

/** Atomically performs side-effect for each item in array */
def foreach[E](f: A => STM[E, Unit]): STM[E, Unit] =
final def foreach[E](f: A => STM[E, Unit]): STM[E, Unit] =
this.foldM(())((_, a) => f(a))

/** Creates [[TArray]] of new [[TRef]]s, mapped with pure function. */
def map[B](f: A => B): STM[Nothing, TArray[B]] =
final def map[B](f: A => B): STM[Nothing, TArray[B]] =
this.mapM(f andThen STM.succeed)

/** Creates [[TArray]] of new [[TRef]]s, mapped with transactional effect. */
def mapM[E, B](f: A => STM[E, B]): STM[E, TArray[B]] =
final def mapM[E, B](f: A => STM[E, B]): STM[E, TArray[B]] =
STM.foreach(array)(_.get.flatMap(f).flatMap(b => TRef.make(b))).map(l => new TArray(l.toArray))

/** Atomically updates all [[TRef]]s inside this array using pure function. */
def transform(f: A => A): STM[Nothing, Unit] =
final def transform(f: A => A): STM[Nothing, Unit] =
(0 to array.size - 1).foldLeft(STM.succeed(())) {
case (tx, idx) => array(idx).update(f) *> tx
}

/** Atomically updates all elements using transactional effect. */
def transformM[E](f: A => STM[E, A]): STM[E, Unit] =
final def transformM[E](f: A => STM[E, A]): STM[E, Unit] =
(0 to array.size - 1).foldLeft[STM[E, Unit]](STM.succeed(())) {
case (tx, idx) =>
val ref = array(idx)
ref.get.flatMap(f).flatMap(a => ref.set(a)).flatMap(_ => tx)
}

/** Updates element in the array with given function. None signals index out of bounds. */
def update(index: Int, fn: A => A): STM[Nothing, Option[A]] =
if (0 <= index && index < array.size) array(index).update(fn).map(Some(_))
else STM.succeed(None)
/** Updates element in the array with given function. */
final def update(index: Int, fn: A => A): STM[ArrayIndexOutOfBoundsException, A] =
if (0 <= index && index < array.size) array(index).update(fn)
else STM.fail(new ArrayIndexOutOfBoundsException(index))

/** Atomically updates element in the array with given transactionall effect. None signals index out of bounds. */
def updateM[E](index: Int, fn: A => STM[E, A]): STM[E, Option[A]] =
if (0 <= index && index < array.size) array(index).get.flatMap(fn).map(Some(_))
else STM.succeed(None)
/** Atomically updates element in the array with given transactionall effect. */
final def updateM[E](index: Int, fn: A => STM[E, A]): STM[Either[E, ArrayIndexOutOfBoundsException], A] =
if (0 <= index && index < array.size) array(index).get.flatMap(fn).mapError(Left(_))
else STM.fail(Right(new ArrayIndexOutOfBoundsException(index)))
}

object TArray {

def apply[A](array: Array[TRef[A]]): TArray[A] = new TArray(array)
final def apply[A](array: Array[TRef[A]]): TArray[A] = new TArray(array)

}

0 comments on commit 97a7d65

Please sign in to comment.