From f003314317c285f8a296c2b5879e1db0c351a3cc Mon Sep 17 00:00:00 2001 From: "P. Oscar Boykin" Date: Sat, 28 Apr 2018 00:00:09 -1000 Subject: [PATCH] Add a Task.tailRecM for stack safe loops (#846) --- .../src/main/scala/coursier/util/Task.scala | 13 +++++++++++ .../test/scala/coursier/util/TaskTests.scala | 22 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 tests/shared/src/test/scala/coursier/util/TaskTests.scala diff --git a/cache/shared/src/main/scala/coursier/util/Task.scala b/cache/shared/src/main/scala/coursier/util/Task.scala index 6082fa3d0..fbd325b2b 100644 --- a/cache/shared/src/main/scala/coursier/util/Task.scala +++ b/cache/shared/src/main/scala/coursier/util/Task.scala @@ -29,5 +29,18 @@ object Task extends PlatformTask { def never[A]: Task[A] = Task(_ => Promise[A].future) + def tailRecM[A, B](a: A)(fn: A => Task[Either[A, B]]): Task[B] = + Task[B] { implicit ec => + def loop(a: A): Future[B] = + fn(a).future().flatMap { + case Right(b) => + Future.successful(b) + case Left(a) => + // this is safe because recursive + // flatMap is safe on Future + loop(a) + } + loop(a) + } } diff --git a/tests/shared/src/test/scala/coursier/util/TaskTests.scala b/tests/shared/src/test/scala/coursier/util/TaskTests.scala new file mode 100644 index 000000000..a1adcd965 --- /dev/null +++ b/tests/shared/src/test/scala/coursier/util/TaskTests.scala @@ -0,0 +1,22 @@ +package coursier.util + +import utest._ + +import scala.concurrent.{Await, ExecutionContext} +import scala.concurrent.duration.Duration + +object TaskTests extends TestSuite { + + val tests = Tests { + 'tailRecM { + import ExecutionContext.Implicits.global + + def countTo(i: Int): Task[Int] = + Task.tailRecM(0) { + case x if x >= i => Task.delay(Right(i)) + case toosmall => Task.delay(Left(toosmall + 1)) + } + countTo(500000).map(_ == 500000).future().map(assert(_)) + } + } +}