From a0bc1ae20a703e208977853d250f8db0c056c9d5 Mon Sep 17 00:00:00 2001 From: sinanspd Date: Mon, 26 Oct 2020 17:51:50 -0400 Subject: [PATCH 1/3] tap each for Future and Try --- .../collection/next/FutureExtensions.scala | 44 ++++++++++++ .../scala/scala/util/next/TryExtensions.scala | 51 ++++++++++++++ .../next/FutureExtensionsTest.scala | 68 +++++++++++++++++++ .../scala/util/next/TryExtensionsTest.scala | 56 +++++++++++++++ 4 files changed, 219 insertions(+) create mode 100644 src/main/scala/scala/collection/next/FutureExtensions.scala create mode 100644 src/main/scala/scala/util/next/TryExtensions.scala create mode 100644 src/test/scala/scala/collection/next/FutureExtensionsTest.scala create mode 100644 src/test/scala/scala/util/next/TryExtensionsTest.scala diff --git a/src/main/scala/scala/collection/next/FutureExtensions.scala b/src/main/scala/scala/collection/next/FutureExtensions.scala new file mode 100644 index 0000000..a268c9f --- /dev/null +++ b/src/main/scala/scala/collection/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.collection.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/collection/next/FutureExtensionsTest.scala b/src/test/scala/scala/collection/next/FutureExtensionsTest.scala new file mode 100644 index 0000000..8594d99 --- /dev/null +++ b/src/test/scala/scala/collection/next/FutureExtensionsTest.scala @@ -0,0 +1,68 @@ +/* + * 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.collection.next + +import org.junit.Assert._ +import org.junit.Test +import scala.concurrent.{Await, Future} +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.duration._ + +final class FutureExtensionsTest{ + + import FutureExtensions._ + + @Test + def futureSuccessTapEachTest(): Unit = { + + val input: Future[List[Option[Int]]] = Future.successful(List(Some(1), None, Some(2), Some(3), Some(4))) + val expected: Future[List[Int]] = Future.successful(List(3, 4, 17)) + + var num = 0 + + def tapAndMutate(t: Future[List[Option[Int]]]) ={ + input.map( + _.flatten + .tapEach(a => {num = num + a}) + .filter(_ > 2) + ) + .tapEach(a => {num = num + a.sum}) + .map(_ :+ num) + } + + val awaited = Await.ready(tapAndMutate(input), 5.second) + + assertEquals(expected, awaited) + } + + /* + @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 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 From e00d5333276360a5bae822eb1178634c948a1654 Mon Sep 17 00:00:00 2001 From: sinanspd Date: Fri, 20 Nov 2020 14:51:38 -0500 Subject: [PATCH 2/3] most recent --- .../next/FutureExtensionsTest.scala | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/test/scala/scala/collection/next/FutureExtensionsTest.scala b/src/test/scala/scala/collection/next/FutureExtensionsTest.scala index 8594d99..8bc1ae4 100644 --- a/src/test/scala/scala/collection/next/FutureExtensionsTest.scala +++ b/src/test/scala/scala/collection/next/FutureExtensionsTest.scala @@ -16,7 +16,10 @@ import org.junit.Assert._ import org.junit.Test import scala.concurrent.{Await, Future} import scala.concurrent.ExecutionContext.Implicits.global +//import scala.concurrent.ExecutionContext._ import scala.concurrent.duration._ +import scala.concurrent.ExecutionContext +import scala.concurrent.Promise final class FutureExtensionsTest{ @@ -25,8 +28,10 @@ final class FutureExtensionsTest{ @Test def futureSuccessTapEachTest(): Unit = { - val input: Future[List[Option[Int]]] = Future.successful(List(Some(1), None, Some(2), Some(3), Some(4))) + implicit val ec = ExecutionContext.parasitic + val input: Future[List[Option[Int]]] = Future(List(Some(1), None, Some(2), Some(3), Some(4))) val expected: Future[List[Int]] = Future.successful(List(3, 4, 17)) + //val expected: List[Int] = List(3, 4, 17) var num = 0 @@ -40,9 +45,21 @@ final class FutureExtensionsTest{ .map(_ :+ num) } - val awaited = Await.ready(tapAndMutate(input), 5.second) + val awaited = Await.result(tapAndMutate(input), 5.second) + val awaited2 = Await.result(expected, 5.second) - assertEquals(expected, awaited) + /* val p = Promise[String] + val q = Promise[String] + val res = Promise[String] + val s = "hi" + p.future.onComplete(t => res.complete(t)) + q.future.onComplete(t => res.complete(t)) // previously, uncompleted promise held reference to promise completed with value + assertNotReachable(s, q) { + p.complete(Try(s)) + } + */ + + assertEquals(awaited2, awaited) } /* From 0749f4195a24cd1075efee7fd40e0adc92112550 Mon Sep 17 00:00:00 2001 From: sinanspd Date: Fri, 20 Nov 2020 14:55:30 -0500 Subject: [PATCH 3/3] rename --- .../next/FutureExtensions.scala | 2 +- .../next/FutureExtensionsTest.scala | 85 ------------------- 2 files changed, 1 insertion(+), 86 deletions(-) rename src/main/scala/scala/{collection => concurrent}/next/FutureExtensions.scala (97%) delete mode 100644 src/test/scala/scala/collection/next/FutureExtensionsTest.scala diff --git a/src/main/scala/scala/collection/next/FutureExtensions.scala b/src/main/scala/scala/concurrent/next/FutureExtensions.scala similarity index 97% rename from src/main/scala/scala/collection/next/FutureExtensions.scala rename to src/main/scala/scala/concurrent/next/FutureExtensions.scala index a268c9f..4122976 100644 --- a/src/main/scala/scala/collection/next/FutureExtensions.scala +++ b/src/main/scala/scala/concurrent/next/FutureExtensions.scala @@ -10,7 +10,7 @@ * additional information regarding copyright ownership. */ -package scala.collection.next +package scala.concurrent.next import scala.concurrent.{Future, ExecutionContext} import scala.util.next.TryExtensions._ diff --git a/src/test/scala/scala/collection/next/FutureExtensionsTest.scala b/src/test/scala/scala/collection/next/FutureExtensionsTest.scala deleted file mode 100644 index 8bc1ae4..0000000 --- a/src/test/scala/scala/collection/next/FutureExtensionsTest.scala +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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.collection.next - -import org.junit.Assert._ -import org.junit.Test -import scala.concurrent.{Await, Future} -import scala.concurrent.ExecutionContext.Implicits.global -//import scala.concurrent.ExecutionContext._ -import scala.concurrent.duration._ -import scala.concurrent.ExecutionContext -import scala.concurrent.Promise - -final class FutureExtensionsTest{ - - import FutureExtensions._ - - @Test - def futureSuccessTapEachTest(): Unit = { - - implicit val ec = ExecutionContext.parasitic - val input: Future[List[Option[Int]]] = Future(List(Some(1), None, Some(2), Some(3), Some(4))) - val expected: Future[List[Int]] = Future.successful(List(3, 4, 17)) - //val expected: List[Int] = List(3, 4, 17) - - var num = 0 - - def tapAndMutate(t: Future[List[Option[Int]]]) ={ - input.map( - _.flatten - .tapEach(a => {num = num + a}) - .filter(_ > 2) - ) - .tapEach(a => {num = num + a.sum}) - .map(_ :+ num) - } - - val awaited = Await.result(tapAndMutate(input), 5.second) - val awaited2 = Await.result(expected, 5.second) - - /* val p = Promise[String] - val q = Promise[String] - val res = Promise[String] - val s = "hi" - p.future.onComplete(t => res.complete(t)) - q.future.onComplete(t => res.complete(t)) // previously, uncompleted promise held reference to promise completed with value - assertNotReachable(s, q) { - p.complete(Try(s)) - } - */ - - assertEquals(awaited2, awaited) - } - - /* - @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