diff --git a/src/main/scala/scala/concurrent/next/FutureExtensions.scala b/src/main/scala/scala/concurrent/next/FutureExtensions.scala new file mode 100644 index 0000000..4122976 --- /dev/null +++ b/src/main/scala/scala/concurrent/next/FutureExtensions.scala @@ -0,0 +1,44 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.concurrent.next + +import scala.concurrent.{Future, ExecutionContext} +import scala.util.next.TryExtensions._ + +object FutureExtensions{ + implicit class FutureExt[T](ft: Future[T]){ + + /** Applies a side-effecting function to the encapsulated value in this Future + * + * Calling `tapEach` on `Failure` will not execute `f` + * + * If the side-effecting function fails, the exception will be + * propagated as a `Failure` and the current value of + * `Success` will be discarded. For example the following will result + * in a `RuntimeException` and will not reach `map` + * + * {{{ + * val f : Future[Int] = Future.successful(5) + * + * f + * .tapEach(throw new RuntimeException("runtime exception")) + * .map(_ + 1) + * }}} + * + * @param f a function to apply to each element in this Future + * @tparam U the return type of f + * @return The same Future as this + */ + def tapEach[U](f: T => U)(implicit executor: ExecutionContext): Future[T] = ft.transform(_ tapEach f) + } +} \ No newline at end of file diff --git a/src/main/scala/scala/util/next/TryExtensions.scala b/src/main/scala/scala/util/next/TryExtensions.scala new file mode 100644 index 0000000..3c53503 --- /dev/null +++ b/src/main/scala/scala/util/next/TryExtensions.scala @@ -0,0 +1,51 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.util.next + +import scala.util.{Try, Failure, Success} +import scala.util.control.NonFatal + +object TryExtensions{ + + implicit class TryExtensions[T](t: Try[T]) { + + /** Applies a side-effecting function to the encapsulated value in this Try + * + * Calling `tapEach` on `Failure` will not execute `f` + * + * If the side-effecting function fails, the exception will be + * propagated as a `Failure` and the current value of + * `Success` will be discarded. For example the following will result + * in a `RuntimeException` and will not reach `map` + * + * {{{ + * val t : Try[Int] = Success(5) + * + * t.tapEach(throw new RuntimeException("runtime exception")).map(_ + 1) + * }}} + * + * @param f a function to apply to each element in this Future + * @tparam U the return type of f + * @return The same Try as this + */ + def tapEach[U](f: T => U) = t match{ + case _ : Failure[T] => t.asInstanceOf[Try[T]] + case _ : Success[T] => try { + f(t.get) + t + } catch{ + case NonFatal(e) => Failure[T](e) + } + } + } +} \ No newline at end of file diff --git a/src/test/scala/scala/util/next/TryExtensionsTest.scala b/src/test/scala/scala/util/next/TryExtensionsTest.scala new file mode 100644 index 0000000..a4c7240 --- /dev/null +++ b/src/test/scala/scala/util/next/TryExtensionsTest.scala @@ -0,0 +1,56 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.util.next + +import org.junit.Assert._ +import org.junit.Test +import scala.util.{Try, Success, Failure} + + +final class TryExtensionsTest{ + + import TryExtensions._ + + @Test + def trySuccessTapEachTest(): Unit = { + + val succInt: Try[Int] = Success[Int](5) + var num = 3 + + def tapAndMutate(t: Try[Int]) ={ + t.tapEach(a => {num = 5} ).map(_ + num) + } + + assertEquals(Success(10), tapAndMutate(succInt)) + } + + @Test + def tryFailureTapEachTest(): Unit = { + val failInt: Try[Int] = Failure[Int](new RuntimeException("run time exception")) + var num = 3 + failInt.tapEach(a => {num = 5}) + assertEquals(3, num) + } + + + @Test + def tryFailingSideEffectTapEachTest() : Unit = { + val succInt: Try[Int] = Success[Int](5) + val e = new RuntimeException("run time exception") + val newSucc: Try[Int] = succInt + .tapEach(_ => throw e) + .map(_ + 1) + + assertEquals(Failure(e), newSucc) + } +} \ No newline at end of file