From 750de9e1602818849b8c59990b63daa1171f812f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joa=CC=83o=20Costa?= Date: Sun, 28 Feb 2021 00:31:06 +0000 Subject: [PATCH 1/5] Add an AsyncIO to simplify the polling interface --- .../eu/joaocosta/minart/pure/AsyncIO.scala | 72 +++++++++++++++++++ .../eu/joaocosta/minart/pure/CanvasIO.scala | 3 - .../scala/eu/joaocosta/minart/pure/RIO.scala | 3 - .../joaocosta/minart/pure/AsyncIOSpec.scala | 56 +++++++++++++++ .../eu/joaocosta/minart/pure/RIOSpec.scala | 8 --- 5 files changed, 128 insertions(+), 14 deletions(-) create mode 100644 pure/shared/src/main/scala/eu/joaocosta/minart/pure/AsyncIO.scala create mode 100644 pure/shared/src/test/scala/eu/joaocosta/minart/pure/AsyncIOSpec.scala diff --git a/pure/shared/src/main/scala/eu/joaocosta/minart/pure/AsyncIO.scala b/pure/shared/src/main/scala/eu/joaocosta/minart/pure/AsyncIO.scala new file mode 100644 index 00000000..fae07a10 --- /dev/null +++ b/pure/shared/src/main/scala/eu/joaocosta/minart/pure/AsyncIO.scala @@ -0,0 +1,72 @@ +package eu.joaocosta.minart.pure + +import scala.concurrent.Future +import scala.util.{Try, Failure, Success} + +import eu.joaocosta.minart.core._ + +/** Representation of an asyncronous operation that can be polled. + * + * Note that, unlike Futures, operations on an AsyncIO don't require an execution context and + * might be applied sequentially. This is by design, to simplify multiplatform development. + */ +case class AsyncIO[+A](poll: RIO[Any, Option[Try[A]]]) extends AnyVal { + + /** Transforms the result of this operation. */ + def transform[B](f: Try[A] => Try[B]): AsyncIO[B] = + AsyncIO[B](poll.map(_.map(f))) + + /** Maps the result of this operation. */ + def map[B](f: A => B): AsyncIO[B] = + this.transform(_.map(f)) + + /** Combines two operations by applying a function to the result of the first operation. */ + def flatMap[B](f: A => AsyncIO[B]): AsyncIO[B] = AsyncIO[B]( + poll.flatMap { + case Some(Success(x)) => f(x).poll + case Some(Failure(t)) => RIO.pure(Some(Failure(t))) + case None => RIO.pure(None) + } + ) + + /** Combines two operations by combining their results with the given function. */ + def zipWith[B, C](that: AsyncIO[B])(f: (A, B) => C): AsyncIO[C] = this.flatMap(x => that.map(y => f(x, y))) + + /** Combines two operations by combining their results into a tuple. */ + def zip[B](that: AsyncIO[B]): AsyncIO[(A, B)] = this.zipWith(that)((x, y) => x -> y) + + /** Changes the result of this operation to another value. */ + def as[B](x: B): AsyncIO[B] = this.map(_ => x) + + /** Changes the result of this operation unit. */ + def unit: AsyncIO[Unit] = this.as(()) +} + +object AsyncIO { + + /** Lifts a value into a successful [[AsyncIO]]. */ + def successful[A](x: A): AsyncIO[A] = AsyncIO(RIO.pure(Some(Success(x)))) + + /** Lifts a value into a failed [[AsyncIO]]. */ + def failed(t: Throwable): AsyncIO[Nothing] = AsyncIO(RIO.pure(Some(Failure(t)))) + + /** Creates an [[AsyncIO]] that never returns a value. */ + val never: AsyncIO[Nothing] = AsyncIO(RIO.pure(None)) + + /** Builds an [[AsyncIO]] from a running future */ + def fromFuture[A](future: Future[A]): AsyncIO[A] = + AsyncIO(RIO.suspend(future.value)) + + /** Converts an `Iterable[AsyncIO[A]]` into a `AsyncIO[List[A]]`. */ + def sequence[A](it: Iterable[AsyncIO[A]]): AsyncIO[List[A]] = + it.foldLeft[AsyncIO[List[A]]](AsyncIO.successful(Nil)) { case (acc, next) => + acc.zipWith(next) { case (xs, x) => x :: xs } + }.map(_.reverse) + + /** Converts an `Iterable[A]` into a `AsyncIO[List[B]]` by applying an operation to each element. */ + def traverse[A, B](it: Iterable[A])(f: A => AsyncIO[B]): AsyncIO[List[B]] = + it.foldLeft[AsyncIO[List[B]]](AsyncIO.successful(Nil)) { case (acc, next) => + acc.zipWith(f(next)) { case (xs, x) => x :: xs } + }.map(_.reverse) + +} diff --git a/pure/shared/src/main/scala/eu/joaocosta/minart/pure/CanvasIO.scala b/pure/shared/src/main/scala/eu/joaocosta/minart/pure/CanvasIO.scala index e25e1f16..907664c8 100644 --- a/pure/shared/src/main/scala/eu/joaocosta/minart/pure/CanvasIO.scala +++ b/pure/shared/src/main/scala/eu/joaocosta/minart/pure/CanvasIO.scala @@ -21,9 +21,6 @@ object CanvasIO { /** Store an unsafe canvas operation in a [[CanvasIO]]. */ def accessCanvas[A](f: Canvas => A): CanvasIO[A] = RIO.access[Canvas, A](f) - /** Polls a future and optionally returns its result. */ - def pollFuture[A](future: Future[A]): CanvasIO[Option[Try[A]]] = RIO.pollFuture(future) - /** Fetches the canvas settings. */ val getSettings: CanvasIO[Canvas.Settings] = accessCanvas(_.settings) diff --git a/pure/shared/src/main/scala/eu/joaocosta/minart/pure/RIO.scala b/pure/shared/src/main/scala/eu/joaocosta/minart/pure/RIO.scala index eb8eecd2..168eb1cd 100644 --- a/pure/shared/src/main/scala/eu/joaocosta/minart/pure/RIO.scala +++ b/pure/shared/src/main/scala/eu/joaocosta/minart/pure/RIO.scala @@ -65,9 +65,6 @@ object RIO { /** Returns a operation that requires some resource. */ def access[R, A](f: R => A): RIO[R, A] = Suspend[R, A](f) - /** Polls a future and optionally returns its result. */ - def pollFuture[A](future: Future[A]): RIO[Any, Option[Try[A]]] = Suspend[Any, Option[Try[A]]](_ => future.value) - /** Converts an `Iterable[RIO[R, A]]` into a `RIO[R, List[A]]`. */ def sequence[R, A](it: Iterable[RIO[R, A]]): RIO[R, List[A]] = access(res => it.map(_.run(res)).toList) diff --git a/pure/shared/src/test/scala/eu/joaocosta/minart/pure/AsyncIOSpec.scala b/pure/shared/src/test/scala/eu/joaocosta/minart/pure/AsyncIOSpec.scala new file mode 100644 index 00000000..f86ffab4 --- /dev/null +++ b/pure/shared/src/test/scala/eu/joaocosta/minart/pure/AsyncIOSpec.scala @@ -0,0 +1,56 @@ +package eu.joaocosta.minart.pure + +import scala.concurrent._ +import scala.util.{Success, Failure} + +import verify._ + +import eu.joaocosta.minart.core._ + + +object AsyncSpec extends BasicTestSuite { + test("store pure results") { + val error = new Exception("error") + assert(AsyncIO.successful(1).poll.run(()) == Some(Success(1))) + assert(AsyncIO.failed(error).poll.run(()) == Some(Failure(error))) + assert(AsyncIO.never.poll.run(()) == None) + } + + test("allow polling Futures") { + val promise = Promise[Int]() + val io = AsyncIO.fromFuture(promise.future) + assert(io.poll.run(()) == None) + promise.complete(scala.util.Success(0)) + assert(io.poll.run(()) == Some(Success(0))) + } + + test("provide a stack-safe transform operation") { + val io = (1 to 1000).foldLeft[AsyncIO[Int]](AsyncIO.failed(new Exception("error"))) { case (acc, _) => + acc.transform(_.recover(_ => 0).map(_ + 1)) + } + assert(io.poll.run(()) == Some(Success(1000))) + } + + test("provide a stack-safe map operation") { + val io = (1 to 1000).foldLeft[AsyncIO[Int]](AsyncIO.successful(0)) { case (acc, _) => acc.map(_ + 1) } + assert(io.poll.run(()) == Some(Success(1000))) + } + + test("provide a stack-safe flatMap operation") { + val io = (1 to 1000).foldLeft[AsyncIO[Int]](AsyncIO.successful(0)) { case (acc, _) => + acc.flatMap(x => AsyncIO.successful(x + 1)) + } + assert(io.poll.run(()) == Some(Success(1000))) + } + + test("provide zip/zipWith operations") { + assert(AsyncIO.successful(1).zip(AsyncIO.successful(2)).poll.run(()) == Some(Success((1, 2)))) + + assert(AsyncIO.successful(1).zipWith(AsyncIO.successful(2))(_ + _).poll.run(()) == Some(Success(3))) + } + + test("correctly sequence operations") { + val io = AsyncIO.sequence(List(AsyncIO.successful(1), AsyncIO.successful(2), AsyncIO.successful(3))) + assert(io.poll.run(()) == Some(Success(List(1, 2, 3)))) + } +} diff --git a/pure/shared/src/test/scala/eu/joaocosta/minart/pure/RIOSpec.scala b/pure/shared/src/test/scala/eu/joaocosta/minart/pure/RIOSpec.scala index 5357db52..c24d2b35 100644 --- a/pure/shared/src/test/scala/eu/joaocosta/minart/pure/RIOSpec.scala +++ b/pure/shared/src/test/scala/eu/joaocosta/minart/pure/RIOSpec.scala @@ -19,14 +19,6 @@ object RIOSpec extends BasicTestSuite { assert(hasRun == true) } - test("allow polling Futures") { - val promise = Promise[Int]() - val io = RIO.pollFuture(promise.future) - assert(io.run(()) == None) - promise.complete(scala.util.Success(0)) - assert(io.run(()) == Some(scala.util.Success(0))) - } - test("provide a stack-safe map operation") { val io = (1 to 1000).foldLeft[RIO[Any, Int]](RIO.pure(0)) { case (acc, _) => acc.map(_ + 1) } assert(io.run(()) == 1000) From 4645a917332533d9959ab4dde2879e89a4b5fed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joa=CC=83o=20Costa?= Date: Sun, 28 Feb 2021 00:46:04 +0000 Subject: [PATCH 2/5] Add a fromCallback operation to AsyncIO --- .../scala/eu/joaocosta/minart/pure/AsyncIO.scala | 8 +++++++- .../eu/joaocosta/minart/pure/AsyncIOSpec.scala | 13 ++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/pure/shared/src/main/scala/eu/joaocosta/minart/pure/AsyncIO.scala b/pure/shared/src/main/scala/eu/joaocosta/minart/pure/AsyncIO.scala index fae07a10..134eb5db 100644 --- a/pure/shared/src/main/scala/eu/joaocosta/minart/pure/AsyncIO.scala +++ b/pure/shared/src/main/scala/eu/joaocosta/minart/pure/AsyncIO.scala @@ -1,6 +1,6 @@ package eu.joaocosta.minart.pure -import scala.concurrent.Future +import scala.concurrent._ import scala.util.{Try, Failure, Success} import eu.joaocosta.minart.core._ @@ -57,6 +57,12 @@ object AsyncIO { def fromFuture[A](future: Future[A]): AsyncIO[A] = AsyncIO(RIO.suspend(future.value)) + /** Builds an [[AsyncIO]] from a function that receives a callback */ + def fromCallback[A](operation: (Try[A] => Unit) => Unit): RIO[Any, AsyncIO[A]] = { + val promise = Promise[A]() + RIO.suspend(operation(promise.complete)).as(AsyncIO.fromFuture(promise.future)) + } + /** Converts an `Iterable[AsyncIO[A]]` into a `AsyncIO[List[A]]`. */ def sequence[A](it: Iterable[AsyncIO[A]]): AsyncIO[List[A]] = it.foldLeft[AsyncIO[List[A]]](AsyncIO.successful(Nil)) { case (acc, next) => diff --git a/pure/shared/src/test/scala/eu/joaocosta/minart/pure/AsyncIOSpec.scala b/pure/shared/src/test/scala/eu/joaocosta/minart/pure/AsyncIOSpec.scala index f86ffab4..6edd1ae0 100644 --- a/pure/shared/src/test/scala/eu/joaocosta/minart/pure/AsyncIOSpec.scala +++ b/pure/shared/src/test/scala/eu/joaocosta/minart/pure/AsyncIOSpec.scala @@ -7,7 +7,6 @@ import verify._ import eu.joaocosta.minart.core._ - object AsyncSpec extends BasicTestSuite { test("store pure results") { val error = new Exception("error") @@ -24,6 +23,18 @@ object AsyncSpec extends BasicTestSuite { assert(io.poll.run(()) == Some(Success(0))) } + test("allow polling async operations") { + var completer: Int => Unit = _ => () + val io = AsyncIO + .fromCallback[Int] { cb => + completer = (x: Int) => cb(Success(x)) + } + .run(()) + assert(io.poll.run(()) == None) + completer(0) + assert(io.poll.run(()) == Some(Success(0))) + } + test("provide a stack-safe transform operation") { val io = (1 to 1000).foldLeft[AsyncIO[Int]](AsyncIO.failed(new Exception("error"))) { case (acc, _) => acc.transform(_.recover(_ => 0).map(_ + 1)) From 93610deb8cf2b9a34b65b837f68de7488e83ac09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joa=CC=83o=20Costa?= Date: Mon, 1 Mar 2021 22:47:55 +0000 Subject: [PATCH 3/5] Rename AsyncIO to Poll and move effectful operations to RIO --- .../eu/joaocosta/minart/pure/AsyncIO.scala | 78 ------------------- .../eu/joaocosta/minart/pure/CanvasIO.scala | 4 + .../scala/eu/joaocosta/minart/pure/Poll.scala | 73 +++++++++++++++++ .../scala/eu/joaocosta/minart/pure/RIO.scala | 6 ++ .../joaocosta/minart/pure/AsyncIOSpec.scala | 67 ---------------- .../eu/joaocosta/minart/pure/PollSpec.scala | 55 +++++++++++++ .../eu/joaocosta/minart/pure/RIOSpec.scala | 12 +++ 7 files changed, 150 insertions(+), 145 deletions(-) delete mode 100644 pure/shared/src/main/scala/eu/joaocosta/minart/pure/AsyncIO.scala create mode 100644 pure/shared/src/main/scala/eu/joaocosta/minart/pure/Poll.scala delete mode 100644 pure/shared/src/test/scala/eu/joaocosta/minart/pure/AsyncIOSpec.scala create mode 100644 pure/shared/src/test/scala/eu/joaocosta/minart/pure/PollSpec.scala diff --git a/pure/shared/src/main/scala/eu/joaocosta/minart/pure/AsyncIO.scala b/pure/shared/src/main/scala/eu/joaocosta/minart/pure/AsyncIO.scala deleted file mode 100644 index 134eb5db..00000000 --- a/pure/shared/src/main/scala/eu/joaocosta/minart/pure/AsyncIO.scala +++ /dev/null @@ -1,78 +0,0 @@ -package eu.joaocosta.minart.pure - -import scala.concurrent._ -import scala.util.{Try, Failure, Success} - -import eu.joaocosta.minart.core._ - -/** Representation of an asyncronous operation that can be polled. - * - * Note that, unlike Futures, operations on an AsyncIO don't require an execution context and - * might be applied sequentially. This is by design, to simplify multiplatform development. - */ -case class AsyncIO[+A](poll: RIO[Any, Option[Try[A]]]) extends AnyVal { - - /** Transforms the result of this operation. */ - def transform[B](f: Try[A] => Try[B]): AsyncIO[B] = - AsyncIO[B](poll.map(_.map(f))) - - /** Maps the result of this operation. */ - def map[B](f: A => B): AsyncIO[B] = - this.transform(_.map(f)) - - /** Combines two operations by applying a function to the result of the first operation. */ - def flatMap[B](f: A => AsyncIO[B]): AsyncIO[B] = AsyncIO[B]( - poll.flatMap { - case Some(Success(x)) => f(x).poll - case Some(Failure(t)) => RIO.pure(Some(Failure(t))) - case None => RIO.pure(None) - } - ) - - /** Combines two operations by combining their results with the given function. */ - def zipWith[B, C](that: AsyncIO[B])(f: (A, B) => C): AsyncIO[C] = this.flatMap(x => that.map(y => f(x, y))) - - /** Combines two operations by combining their results into a tuple. */ - def zip[B](that: AsyncIO[B]): AsyncIO[(A, B)] = this.zipWith(that)((x, y) => x -> y) - - /** Changes the result of this operation to another value. */ - def as[B](x: B): AsyncIO[B] = this.map(_ => x) - - /** Changes the result of this operation unit. */ - def unit: AsyncIO[Unit] = this.as(()) -} - -object AsyncIO { - - /** Lifts a value into a successful [[AsyncIO]]. */ - def successful[A](x: A): AsyncIO[A] = AsyncIO(RIO.pure(Some(Success(x)))) - - /** Lifts a value into a failed [[AsyncIO]]. */ - def failed(t: Throwable): AsyncIO[Nothing] = AsyncIO(RIO.pure(Some(Failure(t)))) - - /** Creates an [[AsyncIO]] that never returns a value. */ - val never: AsyncIO[Nothing] = AsyncIO(RIO.pure(None)) - - /** Builds an [[AsyncIO]] from a running future */ - def fromFuture[A](future: Future[A]): AsyncIO[A] = - AsyncIO(RIO.suspend(future.value)) - - /** Builds an [[AsyncIO]] from a function that receives a callback */ - def fromCallback[A](operation: (Try[A] => Unit) => Unit): RIO[Any, AsyncIO[A]] = { - val promise = Promise[A]() - RIO.suspend(operation(promise.complete)).as(AsyncIO.fromFuture(promise.future)) - } - - /** Converts an `Iterable[AsyncIO[A]]` into a `AsyncIO[List[A]]`. */ - def sequence[A](it: Iterable[AsyncIO[A]]): AsyncIO[List[A]] = - it.foldLeft[AsyncIO[List[A]]](AsyncIO.successful(Nil)) { case (acc, next) => - acc.zipWith(next) { case (xs, x) => x :: xs } - }.map(_.reverse) - - /** Converts an `Iterable[A]` into a `AsyncIO[List[B]]` by applying an operation to each element. */ - def traverse[A, B](it: Iterable[A])(f: A => AsyncIO[B]): AsyncIO[List[B]] = - it.foldLeft[AsyncIO[List[B]]](AsyncIO.successful(Nil)) { case (acc, next) => - acc.zipWith(f(next)) { case (xs, x) => x :: xs } - }.map(_.reverse) - -} diff --git a/pure/shared/src/main/scala/eu/joaocosta/minart/pure/CanvasIO.scala b/pure/shared/src/main/scala/eu/joaocosta/minart/pure/CanvasIO.scala index 907664c8..b8d0394f 100644 --- a/pure/shared/src/main/scala/eu/joaocosta/minart/pure/CanvasIO.scala +++ b/pure/shared/src/main/scala/eu/joaocosta/minart/pure/CanvasIO.scala @@ -21,6 +21,10 @@ object CanvasIO { /** Store an unsafe canvas operation in a [[CanvasIO]]. */ def accessCanvas[A](f: Canvas => A): CanvasIO[A] = RIO.access[Canvas, A](f) + /** Returns a [[Poll]] from a function that receives a callback */ + def fromCallback[A](operation: (Try[A] => Unit) => Unit): CanvasIO[Poll[A]] = + RIO.fromCallback(operation) + /** Fetches the canvas settings. */ val getSettings: CanvasIO[Canvas.Settings] = accessCanvas(_.settings) diff --git a/pure/shared/src/main/scala/eu/joaocosta/minart/pure/Poll.scala b/pure/shared/src/main/scala/eu/joaocosta/minart/pure/Poll.scala new file mode 100644 index 00000000..d898aa4e --- /dev/null +++ b/pure/shared/src/main/scala/eu/joaocosta/minart/pure/Poll.scala @@ -0,0 +1,73 @@ +package eu.joaocosta.minart.pure + +import scala.concurrent._ +import scala.util.{Try, Failure, Success} + +import eu.joaocosta.minart.core._ + +/** Representation of a running asyncronous computation that can be polled. + * + * Note that, unlike Futures, operations on an Poll don't require an execution context and + * might be applied sequentially every time `poll` is called. + * While this might be inneficient, this is by design, to simplify multiplatform development. + */ +case class Poll[+A](poll: RIO[Any, Option[Try[A]]]) extends AnyVal { + + /** Transforms the result of this operation. */ + def transform[B](f: Try[A] => Try[B]): Poll[B] = + Poll[B](poll.map(_.map(f))) + + /** Maps the result of this operation. */ + def map[B](f: A => B): Poll[B] = + this.transform(_.map(f)) + + /** Combines two operations by applying a function to the result of the first operation. */ + def flatMap[B](f: A => Poll[B]): Poll[B] = Poll[B]( + poll.flatMap { + case Some(Success(x)) => f(x).poll + case Some(Failure(t)) => RIO.pure(Some(Failure(t))) + case None => RIO.pure(None) + } + ) + + /** Combines two operations by combining their results with the given function. */ + def zipWith[B, C](that: Poll[B])(f: (A, B) => C): Poll[C] = this.flatMap(x => that.map(y => f(x, y))) + + /** Combines two operations by combining their results into a tuple. */ + def zip[B](that: Poll[B]): Poll[(A, B)] = this.zipWith(that)((x, y) => x -> y) + + /** Changes the result of this operation to another value. */ + def as[B](x: B): Poll[B] = this.map(_ => x) + + /** Changes the result of this operation unit. */ + def unit: Poll[Unit] = this.as(()) +} + +object Poll { + + /** Lifts a value into a successful [[Poll]]. */ + def successful[A](x: A): Poll[A] = Poll(RIO.pure(Some(Success(x)))) + + /** Lifts a value into a failed [[Poll]]. */ + def failed(t: Throwable): Poll[Nothing] = Poll(RIO.pure(Some(Failure(t)))) + + /** Creates an [[Poll]] that never returns a value. */ + val never: Poll[Nothing] = Poll(RIO.pure(None)) + + /** Builds an [[Poll]] from a running future */ + def fromFuture[A](future: Future[A]): Poll[A] = + Poll(RIO.suspend(future.value)) + + /** Converts an `Iterable[Poll[A]]` into a `Poll[List[A]]`. */ + def sequence[A](it: Iterable[Poll[A]]): Poll[List[A]] = + it.foldLeft[Poll[List[A]]](Poll.successful(Nil)) { case (acc, next) => + acc.zipWith(next) { case (xs, x) => x :: xs } + }.map(_.reverse) + + /** Converts an `Iterable[A]` into a `Poll[List[B]]` by applying an operation to each element. */ + def traverse[A, B](it: Iterable[A])(f: A => Poll[B]): Poll[List[B]] = + it.foldLeft[Poll[List[B]]](Poll.successful(Nil)) { case (acc, next) => + acc.zipWith(f(next)) { case (xs, x) => x :: xs } + }.map(_.reverse) + +} diff --git a/pure/shared/src/main/scala/eu/joaocosta/minart/pure/RIO.scala b/pure/shared/src/main/scala/eu/joaocosta/minart/pure/RIO.scala index 168eb1cd..2e22dcba 100644 --- a/pure/shared/src/main/scala/eu/joaocosta/minart/pure/RIO.scala +++ b/pure/shared/src/main/scala/eu/joaocosta/minart/pure/RIO.scala @@ -65,6 +65,12 @@ object RIO { /** Returns a operation that requires some resource. */ def access[R, A](f: R => A): RIO[R, A] = Suspend[R, A](f) + /** Returns a [[Poll]] from a function that receives a callback */ + def fromCallback[A](operation: (Try[A] => Unit) => Unit): RIO[Any, Poll[A]] = { + val promise = scala.concurrent.Promise[A]() + RIO.suspend(operation(promise.complete)).as(Poll.fromFuture(promise.future)) + } + /** Converts an `Iterable[RIO[R, A]]` into a `RIO[R, List[A]]`. */ def sequence[R, A](it: Iterable[RIO[R, A]]): RIO[R, List[A]] = access(res => it.map(_.run(res)).toList) diff --git a/pure/shared/src/test/scala/eu/joaocosta/minart/pure/AsyncIOSpec.scala b/pure/shared/src/test/scala/eu/joaocosta/minart/pure/AsyncIOSpec.scala deleted file mode 100644 index 6edd1ae0..00000000 --- a/pure/shared/src/test/scala/eu/joaocosta/minart/pure/AsyncIOSpec.scala +++ /dev/null @@ -1,67 +0,0 @@ -package eu.joaocosta.minart.pure - -import scala.concurrent._ -import scala.util.{Success, Failure} - -import verify._ - -import eu.joaocosta.minart.core._ - -object AsyncSpec extends BasicTestSuite { - test("store pure results") { - val error = new Exception("error") - assert(AsyncIO.successful(1).poll.run(()) == Some(Success(1))) - assert(AsyncIO.failed(error).poll.run(()) == Some(Failure(error))) - assert(AsyncIO.never.poll.run(()) == None) - } - - test("allow polling Futures") { - val promise = Promise[Int]() - val io = AsyncIO.fromFuture(promise.future) - assert(io.poll.run(()) == None) - promise.complete(scala.util.Success(0)) - assert(io.poll.run(()) == Some(Success(0))) - } - - test("allow polling async operations") { - var completer: Int => Unit = _ => () - val io = AsyncIO - .fromCallback[Int] { cb => - completer = (x: Int) => cb(Success(x)) - } - .run(()) - assert(io.poll.run(()) == None) - completer(0) - assert(io.poll.run(()) == Some(Success(0))) - } - - test("provide a stack-safe transform operation") { - val io = (1 to 1000).foldLeft[AsyncIO[Int]](AsyncIO.failed(new Exception("error"))) { case (acc, _) => - acc.transform(_.recover(_ => 0).map(_ + 1)) - } - assert(io.poll.run(()) == Some(Success(1000))) - } - - test("provide a stack-safe map operation") { - val io = (1 to 1000).foldLeft[AsyncIO[Int]](AsyncIO.successful(0)) { case (acc, _) => acc.map(_ + 1) } - assert(io.poll.run(()) == Some(Success(1000))) - } - - test("provide a stack-safe flatMap operation") { - val io = (1 to 1000).foldLeft[AsyncIO[Int]](AsyncIO.successful(0)) { case (acc, _) => - acc.flatMap(x => AsyncIO.successful(x + 1)) - } - assert(io.poll.run(()) == Some(Success(1000))) - } - - test("provide zip/zipWith operations") { - assert(AsyncIO.successful(1).zip(AsyncIO.successful(2)).poll.run(()) == Some(Success((1, 2)))) - - assert(AsyncIO.successful(1).zipWith(AsyncIO.successful(2))(_ + _).poll.run(()) == Some(Success(3))) - } - - test("correctly sequence operations") { - val io = AsyncIO.sequence(List(AsyncIO.successful(1), AsyncIO.successful(2), AsyncIO.successful(3))) - assert(io.poll.run(()) == Some(Success(List(1, 2, 3)))) - } -} diff --git a/pure/shared/src/test/scala/eu/joaocosta/minart/pure/PollSpec.scala b/pure/shared/src/test/scala/eu/joaocosta/minart/pure/PollSpec.scala new file mode 100644 index 00000000..202da4a2 --- /dev/null +++ b/pure/shared/src/test/scala/eu/joaocosta/minart/pure/PollSpec.scala @@ -0,0 +1,55 @@ +package eu.joaocosta.minart.pure + +import scala.concurrent._ +import scala.util.{Success, Failure} + +import verify._ + +import eu.joaocosta.minart.core._ + +object PollSpec extends BasicTestSuite { + test("store pure results") { + val error = new Exception("error") + assert(Poll.successful(1).poll.run(()) == Some(Success(1))) + assert(Poll.failed(error).poll.run(()) == Some(Failure(error))) + assert(Poll.never.poll.run(()) == None) + } + + test("allow polling Futures") { + val promise = Promise[Int]() + val io = Poll.fromFuture(promise.future) + assert(io.poll.run(()) == None) + promise.complete(scala.util.Success(0)) + assert(io.poll.run(()) == Some(Success(0))) + } + + test("provide a stack-safe transform operation") { + val io = (1 to 1000).foldLeft[Poll[Int]](Poll.failed(new Exception("error"))) { case (acc, _) => + acc.transform(_.recover(_ => 0).map(_ + 1)) + } + assert(io.poll.run(()) == Some(Success(1000))) + } + + test("provide a stack-safe map operation") { + val io = (1 to 1000).foldLeft[Poll[Int]](Poll.successful(0)) { case (acc, _) => acc.map(_ + 1) } + assert(io.poll.run(()) == Some(Success(1000))) + } + + test("provide a stack-safe flatMap operation") { + val io = (1 to 1000).foldLeft[Poll[Int]](Poll.successful(0)) { case (acc, _) => + acc.flatMap(x => Poll.successful(x + 1)) + } + assert(io.poll.run(()) == Some(Success(1000))) + } + + test("provide zip/zipWith operations") { + assert(Poll.successful(1).zip(Poll.successful(2)).poll.run(()) == Some(Success((1, 2)))) + + assert(Poll.successful(1).zipWith(Poll.successful(2))(_ + _).poll.run(()) == Some(Success(3))) + } + + test("correctly sequence operations") { + val io = Poll.sequence(List(Poll.successful(1), Poll.successful(2), Poll.successful(3))) + assert(io.poll.run(()) == Some(Success(List(1, 2, 3)))) + } +} diff --git a/pure/shared/src/test/scala/eu/joaocosta/minart/pure/RIOSpec.scala b/pure/shared/src/test/scala/eu/joaocosta/minart/pure/RIOSpec.scala index c24d2b35..2e9f0480 100644 --- a/pure/shared/src/test/scala/eu/joaocosta/minart/pure/RIOSpec.scala +++ b/pure/shared/src/test/scala/eu/joaocosta/minart/pure/RIOSpec.scala @@ -19,6 +19,18 @@ object RIOSpec extends BasicTestSuite { assert(hasRun == true) } + test("allow polling async operations") { + var completer: Int => Unit = _ => () + val io = RIO + .fromCallback[Int] { cb => + completer = (x: Int) => cb(scala.util.Success(x)) + } + .run(()) + assert(io.poll.run(()) == None) + completer(0) + assert(io.poll.run(()) == Some(scala.util.Success(0))) + } + test("provide a stack-safe map operation") { val io = (1 to 1000).foldLeft[RIO[Any, Int]](RIO.pure(0)) { case (acc, _) => acc.map(_ + 1) } assert(io.run(()) == 1000) From 607ba7c9d5432325c335be5996946a9abc40ea30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joa=CC=83o=20Costa?= Date: Fri, 5 Mar 2021 22:59:51 +0000 Subject: [PATCH 4/5] Remove stray println --- .../src/main/scala/eu/joaocosta/minart/backend/HtmlCanvas.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/core/js/src/main/scala/eu/joaocosta/minart/backend/HtmlCanvas.scala b/core/js/src/main/scala/eu/joaocosta/minart/backend/HtmlCanvas.scala index 0044a179..9efa1027 100644 --- a/core/js/src/main/scala/eu/joaocosta/minart/backend/HtmlCanvas.scala +++ b/core/js/src/main/scala/eu/joaocosta/minart/backend/HtmlCanvas.scala @@ -38,7 +38,6 @@ class HtmlCanvas() extends LowLevelCanvas { "keydown", (ev: KeyboardEvent) => { JsKeyMapping.getKey(ev.keyCode).foreach(k => keyboardInput = keyboardInput.press(k)) - println("keydown") } ) dom.document.addEventListener[KeyboardEvent]( From 59a8f6809d062e087d6003b9b0aa4404888c031e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joa=CC=83o=20Costa?= Date: Fri, 5 Mar 2021 23:28:56 +0000 Subject: [PATCH 5/5] Fix test compilation errors in scala 2.11 --- .../src/test/scala/eu/joaocosta/minart/pure/PollSpec.scala | 2 +- .../src/test/scala/eu/joaocosta/minart/pure/RIOSpec.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pure/shared/src/test/scala/eu/joaocosta/minart/pure/PollSpec.scala b/pure/shared/src/test/scala/eu/joaocosta/minart/pure/PollSpec.scala index 202da4a2..26dd7083 100644 --- a/pure/shared/src/test/scala/eu/joaocosta/minart/pure/PollSpec.scala +++ b/pure/shared/src/test/scala/eu/joaocosta/minart/pure/PollSpec.scala @@ -25,7 +25,7 @@ object PollSpec extends BasicTestSuite { test("provide a stack-safe transform operation") { val io = (1 to 1000).foldLeft[Poll[Int]](Poll.failed(new Exception("error"))) { case (acc, _) => - acc.transform(_.recover(_ => 0).map(_ + 1)) + acc.transform(_.recover { case _ => 0 }.map(_ + 1)) } assert(io.poll.run(()) == Some(Success(1000))) } diff --git a/pure/shared/src/test/scala/eu/joaocosta/minart/pure/RIOSpec.scala b/pure/shared/src/test/scala/eu/joaocosta/minart/pure/RIOSpec.scala index 2e9f0480..f904a161 100644 --- a/pure/shared/src/test/scala/eu/joaocosta/minart/pure/RIOSpec.scala +++ b/pure/shared/src/test/scala/eu/joaocosta/minart/pure/RIOSpec.scala @@ -20,7 +20,7 @@ object RIOSpec extends BasicTestSuite { } test("allow polling async operations") { - var completer: Int => Unit = _ => () + var completer: Int => Unit = (_) => () val io = RIO .fromCallback[Int] { cb => completer = (x: Int) => cb(scala.util.Success(x))