Skip to content

Commit

Permalink
Rename alt and altAll to or and awaitFirst (#44)
Browse files Browse the repository at this point in the history
* Rename `alt` and `altWithCancel` to `or` and `orWithCancel`

Feels closer to `Option`'s `.orElse`

* Rename `altAll` and `altAllWithCancel` to `awaitFirst`(`WithCancel`)

`altAll` is unintuitive and we don't use the term "alternate" anywhere.
`awaitFirst` seems to be a good alternative.

* Rename in tests respectively
  • Loading branch information
natsukagami authored Mar 5, 2024
1 parent d7053b4 commit 40c3327
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 25 deletions.
16 changes: 8 additions & 8 deletions shared/src/main/scala/async/futures.scala
Original file line number Diff line number Diff line change
Expand Up @@ -200,14 +200,14 @@ object Future:
/** Alternative parallel composition of this task with `other` task. If either task succeeds, succeed with the
* success that was returned first. Otherwise, fail with the failure that was returned last.
*/
def alt(f2: Future[T]): Future[T] = altImpl(false)(f2)
def or(f2: Future[T]): Future[T] = orImpl(false)(f2)

/** Like `alt` but the slower future is cancelled. If either task succeeds, succeed with the success that was
/** Like `or` but the slower future is cancelled. If either task succeeds, succeed with the success that was
* returned first and the other is cancelled. Otherwise, fail with the failure that was returned last.
*/
def altWithCancel(f2: Future[T]): Future[T] = altImpl(true)(f2)
def orWithCancel(f2: Future[T]): Future[T] = orImpl(true)(f2)

inline def altImpl(inline withCancel: Boolean)(f2: Future[T]): Future[T] = Future.withResolver: r =>
inline def orImpl(inline withCancel: Boolean)(f2: Future[T]): Future[T] = Future.withResolver: r =>
Async
.raceWithOrigin(f1, f2)
.onComplete(Listener { case ((v, which), _) =>
Expand Down Expand Up @@ -327,12 +327,12 @@ object Future:

/** Race all futures, returning the first successful value. Throws the last exception received, if everything fails.
*/
def altAll(using Async): T = altImpl(false)
def awaitFirst(using Async): T = awaitFirstImpl(false)

/** Like [[altAll]], but cancels all other futures as soon as the first future succeeds. */
def altAllWithCancel(using Async): T = altImpl(true)
/** Like [[awaitFirst]], but cancels all other futures as soon as the first future succeeds. */
def awaitFirstWithCancel(using Async): T = awaitFirstImpl(true)

private inline def altImpl(withCancel: Boolean)(using Async): T =
private inline def awaitFirstImpl(withCancel: Boolean)(using Async): T =
val collector = Collector(fs*)
@scala.annotation.tailrec
def loop(attempt: Int): T =
Expand Down
32 changes: 16 additions & 16 deletions shared/src/test/scala/FutureBehavior.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ class FutureBehavior extends munit.FunSuite {
assert(false); 1
}
val res = c
.alt(Future {
.or(Future {
val res = a.await + b.await
res
})
.alt(c)
.or(c)
.await
res
val y = Future:
Expand All @@ -51,7 +51,7 @@ class FutureBehavior extends munit.FunSuite {
val b = Future {
true
}
val res = a.alt(b).await
val res = a.or(b).await
res
val _: Future[Int | Boolean] = z
assertEquals(x.await, 33)
Expand Down Expand Up @@ -82,26 +82,26 @@ class FutureBehavior extends munit.FunSuite {
assertEquals(f.await, 55)
}

test("alt") {
test("or") {
Async.blocking:
val error = new AssertionError()
val fail = Future.now(Failure(error))
val fail1 = Future.now(Failure(error))
val succeed = Future.now(Success(13))

assert(Set(10, 20).contains(Future { 10 }.alt(Future { 20 }).await))
assertEquals(fail.alt(succeed).await, 13)
assertEquals(succeed.alt(fail).await, 13)
assertEquals(fail.alt(fail1).awaitResult, Failure(error))
assert(Set(10, 20).contains(Future { 10 }.or(Future { 20 }).await))
assertEquals(fail.or(succeed).await, 13)
assertEquals(succeed.or(fail).await, 13)
assertEquals(fail.or(fail1).awaitResult, Failure(error))
}

test("altC of 2 futures") {
test("orWithCancel of 2 futures") {
Async.blocking:
var touched = 0
Future {
sleep(200)
touched += 1
}.alt(Future {
}.or(Future {
10
}).awaitResult
sleep(300)
Expand All @@ -111,7 +111,7 @@ class FutureBehavior extends munit.FunSuite {
Future {
sleep(200)
touched += 1
}.altWithCancel(Future { 10 }).awaitResult
}.orWithCancel(Future { 10 }).awaitResult
sleep(300)
assertEquals(touched, 0)
}
Expand Down Expand Up @@ -414,23 +414,23 @@ class FutureBehavior extends munit.FunSuite {
assert(!lastFutureFinished)
}

test("future collection: altAll*") {
test("future collection: awaitFirst*") {
Async.blocking:
val range = (0 to 10)
def futs = range.map(i => Future { sleep(i * 100); i })
assert(range contains futs.altAll)
assert(range contains futs.awaitFirst)

val exc = new Exception("a")
def futsWithFail = futs ++ Seq(Future { throw exc })
assert(range contains futsWithFail.altAll)
assert(range contains futsWithFail.awaitFirst)

val excs = range.map(i => new Exception(i.toString()))
def futsAllFail = range.zip(excs).map((i, exc) => Future { sleep(i * 100); throw exc })
assertEquals(Try(futsAllFail.altAll), Failure(excs.last))
assertEquals(Try(futsAllFail.awaitFirst), Failure(excs.last))

var lastFutureFinished = false
def futsWithSleepy = futsWithFail ++ Seq(Future { sleep(200000); lastFutureFinished = true; 0 })
assert(range contains futsWithSleepy.altAll)
assert(range contains futsWithSleepy.awaitFirst)
assert(!lastFutureFinished)
}

Expand Down
2 changes: 1 addition & 1 deletion shared/src/test/scala/SourceBehavior.scala
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ class SourceBehavior extends munit.FunSuite {
assertEquals(bRan.poll(), None)
f.await
Thread.sleep(100) // onComplete of await and manual may be scheduled
aRan.zip(bRan).alt(Future(sleep(600))).await
aRan.zip(bRan).or(Future(sleep(600))).await
}

test("either") {
Expand Down

0 comments on commit 40c3327

Please sign in to comment.