From e7a06153874280c92956af5b82534b6b24caee47 Mon Sep 17 00:00:00 2001 From: pakoito Date: Sun, 22 Jul 2018 17:41:57 +0100 Subject: [PATCH 01/26] Add IO implementation of parMap --- .../src/main/kotlin/arrow/effects/IO.kt | 51 +++++++++++++++++ .../kotlin/arrow/effects/internal/Utils.kt | 56 +++++++++++++++++-- 2 files changed, 102 insertions(+), 5 deletions(-) diff --git a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IO.kt b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IO.kt index bcfd08f3d2d..3bee005b6e2 100644 --- a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IO.kt +++ b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IO.kt @@ -5,10 +5,15 @@ import arrow.core.Either.Left import arrow.effects.internal.Platform.maxStackDepthSize import arrow.effects.internal.Platform.onceOnly import arrow.effects.internal.Platform.unsafeResync +import arrow.effects.internal.asyncIOContinuation +import arrow.effects.internal.parContinuation import arrow.effects.typeclasses.Duration import arrow.effects.typeclasses.Proc import arrow.higherkind +import kotlin.coroutines.experimental.Continuation import kotlin.coroutines.experimental.CoroutineContext +import kotlin.coroutines.experimental.startCoroutine +import kotlin.coroutines.experimental.suspendCoroutine @higherkind sealed class IO : IOOf { @@ -55,6 +60,52 @@ sealed class IO : IOOf { is Either.Right -> IO.just(it.b) } } + + fun parMap(ctx: CoroutineContext, ioA: IO, ioB: IO, f: (A, B) -> C): IO = + IO.async { cc -> + val a: suspend () -> Either = { + suspendCoroutine { ca: Continuation> -> + ioA.map(::Left).unsafeRunAsync { + it.fold(ca::resumeWithException, ca::resume) + } + } + } + val b: suspend () -> Either = { + suspendCoroutine { ca: Continuation> -> + ioB.map(::Right).unsafeRunAsync { + it.fold(ca::resumeWithException, ca::resume) + } + } + } + val parCont = parContinuation(ctx, f, asyncIOContinuation(ctx, cc)) + a.startCoroutine(parCont) + b.startCoroutine(parCont) + } + + fun parMap3(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, f: (A, B, C) -> D): IO = + parMap(ctx, ioA, + parMap(ctx, ioB, ioC, { b, c -> b toT c }), + { a, bc -> f(a, bc.a, bc.b) }) + + fun parMap4(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, f: (A, B, C, D) -> E): IO = + parMap(ctx, + parMap(ctx, ioA, ioB, { a, b -> a toT b }), + parMap(ctx, ioC, ioD, { c, d -> c toT d }), + { ab, cd -> f(ab.a, ab.b, cd.a, cd.b) }) + + fun parMap5(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, f: (A, B, C, D, E) -> F): IO = + parMap(ctx, + parMap4(ctx, ioA, ioB, ioC, ioD, + { a, b, c, d -> Tuple4(a, b, c, d) }), + ioE, + { abcd, e -> f(abcd.a, abcd.b, abcd.c, abcd.d, e) }) + + fun parMap6(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, ioF: IO, f: (A, B, C, D, E, F) -> G): IO = + parMap3(ctx, + parMap(ctx, ioA, ioB, { a, b -> a toT b }), + parMap(ctx, ioC, ioD, { c, d -> c toT d }), + parMap(ctx, ioE, ioF, { e, ff -> e toT ff }), + { ab, cd, ef -> f(ab.a, ab.b, cd.a, cd.b, ef.a, ef.b) }) } abstract fun map(f: (A) -> B): IO diff --git a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/Utils.kt b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/Utils.kt index e61cedba9eb..b161a0787aa 100644 --- a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/Utils.kt +++ b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/Utils.kt @@ -1,14 +1,13 @@ package arrow.effects.internal -import arrow.core.Either -import arrow.core.None -import arrow.core.Option -import arrow.core.Some -import arrow.effects.typeclasses.Duration +import arrow.core.* import arrow.effects.IO +import arrow.effects.typeclasses.Duration import java.util.* import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.locks.AbstractQueuedSynchronizer +import kotlin.coroutines.experimental.Continuation +import kotlin.coroutines.experimental.CoroutineContext object Platform { @@ -77,3 +76,50 @@ private class OneShotLatch : AbstractQueuedSynchronizer() { return true } } + +internal fun parContinuation(ctx: CoroutineContext, f: (A, B) -> C, c: Continuation): Continuation> = + object : Continuation> { + var intermediate: Either? = null + + override val context: CoroutineContext = ctx + + override fun resume(value: Either) = + synchronized(this) { + val result = intermediate + if (null == result) { + intermediate = value + } else { + value.fold({ a -> + result.fold({ + // Resumed twice on the same side + }, { b -> + c.resume(f(a, b)) + }) + }, { b -> + result.fold({ a -> + c.resume(f(a, b)) + }, { + // Resumed twice on the same side + }) + }) + } + } + + override fun resumeWithException(exception: Throwable) { + c.resumeWithException(exception) + } + } + +internal fun asyncIOContinuation(ctx: CoroutineContext, cc: (Either) -> Unit): Continuation { + return object : Continuation { + override val context: CoroutineContext = ctx + + override fun resume(value: A) { + cc(value.right()) + } + + override fun resumeWithException(exception: Throwable) { + cc(exception.left()) + } + } +} From 3deea20d71dd5d096093cfcf75655e22d8d68d96 Mon Sep 17 00:00:00 2001 From: pakoito Date: Sun, 22 Jul 2018 17:53:55 +0100 Subject: [PATCH 02/26] Update policy for resuming on the same side --- infographic/arrow-infographic.txt | 26 +++++++++---------- .../kotlin/arrow/effects/internal/Utils.kt | 6 +++-- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/infographic/arrow-infographic.txt b/infographic/arrow-infographic.txt index dd0dd8845a4..52043dd8849 100644 --- a/infographic/arrow-infographic.txt +++ b/infographic/arrow-infographic.txt @@ -40,22 +40,9 @@ [Eq]<-[Eq Instances|Tuple7EqInstance|Tuple5EqInstance|Tuple2EqInstance|EitherEqInstance|Tuple9EqInstance|Tuple3EqInstance|TryEqInstance|Tuple6EqInstance|ConstEqInstance|IdEqInstance|OptionEqInstance|Tuple10EqInstance|Tuple4EqInstance|Tuple8EqInstance|NonEmptyListEqInstance|ListKEqInstance|SetKEqInstance|IorEqInstance|SequenceKEqInstance|ValidatedEqInstance|MapKEqInstance] [Show]<-[Show Instances|Tuple3ShowInstance|IdShowInstance|OptionShowInstance|Tuple8ShowInstance|ConstShowInstance|EitherShowInstance|Tuple4ShowInstance|TryShowInstance|Tuple2ShowInstance|Tuple9ShowInstance|Tuple6ShowInstance|Tuple5ShowInstance|Tuple10ShowInstance|Tuple7ShowInstance|IorShowInstance|MapKShowInstance|SetKShowInstance|SortedMapKShowInstance|SequenceKShowInstance|ListKShowInstance|ValidatedShowInstance|NonEmptyListShowInstance] [Bimonad]<-[Bimonad Instances|EvalBimonadInstance|Function0BimonadInstance|IdBimonadInstance|NonEmptyListBimonadInstance] -[FilterIndex]<-[FilterIndex Instances|SequenceKFilterIndexInstance|MapKFilterIndexInstance|ListKFilterIndexInstance|NonEmptyListFilterIndexInstance] -[Index]<-[Index Instances|MapKIndexInstance|SequenceKIndexInstance|NonEmptyListIndexInstance|ListKIndexInstance|NonEmptyListFilterIndexInstance|MapKFilterIndexInstance|SequenceKFilterIndexInstance|ListKFilterIndexInstance] -[At]<-[At Instances|MapKAtInstance|SetKAtInstance] -[Each]<-[Each Instances|ListKEachInstance|OptionEachInstance|EitherEachInstance|TryEachInstance|SequenceKEachInstance|NonEmptyListEachInstance|MapKEachInstance] -[Semigroup]<-[Semigroup Instances|ConstSemigroupInstance|OptionSemigroupInstance|SortedMapKSemigroupInstance|ListKSemigroupInstance|MapKSemigroupInstance|SetKSemigroupInstance|SequenceKSemigroupInstance|NonEmptyListSemigroupInstance|IOSemigroupInstance|IOMonoidInstance|EitherSemigroupInstance|TrySemigroupInstance] -[Monoid]<-[Monoid Instances|OptionMonoidInstance|ConstMonoidInstance|Tuple2MonoidInstance|SequenceKMonoidInstance|SortedMapKMonoidInstance|ListKMonoidInstance|MapKMonoidInstance|SetKMonoidInstance|IOMonoidInstance|TryMonoidInstance|EitherMonoidInstance] [Traverse]<-[Traverse Instances|EitherTraverseInstance|OptionTraverseInstance|Tuple2TraverseInstance|TryTraverseInstance|IdTraverseInstance|ConstTraverseInstance|IorTraverseInstance|MapKTraverseInstance|ValidatedTraverseInstance|CoproductTraverseInstance|ListKTraverseInstance|SequenceKTraverseInstance|OptionTTraverseInstance|NonEmptyListTraverseInstance|SortedMapKTraverseInstance|FlowableKTraverseInstance|ObservableKTraverseInstance|FluxKTraverseInstance] [Foldable]<-[Foldable Instances|ConstFoldableInstance|IdFoldableInstance|EitherFoldableInstance|TryFoldableInstance|OptionFoldableInstance|Tuple2FoldableInstance|OptionTFoldableInstance|MapKFoldableInstance|SetKFoldableInstance|ListKFoldableInstance|NonEmptyListFoldableInstance|IorFoldableInstance|SequenceKFoldableInstance|SortedMapKFoldableInstance|CoproductFoldableInstance|ValidatedFoldableInstance|MaybeKFoldableInstance|FlowableKFoldableInstance|ObservableKFoldableInstance|FluxKFoldableInstance] -[MonadDefer]<-[MonadDefer Instances|IOMonadDeferInstance|DeferredKMonadDeferInstance|ObservableKMonadDeferInstance|MaybeKMonadDeferInstance|SingleKMonadDeferInstance|FlowableKMonadDeferInstance|FluxKMonadDeferInstance|MonoKMonadDeferInstance] -[Async]<-[Async Instances|IOAsyncInstance|DeferredKAsyncInstance|MaybeKAsyncInstance|SingleKAsyncInstance|ObservableKAsyncInstance|FlowableKAsyncInstance|MonoKAsyncInstance|FluxKAsyncInstance] -[Effect]<-[Effect Instances|IOEffectInstance|DeferredKEffectInstance|MaybeKEffectInstance|ObservableKEffectInstance|SingleKEffectInstance|FlowableKEffectInstance|FluxKEffectInstance|MonoKEffectInstance] -[MonadError]<-[MonadError Instances|EitherMonadErrorInstance|TryMonadErrorInstance|OptionMonadErrorInstance|StateTMonadErrorInstance|KleisliMonadErrorInstance|IOMonadErrorInstance|DeferredKMonadErrorInstance|MaybeKMonadErrorInstance|ObservableKMonadErrorInstance|SingleKMonadErrorInstance|FlowableKMonadErrorInstance|FluxKMonadErrorInstance|MonoKMonadErrorInstance] -[ApplicativeError]<-[ApplicativeError Instances|OptionApplicativeErrorInstance|TryApplicativeErrorInstance|EitherApplicativeErrorInstance|StateTApplicativeErrorInstance|KleisliApplicativeErrorInstance|ValidatedApplicativeErrorInstance|IOApplicativeErrorInstance|DeferredKApplicativeErrorInstance|SingleKApplicativeErrorInstance|ObservableKApplicativeErrorInstance|MaybeKApplicativeErrorInstance|FlowableKApplicativeErrorInstance|FluxKApplicativeErrorInstance|MonoKApplicativeErrorInstance] [Comonad]<-[Comonad Instances|Tuple2ComonadInstance|IdComonadInstance|Function0ComonadInstance|EvalComonadInstance|CoproductComonadInstance|NonEmptyListComonadInstance|CofreeComonadInstance] -[Applicative]<-[Applicative Instances|Function1ApplicativeInstance|IdApplicativeInstance|Tuple2ApplicativeInstance|Function0ApplicativeInstance|ConstApplicativeInstance|OptionApplicativeInstance|EvalApplicativeInstance|EitherApplicativeInstance|TryApplicativeInstance|ListKApplicativeInstance|ValidatedApplicativeInstance|IorApplicativeInstance|WriterTApplicativeInstance|NonEmptyListApplicativeInstance|KleisliApplicativeInstance|StateTApplicativeInstance|SequenceKApplicativeInstance|OptionTApplicativeInstance|IOApplicativeInstance|FreeApplicativeInstance|FreeApplicativeApplicativeInstance|DeferredKApplicativeInstance|MaybeKApplicativeInstance|ObservableKApplicativeInstance|FlowableKApplicativeInstance|SingleKApplicativeInstance|FluxKApplicativeInstance|MonoKApplicativeInstance] -[Monad]<-[Monad Instances|Function1MonadInstance|Function0MonadInstance|EitherMonadInstance|IdMonadInstance|TryMonadInstance|Tuple2MonadInstance|EvalMonadInstance|OptionMonadInstance|NonEmptyListMonadInstance|OptionTMonadInstance|WriterTMonadInstance|SequenceKMonadInstance|IorMonadInstance|StateTMonadInstance|KleisliMonadInstance|ListKMonadInstance|IOMonadInstance|FreeMonadInstance|DeferredKMonadInstance|SingleKMonadInstance|MaybeKMonadInstance|FlowableKMonadInstance|ObservableKMonadInstance|FluxKMonadInstance|MonoKMonadInstance] [MonadFilter]<-[MonadFilter Instances|WriterTMonadFilterInstance|OptionMonadFilterInstance|ListKMonadFilterInstance] [MonadWriter]<-[MonadWriter Instances|WriterTMonadWriterInstance] [FunctorFilter]<-[FunctorFilter Instances|ListKFunctorFilterInstance|OptionTFunctorFilterInstance] @@ -63,7 +50,20 @@ [TraverseFilter]<-[TraverseFilter Instances|OptionTraverseFilterInstance|OptionTTraverseFilterInstance|ConstTraverseFilterInstance] [MonadState]<-[MonadState Instances|StateTMonadStateInstance] [MonadCombine]<-[MonadCombine Instances|ListKMonadCombineInstance|StateTMonadCombineInstance] +[FilterIndex]<-[FilterIndex Instances|SequenceKFilterIndexInstance|MapKFilterIndexInstance|ListKFilterIndexInstance|NonEmptyListFilterIndexInstance] +[Index]<-[Index Instances|MapKIndexInstance|SequenceKIndexInstance|NonEmptyListIndexInstance|ListKIndexInstance|NonEmptyListFilterIndexInstance|MapKFilterIndexInstance|SequenceKFilterIndexInstance|ListKFilterIndexInstance] +[At]<-[At Instances|MapKAtInstance|SetKAtInstance] +[Each]<-[Each Instances|ListKEachInstance|OptionEachInstance|EitherEachInstance|TryEachInstance|SequenceKEachInstance|NonEmptyListEachInstance|MapKEachInstance] [Corecursive]<-[Corecursive Instances|MuCorecursiveInstance|FixCorecursiveInstance|NuCorecursiveInstance] [Birecursive]<-[Birecursive Instances|FixBirecursiveInstance|MuBirecursiveInstance|NuBirecursiveInstance] [Recursive]<-[Recursive Instances|MuRecursiveInstance|FixRecursiveInstance|NuRecursiveInstance] +[MonadDefer]<-[MonadDefer Instances|IOMonadDeferInstance|DeferredKMonadDeferInstance|ObservableKMonadDeferInstance|MaybeKMonadDeferInstance|SingleKMonadDeferInstance|FlowableKMonadDeferInstance|FluxKMonadDeferInstance|MonoKMonadDeferInstance] +[Async]<-[Async Instances|IOAsyncInstance|DeferredKAsyncInstance|MaybeKAsyncInstance|SingleKAsyncInstance|ObservableKAsyncInstance|FlowableKAsyncInstance|MonoKAsyncInstance|FluxKAsyncInstance] +[Effect]<-[Effect Instances|IOEffectInstance|DeferredKEffectInstance|MaybeKEffectInstance|ObservableKEffectInstance|SingleKEffectInstance|FlowableKEffectInstance|FluxKEffectInstance|MonoKEffectInstance] +[Semigroup]<-[Semigroup Instances|ConstSemigroupInstance|OptionSemigroupInstance|SortedMapKSemigroupInstance|ListKSemigroupInstance|MapKSemigroupInstance|SetKSemigroupInstance|SequenceKSemigroupInstance|NonEmptyListSemigroupInstance|IOSemigroupInstance|IOMonoidInstance|EitherSemigroupInstance|TrySemigroupInstance] +[Monoid]<-[Monoid Instances|OptionMonoidInstance|ConstMonoidInstance|Tuple2MonoidInstance|SequenceKMonoidInstance|SortedMapKMonoidInstance|ListKMonoidInstance|MapKMonoidInstance|SetKMonoidInstance|IOMonoidInstance|TryMonoidInstance|EitherMonoidInstance] +[MonadError]<-[MonadError Instances|EitherMonadErrorInstance|TryMonadErrorInstance|OptionMonadErrorInstance|StateTMonadErrorInstance|KleisliMonadErrorInstance|IOMonadErrorInstance|DeferredKMonadErrorInstance|MaybeKMonadErrorInstance|ObservableKMonadErrorInstance|SingleKMonadErrorInstance|FlowableKMonadErrorInstance|FluxKMonadErrorInstance|MonoKMonadErrorInstance] +[ApplicativeError]<-[ApplicativeError Instances|OptionApplicativeErrorInstance|TryApplicativeErrorInstance|EitherApplicativeErrorInstance|StateTApplicativeErrorInstance|KleisliApplicativeErrorInstance|ValidatedApplicativeErrorInstance|IOApplicativeErrorInstance|DeferredKApplicativeErrorInstance|SingleKApplicativeErrorInstance|ObservableKApplicativeErrorInstance|MaybeKApplicativeErrorInstance|FlowableKApplicativeErrorInstance|FluxKApplicativeErrorInstance|MonoKApplicativeErrorInstance] +[Applicative]<-[Applicative Instances|Function1ApplicativeInstance|IdApplicativeInstance|Tuple2ApplicativeInstance|Function0ApplicativeInstance|ConstApplicativeInstance|OptionApplicativeInstance|EvalApplicativeInstance|EitherApplicativeInstance|TryApplicativeInstance|ListKApplicativeInstance|ValidatedApplicativeInstance|IorApplicativeInstance|WriterTApplicativeInstance|NonEmptyListApplicativeInstance|KleisliApplicativeInstance|StateTApplicativeInstance|SequenceKApplicativeInstance|OptionTApplicativeInstance|IOApplicativeInstance|FreeApplicativeInstance|FreeApplicativeApplicativeInstance|DeferredKApplicativeInstance|MaybeKApplicativeInstance|ObservableKApplicativeInstance|FlowableKApplicativeInstance|SingleKApplicativeInstance|FluxKApplicativeInstance|MonoKApplicativeInstance] +[Monad]<-[Monad Instances|Function1MonadInstance|Function0MonadInstance|EitherMonadInstance|IdMonadInstance|TryMonadInstance|Tuple2MonadInstance|EvalMonadInstance|OptionMonadInstance|NonEmptyListMonadInstance|OptionTMonadInstance|WriterTMonadInstance|SequenceKMonadInstance|IorMonadInstance|StateTMonadInstance|KleisliMonadInstance|ListKMonadInstance|IOMonadInstance|FreeMonadInstance|DeferredKMonadInstance|SingleKMonadInstance|MaybeKMonadInstance|FlowableKMonadInstance|ObservableKMonadInstance|FluxKMonadInstance|MonoKMonadInstance] [Functor]<-[Functor Instances|ConstFunctorInstance|OptionFunctorInstance|Function1FunctorInstance|Tuple2FunctorInstance|TryFunctorInstance|IdFunctorInstance|EvalFunctorInstance|EitherFunctorInstance|Function0FunctorInstance|MapKFunctorInstance|SortedMapKFunctorInstance|CoproductFunctorInstance|OptionTFunctorInstance|IorFunctorInstance|StateTFunctorInstance|ValidatedFunctorInstance|SequenceKFunctorInstance|WriterTFunctorInstance|ListKFunctorInstance|KleisliFunctorInstance|NonEmptyListFunctorInstance|IOFunctorInstance|CofreeFunctorInstance|YonedaFunctorInstance|FreeFunctorInstance|CoyonedaFunctorInstance|FreeApplicativeFunctorInstance|DeferredKFunctorInstance|MaybeKFunctorInstance|SingleKFunctorInstance|FlowableKFunctorInstance|ObservableKFunctorInstance|MonoKFunctorInstance|FluxKFunctorInstance|IntListPatternFunctorInstance] \ No newline at end of file diff --git a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/Utils.kt b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/Utils.kt index b161a0787aa..727fb8ecbe9 100644 --- a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/Utils.kt +++ b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/Utils.kt @@ -91,7 +91,8 @@ internal fun parContinuation(ctx: CoroutineContext, f: (A, B) -> C, c: } else { value.fold({ a -> result.fold({ - // Resumed twice on the same side + // Resumed twice on the same side, updating + intermediate = value }, { b -> c.resume(f(a, b)) }) @@ -99,7 +100,8 @@ internal fun parContinuation(ctx: CoroutineContext, f: (A, B) -> C, c: result.fold({ a -> c.resume(f(a, b)) }, { - // Resumed twice on the same side + // Resumed twice on the same side, updating + intermediate = value }) }) } From 7bf9929be82ffaf8a7cce0756522da64f7058d9c Mon Sep 17 00:00:00 2001 From: pakoito Date: Sun, 22 Jul 2018 17:54:30 +0100 Subject: [PATCH 03/26] Formatting --- .../src/test/kotlin/arrow/effects/IOTest.kt | 469 +++++++++--------- 1 file changed, 244 insertions(+), 225 deletions(-) diff --git a/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt b/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt index c3164413b50..a9b12504d49 100644 --- a/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt +++ b/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt @@ -1,18 +1,13 @@ package arrow.effects import arrow.Kind -import arrow.core.* -import arrow.effects.typeclasses.milliseconds +import arrow.core.Option +import arrow.core.eq import arrow.effects.typeclasses.seconds import arrow.test.UnitSpec -import arrow.test.concurrency.SideEffect -import arrow.test.laws.AsyncLaws import arrow.typeclasses.Eq -import arrow.typeclasses.binding import io.kotlintest.KTestJUnitRunner -import io.kotlintest.matchers.fail -import io.kotlintest.matchers.shouldBe -import io.kotlintest.matchers.shouldEqual +import kotlinx.coroutines.experimental.newSingleThreadContext import org.junit.runner.RunWith @RunWith(KTestJUnitRunner::class) @@ -27,228 +22,252 @@ class IOTest : UnitSpec() { } } - init { - testLaws(AsyncLaws.laws(IO.async(), EQ(), EQ())) - - "should defer evaluation until run" { - var run = false - val ioa = IO { run = true } - run shouldEqual false - ioa.unsafeRunSync() - run shouldEqual true - } - - class MyException : Exception() - - "should catch exceptions within main block" { - val exception = MyException() - val ioa = IO { throw exception } - val result: Either = ioa.attempt().unsafeRunSync() - - val expected = Left(exception) - - result shouldBe expected - } - - "should yield immediate successful invoke value" { - val run = IO { 1 }.unsafeRunSync() - - val expected = 1 - - run shouldBe expected - } - - "should yield immediate successful pure value" { - val run = IO.just(1).unsafeRunSync() - - val expected = 1 - - run shouldBe expected - } - - "should yield immediate successful pure value" { - val run = IO.just(1).unsafeRunSync() - - val expected = 1 - - run shouldBe expected - } - - "should throw immediate failure by raiseError" { - try { - IO.raiseError(MyException()).unsafeRunSync() - fail("") - } catch (myException: MyException) { - // Success - } catch (throwable: Throwable) { - fail("Should only throw MyException") - } - } - - "should time out on unending unsafeRunTimed" { - val never = IO.async { Unit } - val start = System.currentTimeMillis() - val received = never.unsafeRunTimed(100.milliseconds) - val elapsed = System.currentTimeMillis() - start - - received shouldBe None - (elapsed >= 100) shouldBe true - } - - "should return a null value from unsafeRunTimed" { - val never = IO.just(null) - val received = never.unsafeRunTimed(100.milliseconds) - - received shouldBe Some(null) - } - - "should return a null value from unsafeRunSync" { - val value = IO.just(null).unsafeRunSync() - - value shouldBe null - } - - "should complete when running a pure value with unsafeRunAsync" { - val expected = 0 - IO.just(expected).unsafeRunAsync { either -> - either.fold({ fail("") }, { it shouldBe expected }) - } - } - - - "should complete when running a return value with unsafeRunAsync" { - val expected = 0 - IO { expected }.unsafeRunAsync { either -> - either.fold({ fail("") }, { it shouldBe expected }) - } - } - - "should return an error when running an exception with unsafeRunAsync" { - IO.raiseError(MyException()).unsafeRunAsync { either -> - either.fold({ - when (it) { - is MyException -> { - } - else -> fail("Should only throw MyException") - } - }, { fail("") }) - } - } - - "should return exceptions within main block with unsafeRunAsync" { - val exception = MyException() - val ioa = IO { throw exception } - ioa.unsafeRunAsync { either -> - either.fold({ it shouldBe exception }, { fail("") }) - } - } - - "should not catch exceptions within run block with unsafeRunAsync" { - try { - val exception = MyException() - val ioa = IO { throw exception } - ioa.unsafeRunAsync { either -> - either.fold({ throw exception }, { fail("") }) + private fun makePar(num: Int) = + IO.async { cc -> + IO.unit.continueOn(newSingleThreadContext("$num")) + .map { + val delay = (Math.random() * 100).toLong() + Thread.sleep(delay) + delay } - } catch (myException: MyException) { - // Success - } catch (throwable: Throwable) { - fail("Should only throw MyException") - } - } - - "should complete when running a pure value with runAsync" { - val expected = 0 - IO.just(expected).runAsync { either -> - either.fold({ fail("") }, { IO { it shouldBe expected } }) - } - } - - - "should complete when running a return value with runAsync" { - val expected = 0 - IO { expected }.runAsync { either -> - either.fold({ fail("") }, { IO { it shouldBe expected } }) - } - } - - "should return an error when running an exception with runAsync" { - IO.raiseError(MyException()).runAsync { either -> - either.fold({ - when (it) { - is MyException -> { - IO { } - } - else -> fail("Should only throw MyException") - } - }, { fail("") }) - } - } - - "should return exceptions within main block with runAsync" { - val exception = MyException() - val ioa = IO { throw exception } - ioa.runAsync { either -> - either.fold({ IO { it shouldBe exception } }, { fail("") }) - } - } - - "should catch exceptions within run block with runAsync" { - try { - val exception = MyException() - val ioa = IO { throw exception } - ioa.runAsync { either -> - either.fold({ throw it }, { fail("") }) - }.unsafeRunSync() - fail("Should rethrow the exception") - } catch (throwable: AssertionError) { - fail("${throwable.message}") - } catch (throwable: Throwable) { - // Success - } + .map { + println("I am $num from ${Thread.currentThread().name} after $it ms delay") + num + }.unsafeRunAsync(cc) } - with(IO.monad()) { - - "should map values correctly on success" { - val run = IO.just(1).map() { it + 1 }.unsafeRunSync() - - val expected = 2 - - run shouldBe expected - } - - "should flatMap values correctly on success" { - val run = just(1).flatMap { num -> IO { num + 1 } }.unsafeRunSync() + private fun hey(a: A): A { println("I am $a in ${Thread.currentThread().name}"); return a } - val expected = 2 - - run shouldBe expected - } - } - - "invoke is called on every run call" { - val sideEffect = SideEffect() - val io = IO { sideEffect.increment(); 1 } - io.unsafeRunSync() - io.unsafeRunSync() - - sideEffect.counter shouldBe 2 - } - - "unsafeRunTimed times out with None result" { - val never = IO.async { } - val result = never.unsafeRunTimed(100.milliseconds) - result shouldBe None + init { + "test parallel" { + IO.parMap5(newSingleThreadContext("all"), IO.just(0).map(::hey) , IO { 1 }.map(::hey), makePar(2), makePar(3), makePar(4)) + { _, _, _, _, _ -> + hey(-1) + 100 + }.unsafeRunSync() } - "IO.binding should for comprehend over IO" { - val result = IO.monad().binding { - val x = IO.just(1).bind() - val y = bind { IO { x + 1 } } - y - }.fix() - result.unsafeRunSync() shouldBe 2 - } + // testLaws(AsyncLaws.laws(IO.async(), EQ(), EQ())) + // + // "should defer evaluation until run" { + // var run = false + // val ioa = IO { run = true } + // run shouldEqual false + // ioa.unsafeRunSync() + // run shouldEqual true + // } + // + // class MyException : Exception() + // + // "should catch exceptions within main block" { + // val exception = MyException() + // val ioa = IO { throw exception } + // val result: Either = ioa.attempt().unsafeRunSync() + // + // val expected = Left(exception) + // + // result shouldBe expected + // } + // + // "should yield immediate successful invoke value" { + // val run = IO { 1 }.unsafeRunSync() + // + // val expected = 1 + // + // run shouldBe expected + // } + // + // "should yield immediate successful pure value" { + // val run = IO.just(1).unsafeRunSync() + // + // val expected = 1 + // + // run shouldBe expected + // } + // + // "should yield immediate successful pure value" { + // val run = IO.just(1).unsafeRunSync() + // + // val expected = 1 + // + // run shouldBe expected + // } + // + // "should throw immediate failure by raiseError" { + // try { + // IO.raiseError(MyException()).unsafeRunSync() + // fail("") + // } catch (myException: MyException) { + // // Success + // } catch (throwable: Throwable) { + // fail("Should only throw MyException") + // } + // } + // + // "should time out on unending unsafeRunTimed" { + // val never = IO.async { Unit } + // val start = System.currentTimeMillis() + // val received = never.unsafeRunTimed(100.milliseconds) + // val elapsed = System.currentTimeMillis() - start + // + // received shouldBe None + // (elapsed >= 100) shouldBe true + // } + // + // "should return a null value from unsafeRunTimed" { + // val never = IO.just(null) + // val received = never.unsafeRunTimed(100.milliseconds) + // + // received shouldBe Some(null) + // } + // + // "should return a null value from unsafeRunSync" { + // val value = IO.just(null).unsafeRunSync() + // + // value shouldBe null + // } + // + // "should complete when running a pure value with unsafeRunAsync" { + // val expected = 0 + // IO.just(expected).unsafeRunAsync { either -> + // either.fold({ fail("") }, { it shouldBe expected }) + // } + // } + // + // + // "should complete when running a return value with unsafeRunAsync" { + // val expected = 0 + // IO { expected }.unsafeRunAsync { either -> + // either.fold({ fail("") }, { it shouldBe expected }) + // } + // } + // + // "should return an error when running an exception with unsafeRunAsync" { + // IO.raiseError(MyException()).unsafeRunAsync { either -> + // either.fold({ + // when (it) { + // is MyException -> { + // } + // else -> fail("Should only throw MyException") + // } + // }, { fail("") }) + // } + // } + // + // "should return exceptions within main block with unsafeRunAsync" { + // val exception = MyException() + // val ioa = IO { throw exception } + // ioa.unsafeRunAsync { either -> + // either.fold({ it shouldBe exception }, { fail("") }) + // } + // } + // + // "should not catch exceptions within run block with unsafeRunAsync" { + // try { + // val exception = MyException() + // val ioa = IO { throw exception } + // ioa.unsafeRunAsync { either -> + // either.fold({ throw exception }, { fail("") }) + // } + // } catch (myException: MyException) { + // // Success + // } catch (throwable: Throwable) { + // fail("Should only throw MyException") + // } + // } + // + // "should complete when running a pure value with runAsync" { + // val expected = 0 + // IO.just(expected).runAsync { either -> + // either.fold({ fail("") }, { IO { it shouldBe expected } }) + // } + // } + // + // + // "should complete when running a return value with runAsync" { + // val expected = 0 + // IO { expected }.runAsync { either -> + // either.fold({ fail("") }, { IO { it shouldBe expected } }) + // } + // } + // + // "should return an error when running an exception with runAsync" { + // IO.raiseError(MyException()).runAsync { either -> + // either.fold({ + // when (it) { + // is MyException -> { + // IO { } + // } + // else -> fail("Should only throw MyException") + // } + // }, { fail("") }) + // } + // } + // + // "should return exceptions within main block with runAsync" { + // val exception = MyException() + // val ioa = IO { throw exception } + // ioa.runAsync { either -> + // either.fold({ IO { it shouldBe exception } }, { fail("") }) + // } + // } + // + // "should catch exceptions within run block with runAsync" { + // try { + // val exception = MyException() + // val ioa = IO { throw exception } + // ioa.runAsync { either -> + // either.fold({ throw it }, { fail("") }) + // }.unsafeRunSync() + // fail("Should rethrow the exception") + // } catch (throwable: AssertionError) { + // fail("${throwable.message}") + // } catch (throwable: Throwable) { + // // Success + // } + // } + // + // with(IO.monad()) { + // + // "should map values correctly on success" { + // val run = IO.just(1).map() { it + 1 }.unsafeRunSync() + // + // val expected = 2 + // + // run shouldBe expected + // } + // + // "should flatMap values correctly on success" { + // val run = just(1).flatMap { num -> IO { num + 1 } }.unsafeRunSync() + // + // val expected = 2 + // + // run shouldBe expected + // } + // } + // + // "invoke is called on every run call" { + // val sideEffect = SideEffect() + // val io = IO { sideEffect.increment(); 1 } + // io.unsafeRunSync() + // io.unsafeRunSync() + // + // sideEffect.counter shouldBe 2 + // } + // + // "unsafeRunTimed times out with None result" { + // val never = IO.async { } + // val result = never.unsafeRunTimed(100.milliseconds) + // result shouldBe None + // } + // + // "IO.binding should for comprehend over IO" { + // val result = IO.monad().binding { + // val x = IO.just(1).bind() + // val y = bind { IO { x + 1 } } + // y + // }.fix() + // result.unsafeRunSync() shouldBe 2 + // } } } From 339c5508b47bf80f48890477f4e4118d3ce1c290 Mon Sep 17 00:00:00 2001 From: pakoito Date: Sun, 22 Jul 2018 17:56:23 +0100 Subject: [PATCH 04/26] Formatting --- .../src/main/kotlin/arrow/effects/internal/Utils.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/Utils.kt b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/Utils.kt index 727fb8ecbe9..a28e0dac587 100644 --- a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/Utils.kt +++ b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/Utils.kt @@ -79,10 +79,10 @@ private class OneShotLatch : AbstractQueuedSynchronizer() { internal fun parContinuation(ctx: CoroutineContext, f: (A, B) -> C, c: Continuation): Continuation> = object : Continuation> { - var intermediate: Either? = null - override val context: CoroutineContext = ctx + var intermediate: Either? = null + override fun resume(value: Either) = synchronized(this) { val result = intermediate From 3f2f8d4cb83675f7ea8c25ff4ba9a0e0d7dae67b Mon Sep 17 00:00:00 2001 From: pakoito Date: Sun, 22 Jul 2018 18:34:02 +0100 Subject: [PATCH 05/26] Add triContinuation to start 3 IOs at the same time --- .../src/main/kotlin/arrow/effects/IO.kt | 39 +++++++++-- .../kotlin/arrow/effects/internal/Utils.kt | 64 +++++++++++++++++-- 2 files changed, 92 insertions(+), 11 deletions(-) diff --git a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IO.kt b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IO.kt index 3bee005b6e2..18a9dd9b5a3 100644 --- a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IO.kt +++ b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IO.kt @@ -5,8 +5,10 @@ import arrow.core.Either.Left import arrow.effects.internal.Platform.maxStackDepthSize import arrow.effects.internal.Platform.onceOnly import arrow.effects.internal.Platform.unsafeResync +import arrow.effects.internal.Treither import arrow.effects.internal.asyncIOContinuation import arrow.effects.internal.parContinuation +import arrow.effects.internal.triContinuation import arrow.effects.typeclasses.Duration import arrow.effects.typeclasses.Proc import arrow.higherkind @@ -83,9 +85,33 @@ sealed class IO : IOOf { } fun parMap3(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, f: (A, B, C) -> D): IO = - parMap(ctx, ioA, - parMap(ctx, ioB, ioC, { b, c -> b toT c }), - { a, bc -> f(a, bc.a, bc.b) }) + IO.async { cc -> + val a: suspend () -> Treither = { + suspendCoroutine { ca: Continuation> -> + ioA.map { Treither.Left(it) }.unsafeRunAsync { + it.fold(ca::resumeWithException, ca::resume) + } + } + } + val b: suspend () -> Treither = { + suspendCoroutine { ca: Continuation> -> + ioB.map { Treither.Middle(it) }.unsafeRunAsync { + it.fold(ca::resumeWithException, ca::resume) + } + } + } + val c: suspend () -> Treither = { + suspendCoroutine { ca: Continuation> -> + ioC.map { Treither.Right(it) }.unsafeRunAsync { + it.fold(ca::resumeWithException, ca::resume) + } + } + } + val triCont = triContinuation(ctx, f, asyncIOContinuation(ctx, cc)) + a.startCoroutine(triCont) + b.startCoroutine(triCont) + c.startCoroutine(triCont) + } fun parMap4(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, f: (A, B, C, D) -> E): IO = parMap(ctx, @@ -95,10 +121,9 @@ sealed class IO : IOOf { fun parMap5(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, f: (A, B, C, D, E) -> F): IO = parMap(ctx, - parMap4(ctx, ioA, ioB, ioC, ioD, - { a, b, c, d -> Tuple4(a, b, c, d) }), - ioE, - { abcd, e -> f(abcd.a, abcd.b, abcd.c, abcd.d, e) }) + parMap(ctx, ioA, ioB, { a, b -> a toT b }), + parMap3(ctx, ioC, ioD, ioE, { c, d, e -> Tuple3(c, d, e) }), + { ab, cde -> f(ab.a, ab.b, cde.a, cde.b, cde.c) }) fun parMap6(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, ioF: IO, f: (A, B, C, D, E, F) -> G): IO = parMap3(ctx, diff --git a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/Utils.kt b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/Utils.kt index a28e0dac587..83ab8eb5627 100644 --- a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/Utils.kt +++ b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/Utils.kt @@ -77,7 +77,7 @@ private class OneShotLatch : AbstractQueuedSynchronizer() { } } -internal fun parContinuation(ctx: CoroutineContext, f: (A, B) -> C, c: Continuation): Continuation> = +internal fun parContinuation(ctx: CoroutineContext, f: (A, B) -> C, cc: Continuation): Continuation> = object : Continuation> { override val context: CoroutineContext = ctx @@ -94,11 +94,11 @@ internal fun parContinuation(ctx: CoroutineContext, f: (A, B) -> C, c: // Resumed twice on the same side, updating intermediate = value }, { b -> - c.resume(f(a, b)) + cc.resume(f(a, b)) }) }, { b -> result.fold({ a -> - c.resume(f(a, b)) + cc.resume(f(a, b)) }, { // Resumed twice on the same side, updating intermediate = value @@ -108,10 +108,66 @@ internal fun parContinuation(ctx: CoroutineContext, f: (A, B) -> C, c: } override fun resumeWithException(exception: Throwable) { - c.resumeWithException(exception) + cc.resumeWithException(exception) } } +internal fun triContinuation(ctx: CoroutineContext, f: (A, B, C) -> D, cc: Continuation): Continuation> = + object : Continuation> { + override val context: CoroutineContext = ctx + + var intermediate: Tuple3 = Tuple3(null, null, null) + + override fun resume(value: Treither) = + synchronized(this) { + val resA = intermediate.a + val resB = intermediate.b + val resC = intermediate.c + value.fold({ a -> + if (resB != null && resC != null) { + cc.resume(f(a, resB, resC)) + } else { + intermediate = Tuple3(a, resB, resC) + } + }, { b -> + if (resA != null && resC != null) { + cc.resume(f(resA, b, resC)) + } else { + intermediate = Tuple3(resA, b, resC) + } + }, { c -> + if (resA != null && resB != null) { + cc.resume(f(resA, resB, c)) + } else { + intermediate = Tuple3(resA, resB, c) + } + }) + } + + override fun resumeWithException(exception: Throwable) { + cc.resumeWithException(exception) + } + } + +internal sealed class Treither { + data class Left(val a: A) : Treither() { + override fun fold(fa: (A) -> D, fb: (B) -> D, fc: (C) -> D) = + fa(a) + } + + data class Middle(val b: B) : Treither() { + override fun fold(fa: (A) -> D, fb: (B) -> D, fc: (C) -> D) = + fb(b) + } + + data class Right(val c: C) : Treither() { + override fun fold(fa: (A) -> D, fb: (B) -> D, fc: (C) -> D) = + fc(c) + } + + abstract fun fold(fa: (A) -> D, fb: (B) -> D, fc: (C) -> D): D +} + internal fun asyncIOContinuation(ctx: CoroutineContext, cc: (Either) -> Unit): Continuation { return object : Continuation { override val context: CoroutineContext = ctx From 03a8f04cbe3620048f23d83d7415ac877fd0a5ad Mon Sep 17 00:00:00 2001 From: pakoito Date: Sun, 22 Jul 2018 18:49:36 +0100 Subject: [PATCH 06/26] Fix parallelization start issue with high-latency IO --- .../src/main/kotlin/arrow/effects/IO.kt | 53 +----- .../arrow/effects/internal/IOParallel.kt | 168 ++++++++++++++++++ .../kotlin/arrow/effects/internal/Utils.kt | 112 +----------- .../src/test/kotlin/arrow/effects/IOTest.kt | 10 +- 4 files changed, 184 insertions(+), 159 deletions(-) create mode 100644 modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/IOParallel.kt diff --git a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IO.kt b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IO.kt index 18a9dd9b5a3..bbc39024dc2 100644 --- a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IO.kt +++ b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IO.kt @@ -5,17 +5,12 @@ import arrow.core.Either.Left import arrow.effects.internal.Platform.maxStackDepthSize import arrow.effects.internal.Platform.onceOnly import arrow.effects.internal.Platform.unsafeResync -import arrow.effects.internal.Treither -import arrow.effects.internal.asyncIOContinuation -import arrow.effects.internal.parContinuation -import arrow.effects.internal.triContinuation +import arrow.effects.internal.par2 +import arrow.effects.internal.par3 import arrow.effects.typeclasses.Duration import arrow.effects.typeclasses.Proc import arrow.higherkind -import kotlin.coroutines.experimental.Continuation import kotlin.coroutines.experimental.CoroutineContext -import kotlin.coroutines.experimental.startCoroutine -import kotlin.coroutines.experimental.suspendCoroutine @higherkind sealed class IO : IOOf { @@ -65,52 +60,12 @@ sealed class IO : IOOf { fun parMap(ctx: CoroutineContext, ioA: IO, ioB: IO, f: (A, B) -> C): IO = IO.async { cc -> - val a: suspend () -> Either = { - suspendCoroutine { ca: Continuation> -> - ioA.map(::Left).unsafeRunAsync { - it.fold(ca::resumeWithException, ca::resume) - } - } - } - val b: suspend () -> Either = { - suspendCoroutine { ca: Continuation> -> - ioB.map(::Right).unsafeRunAsync { - it.fold(ca::resumeWithException, ca::resume) - } - } - } - val parCont = parContinuation(ctx, f, asyncIOContinuation(ctx, cc)) - a.startCoroutine(parCont) - b.startCoroutine(parCont) + par2(ctx, ioA, ioB, f, cc) } fun parMap3(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, f: (A, B, C) -> D): IO = IO.async { cc -> - val a: suspend () -> Treither = { - suspendCoroutine { ca: Continuation> -> - ioA.map { Treither.Left(it) }.unsafeRunAsync { - it.fold(ca::resumeWithException, ca::resume) - } - } - } - val b: suspend () -> Treither = { - suspendCoroutine { ca: Continuation> -> - ioB.map { Treither.Middle(it) }.unsafeRunAsync { - it.fold(ca::resumeWithException, ca::resume) - } - } - } - val c: suspend () -> Treither = { - suspendCoroutine { ca: Continuation> -> - ioC.map { Treither.Right(it) }.unsafeRunAsync { - it.fold(ca::resumeWithException, ca::resume) - } - } - } - val triCont = triContinuation(ctx, f, asyncIOContinuation(ctx, cc)) - a.startCoroutine(triCont) - b.startCoroutine(triCont) - c.startCoroutine(triCont) + par3(ctx, ioA, ioB, ioC, f, cc) } fun parMap4(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, f: (A, B, C, D) -> E): IO = diff --git a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/IOParallel.kt b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/IOParallel.kt new file mode 100644 index 00000000000..1677504c309 --- /dev/null +++ b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/IOParallel.kt @@ -0,0 +1,168 @@ +package arrow.effects.internal + +import arrow.core.Either +import arrow.core.Tuple3 +import arrow.core.left +import arrow.core.right +import arrow.effects.IO +import kotlin.coroutines.experimental.Continuation +import kotlin.coroutines.experimental.CoroutineContext +import kotlin.coroutines.experimental.startCoroutine +import kotlin.coroutines.experimental.suspendCoroutine + +internal fun par2(ctx: CoroutineContext, ioA: IO, ioB: IO, f: (A, B) -> C, cc: (Either) -> Unit) { + val a: suspend () -> Either = { + suspendCoroutine { ca: Continuation> -> + ioA.map { it.left() }.unsafeRunAsync { + it.fold(ca::resumeWithException, ca::resume) + } + } + } + val b: suspend () -> Either = { + suspendCoroutine { ca: Continuation> -> + ioB.map { it.right() }.unsafeRunAsync { + it.fold(ca::resumeWithException, ca::resume) + } + } + } + val parCont = parContinuation(ctx, f, asyncIOContinuation(ctx, cc)) + a.startCoroutine(parCont) + b.startCoroutine(parCont) +} + +/* Parallelization is only provided in pairs and triples. + * Every time you start 4+ elements, each pair or triple has to be combined with another one at the same depth. + * Elements at higher depths that are synchronous can prevent elements at a higher depth to start. + * Thus, we need to provide solutions for even and uneven amounts of IOs for all to be started at the same depth. */ +internal fun par3(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, f: (A, B, C) -> D, cc: (Either) -> Unit) { + val a: suspend () -> Treither = { + suspendCoroutine { ca: Continuation> -> + ioA.map { Treither.Left(it) }.unsafeRunAsync { + it.fold(ca::resumeWithException, ca::resume) + } + } + } + val b: suspend () -> Treither = { + suspendCoroutine { ca: Continuation> -> + ioB.map { Treither.Middle(it) }.unsafeRunAsync { + it.fold(ca::resumeWithException, ca::resume) + } + } + } + val c: suspend () -> Treither = { + suspendCoroutine { ca: Continuation> -> + ioC.map { Treither.Right(it) }.unsafeRunAsync { + it.fold(ca::resumeWithException, ca::resume) + } + } + } + val triCont = triContinuation(ctx, f, asyncIOContinuation(ctx, cc)) + a.startCoroutine(triCont) + b.startCoroutine(triCont) + c.startCoroutine(triCont) +} + +private fun parContinuation(ctx: CoroutineContext, f: (A, B) -> C, cc: Continuation): Continuation> = + object : Continuation> { + override val context: CoroutineContext = ctx + + var intermediate: Either? = null + + override fun resume(value: Either) = + synchronized(this) { + val result = intermediate + if (null == result) { + intermediate = value + } else { + value.fold({ a -> + result.fold({ + // Resumed twice on the same side, updating + intermediate = value + }, { b -> + cc.resume(f(a, b)) + }) + }, { b -> + result.fold({ a -> + cc.resume(f(a, b)) + }, { + // Resumed twice on the same side, updating + intermediate = value + }) + }) + } + } + + override fun resumeWithException(exception: Throwable) { + cc.resumeWithException(exception) + } + } + +private fun triContinuation(ctx: CoroutineContext, f: (A, B, C) -> D, cc: Continuation): Continuation> = + object : Continuation> { + override val context: CoroutineContext = ctx + + var intermediate: Tuple3 = Tuple3(null, null, null) + + override fun resume(value: Treither) = + synchronized(this) { + val resA = intermediate.a + val resB = intermediate.b + val resC = intermediate.c + value.fold({ a -> + if (resB != null && resC != null) { + cc.resume(f(a, resB, resC)) + } else { + intermediate = Tuple3(a, resB, resC) + } + }, { b -> + if (resA != null && resC != null) { + cc.resume(f(resA, b, resC)) + } else { + intermediate = Tuple3(resA, b, resC) + } + }, { c -> + if (resA != null && resB != null) { + cc.resume(f(resA, resB, c)) + } else { + intermediate = Tuple3(resA, resB, c) + } + }) + } + + override fun resumeWithException(exception: Throwable) { + cc.resumeWithException(exception) + } + } + +private sealed class Treither { + data class Left(val a: A) : Treither() { + override fun fold(fa: (A) -> D, fb: (B) -> D, fc: (C) -> D) = + fa(a) + } + + data class Middle(val b: B) : Treither() { + override fun fold(fa: (A) -> D, fb: (B) -> D, fc: (C) -> D) = + fb(b) + } + + data class Right(val c: C) : Treither() { + override fun fold(fa: (A) -> D, fb: (B) -> D, fc: (C) -> D) = + fc(c) + } + + abstract fun fold(fa: (A) -> D, fb: (B) -> D, fc: (C) -> D): D +} + +private fun asyncIOContinuation(ctx: CoroutineContext, cc: (Either) -> Unit): Continuation { + return object : Continuation { + override val context: CoroutineContext = ctx + + override fun resume(value: A) { + cc(value.right()) + } + + override fun resumeWithException(exception: Throwable) { + cc(exception.left()) + } + } +} diff --git a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/Utils.kt b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/Utils.kt index 83ab8eb5627..3e67f3a7604 100644 --- a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/Utils.kt +++ b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/Utils.kt @@ -1,13 +1,14 @@ package arrow.effects.internal -import arrow.core.* +import arrow.core.Either +import arrow.core.None +import arrow.core.Option +import arrow.core.Some import arrow.effects.IO import arrow.effects.typeclasses.Duration import java.util.* import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.locks.AbstractQueuedSynchronizer -import kotlin.coroutines.experimental.Continuation -import kotlin.coroutines.experimental.CoroutineContext object Platform { @@ -76,108 +77,3 @@ private class OneShotLatch : AbstractQueuedSynchronizer() { return true } } - -internal fun parContinuation(ctx: CoroutineContext, f: (A, B) -> C, cc: Continuation): Continuation> = - object : Continuation> { - override val context: CoroutineContext = ctx - - var intermediate: Either? = null - - override fun resume(value: Either) = - synchronized(this) { - val result = intermediate - if (null == result) { - intermediate = value - } else { - value.fold({ a -> - result.fold({ - // Resumed twice on the same side, updating - intermediate = value - }, { b -> - cc.resume(f(a, b)) - }) - }, { b -> - result.fold({ a -> - cc.resume(f(a, b)) - }, { - // Resumed twice on the same side, updating - intermediate = value - }) - }) - } - } - - override fun resumeWithException(exception: Throwable) { - cc.resumeWithException(exception) - } - } - -internal fun triContinuation(ctx: CoroutineContext, f: (A, B, C) -> D, cc: Continuation): Continuation> = - object : Continuation> { - override val context: CoroutineContext = ctx - - var intermediate: Tuple3 = Tuple3(null, null, null) - - override fun resume(value: Treither) = - synchronized(this) { - val resA = intermediate.a - val resB = intermediate.b - val resC = intermediate.c - value.fold({ a -> - if (resB != null && resC != null) { - cc.resume(f(a, resB, resC)) - } else { - intermediate = Tuple3(a, resB, resC) - } - }, { b -> - if (resA != null && resC != null) { - cc.resume(f(resA, b, resC)) - } else { - intermediate = Tuple3(resA, b, resC) - } - }, { c -> - if (resA != null && resB != null) { - cc.resume(f(resA, resB, c)) - } else { - intermediate = Tuple3(resA, resB, c) - } - }) - } - - override fun resumeWithException(exception: Throwable) { - cc.resumeWithException(exception) - } - } - -internal sealed class Treither { - data class Left(val a: A) : Treither() { - override fun fold(fa: (A) -> D, fb: (B) -> D, fc: (C) -> D) = - fa(a) - } - - data class Middle(val b: B) : Treither() { - override fun fold(fa: (A) -> D, fb: (B) -> D, fc: (C) -> D) = - fb(b) - } - - data class Right(val c: C) : Treither() { - override fun fold(fa: (A) -> D, fb: (B) -> D, fc: (C) -> D) = - fc(c) - } - - abstract fun fold(fa: (A) -> D, fb: (B) -> D, fc: (C) -> D): D -} - -internal fun asyncIOContinuation(ctx: CoroutineContext, cc: (Either) -> Unit): Continuation { - return object : Continuation { - override val context: CoroutineContext = ctx - - override fun resume(value: A) { - cc(value.right()) - } - - override fun resumeWithException(exception: Throwable) { - cc(exception.left()) - } - } -} diff --git a/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt b/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt index a9b12504d49..46c0d0c9c54 100644 --- a/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt +++ b/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt @@ -25,6 +25,7 @@ class IOTest : UnitSpec() { private fun makePar(num: Int) = IO.async { cc -> IO.unit.continueOn(newSingleThreadContext("$num")) + .map(::hey) .map { val delay = (Math.random() * 100).toLong() Thread.sleep(delay) @@ -36,11 +37,16 @@ class IOTest : UnitSpec() { }.unsafeRunAsync(cc) } - private fun hey(a: A): A { println("I am $a in ${Thread.currentThread().name}"); return a } + private fun hey(a: A): A { + println("I am $a in ${Thread.currentThread().name}"); return a + } init { "test parallel" { - IO.parMap5(newSingleThreadContext("all"), IO.just(0).map(::hey) , IO { 1 }.map(::hey), makePar(2), makePar(3), makePar(4)) + IO.parMap5(newSingleThreadContext("all"), IO.just(50).map(::hey), IO { + Thread.sleep(101) + 1 + }.map(::hey), makePar(3), makePar(4), IO.just(0).map(::hey)) { _, _, _, _, _ -> hey(-1) 100 From 744a35bbf22d62d5ccf6e3cda8e5a34cb15d4bd7 Mon Sep 17 00:00:00 2001 From: pakoito Date: Sun, 22 Jul 2018 18:53:20 +0100 Subject: [PATCH 07/26] Add doc --- .../src/main/kotlin/arrow/effects/internal/IOParallel.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/IOParallel.kt b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/IOParallel.kt index 1677504c309..e2687476f45 100644 --- a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/IOParallel.kt +++ b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/IOParallel.kt @@ -10,6 +10,7 @@ import kotlin.coroutines.experimental.CoroutineContext import kotlin.coroutines.experimental.startCoroutine import kotlin.coroutines.experimental.suspendCoroutine +/* See par3 */ internal fun par2(ctx: CoroutineContext, ioA: IO, ioB: IO, f: (A, B) -> C, cc: (Either) -> Unit) { val a: suspend () -> Either = { suspendCoroutine { ca: Continuation> -> From 17459b4c440acadf3c98710e62126a58c53a11e0 Mon Sep 17 00:00:00 2001 From: pakoito Date: Sun, 22 Jul 2018 18:56:33 +0100 Subject: [PATCH 08/26] Reset tests to Master --- .../src/test/kotlin/arrow/effects/IOTest.kt | 475 +++++++++--------- 1 file changed, 225 insertions(+), 250 deletions(-) diff --git a/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt b/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt index 46c0d0c9c54..c3164413b50 100644 --- a/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt +++ b/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt @@ -1,13 +1,18 @@ package arrow.effects import arrow.Kind -import arrow.core.Option -import arrow.core.eq +import arrow.core.* +import arrow.effects.typeclasses.milliseconds import arrow.effects.typeclasses.seconds import arrow.test.UnitSpec +import arrow.test.concurrency.SideEffect +import arrow.test.laws.AsyncLaws import arrow.typeclasses.Eq +import arrow.typeclasses.binding import io.kotlintest.KTestJUnitRunner -import kotlinx.coroutines.experimental.newSingleThreadContext +import io.kotlintest.matchers.fail +import io.kotlintest.matchers.shouldBe +import io.kotlintest.matchers.shouldEqual import org.junit.runner.RunWith @RunWith(KTestJUnitRunner::class) @@ -22,258 +27,228 @@ class IOTest : UnitSpec() { } } - private fun makePar(num: Int) = - IO.async { cc -> - IO.unit.continueOn(newSingleThreadContext("$num")) - .map(::hey) - .map { - val delay = (Math.random() * 100).toLong() - Thread.sleep(delay) - delay + init { + testLaws(AsyncLaws.laws(IO.async(), EQ(), EQ())) + + "should defer evaluation until run" { + var run = false + val ioa = IO { run = true } + run shouldEqual false + ioa.unsafeRunSync() + run shouldEqual true + } + + class MyException : Exception() + + "should catch exceptions within main block" { + val exception = MyException() + val ioa = IO { throw exception } + val result: Either = ioa.attempt().unsafeRunSync() + + val expected = Left(exception) + + result shouldBe expected + } + + "should yield immediate successful invoke value" { + val run = IO { 1 }.unsafeRunSync() + + val expected = 1 + + run shouldBe expected + } + + "should yield immediate successful pure value" { + val run = IO.just(1).unsafeRunSync() + + val expected = 1 + + run shouldBe expected + } + + "should yield immediate successful pure value" { + val run = IO.just(1).unsafeRunSync() + + val expected = 1 + + run shouldBe expected + } + + "should throw immediate failure by raiseError" { + try { + IO.raiseError(MyException()).unsafeRunSync() + fail("") + } catch (myException: MyException) { + // Success + } catch (throwable: Throwable) { + fail("Should only throw MyException") + } + } + + "should time out on unending unsafeRunTimed" { + val never = IO.async { Unit } + val start = System.currentTimeMillis() + val received = never.unsafeRunTimed(100.milliseconds) + val elapsed = System.currentTimeMillis() - start + + received shouldBe None + (elapsed >= 100) shouldBe true + } + + "should return a null value from unsafeRunTimed" { + val never = IO.just(null) + val received = never.unsafeRunTimed(100.milliseconds) + + received shouldBe Some(null) + } + + "should return a null value from unsafeRunSync" { + val value = IO.just(null).unsafeRunSync() + + value shouldBe null + } + + "should complete when running a pure value with unsafeRunAsync" { + val expected = 0 + IO.just(expected).unsafeRunAsync { either -> + either.fold({ fail("") }, { it shouldBe expected }) + } + } + + + "should complete when running a return value with unsafeRunAsync" { + val expected = 0 + IO { expected }.unsafeRunAsync { either -> + either.fold({ fail("") }, { it shouldBe expected }) + } + } + + "should return an error when running an exception with unsafeRunAsync" { + IO.raiseError(MyException()).unsafeRunAsync { either -> + either.fold({ + when (it) { + is MyException -> { + } + else -> fail("Should only throw MyException") + } + }, { fail("") }) + } + } + + "should return exceptions within main block with unsafeRunAsync" { + val exception = MyException() + val ioa = IO { throw exception } + ioa.unsafeRunAsync { either -> + either.fold({ it shouldBe exception }, { fail("") }) + } + } + + "should not catch exceptions within run block with unsafeRunAsync" { + try { + val exception = MyException() + val ioa = IO { throw exception } + ioa.unsafeRunAsync { either -> + either.fold({ throw exception }, { fail("") }) } - .map { - println("I am $num from ${Thread.currentThread().name} after $it ms delay") - num - }.unsafeRunAsync(cc) + } catch (myException: MyException) { + // Success + } catch (throwable: Throwable) { + fail("Should only throw MyException") + } } - private fun hey(a: A): A { - println("I am $a in ${Thread.currentThread().name}"); return a - } + "should complete when running a pure value with runAsync" { + val expected = 0 + IO.just(expected).runAsync { either -> + either.fold({ fail("") }, { IO { it shouldBe expected } }) + } + } - init { - "test parallel" { - IO.parMap5(newSingleThreadContext("all"), IO.just(50).map(::hey), IO { - Thread.sleep(101) - 1 - }.map(::hey), makePar(3), makePar(4), IO.just(0).map(::hey)) - { _, _, _, _, _ -> - hey(-1) - 100 - }.unsafeRunSync() + + "should complete when running a return value with runAsync" { + val expected = 0 + IO { expected }.runAsync { either -> + either.fold({ fail("") }, { IO { it shouldBe expected } }) + } + } + + "should return an error when running an exception with runAsync" { + IO.raiseError(MyException()).runAsync { either -> + either.fold({ + when (it) { + is MyException -> { + IO { } + } + else -> fail("Should only throw MyException") + } + }, { fail("") }) + } + } + + "should return exceptions within main block with runAsync" { + val exception = MyException() + val ioa = IO { throw exception } + ioa.runAsync { either -> + either.fold({ IO { it shouldBe exception } }, { fail("") }) + } } - // testLaws(AsyncLaws.laws(IO.async(), EQ(), EQ())) - // - // "should defer evaluation until run" { - // var run = false - // val ioa = IO { run = true } - // run shouldEqual false - // ioa.unsafeRunSync() - // run shouldEqual true - // } - // - // class MyException : Exception() - // - // "should catch exceptions within main block" { - // val exception = MyException() - // val ioa = IO { throw exception } - // val result: Either = ioa.attempt().unsafeRunSync() - // - // val expected = Left(exception) - // - // result shouldBe expected - // } - // - // "should yield immediate successful invoke value" { - // val run = IO { 1 }.unsafeRunSync() - // - // val expected = 1 - // - // run shouldBe expected - // } - // - // "should yield immediate successful pure value" { - // val run = IO.just(1).unsafeRunSync() - // - // val expected = 1 - // - // run shouldBe expected - // } - // - // "should yield immediate successful pure value" { - // val run = IO.just(1).unsafeRunSync() - // - // val expected = 1 - // - // run shouldBe expected - // } - // - // "should throw immediate failure by raiseError" { - // try { - // IO.raiseError(MyException()).unsafeRunSync() - // fail("") - // } catch (myException: MyException) { - // // Success - // } catch (throwable: Throwable) { - // fail("Should only throw MyException") - // } - // } - // - // "should time out on unending unsafeRunTimed" { - // val never = IO.async { Unit } - // val start = System.currentTimeMillis() - // val received = never.unsafeRunTimed(100.milliseconds) - // val elapsed = System.currentTimeMillis() - start - // - // received shouldBe None - // (elapsed >= 100) shouldBe true - // } - // - // "should return a null value from unsafeRunTimed" { - // val never = IO.just(null) - // val received = never.unsafeRunTimed(100.milliseconds) - // - // received shouldBe Some(null) - // } - // - // "should return a null value from unsafeRunSync" { - // val value = IO.just(null).unsafeRunSync() - // - // value shouldBe null - // } - // - // "should complete when running a pure value with unsafeRunAsync" { - // val expected = 0 - // IO.just(expected).unsafeRunAsync { either -> - // either.fold({ fail("") }, { it shouldBe expected }) - // } - // } - // - // - // "should complete when running a return value with unsafeRunAsync" { - // val expected = 0 - // IO { expected }.unsafeRunAsync { either -> - // either.fold({ fail("") }, { it shouldBe expected }) - // } - // } - // - // "should return an error when running an exception with unsafeRunAsync" { - // IO.raiseError(MyException()).unsafeRunAsync { either -> - // either.fold({ - // when (it) { - // is MyException -> { - // } - // else -> fail("Should only throw MyException") - // } - // }, { fail("") }) - // } - // } - // - // "should return exceptions within main block with unsafeRunAsync" { - // val exception = MyException() - // val ioa = IO { throw exception } - // ioa.unsafeRunAsync { either -> - // either.fold({ it shouldBe exception }, { fail("") }) - // } - // } - // - // "should not catch exceptions within run block with unsafeRunAsync" { - // try { - // val exception = MyException() - // val ioa = IO { throw exception } - // ioa.unsafeRunAsync { either -> - // either.fold({ throw exception }, { fail("") }) - // } - // } catch (myException: MyException) { - // // Success - // } catch (throwable: Throwable) { - // fail("Should only throw MyException") - // } - // } - // - // "should complete when running a pure value with runAsync" { - // val expected = 0 - // IO.just(expected).runAsync { either -> - // either.fold({ fail("") }, { IO { it shouldBe expected } }) - // } - // } - // - // - // "should complete when running a return value with runAsync" { - // val expected = 0 - // IO { expected }.runAsync { either -> - // either.fold({ fail("") }, { IO { it shouldBe expected } }) - // } - // } - // - // "should return an error when running an exception with runAsync" { - // IO.raiseError(MyException()).runAsync { either -> - // either.fold({ - // when (it) { - // is MyException -> { - // IO { } - // } - // else -> fail("Should only throw MyException") - // } - // }, { fail("") }) - // } - // } - // - // "should return exceptions within main block with runAsync" { - // val exception = MyException() - // val ioa = IO { throw exception } - // ioa.runAsync { either -> - // either.fold({ IO { it shouldBe exception } }, { fail("") }) - // } - // } - // - // "should catch exceptions within run block with runAsync" { - // try { - // val exception = MyException() - // val ioa = IO { throw exception } - // ioa.runAsync { either -> - // either.fold({ throw it }, { fail("") }) - // }.unsafeRunSync() - // fail("Should rethrow the exception") - // } catch (throwable: AssertionError) { - // fail("${throwable.message}") - // } catch (throwable: Throwable) { - // // Success - // } - // } - // - // with(IO.monad()) { - // - // "should map values correctly on success" { - // val run = IO.just(1).map() { it + 1 }.unsafeRunSync() - // - // val expected = 2 - // - // run shouldBe expected - // } - // - // "should flatMap values correctly on success" { - // val run = just(1).flatMap { num -> IO { num + 1 } }.unsafeRunSync() - // - // val expected = 2 - // - // run shouldBe expected - // } - // } - // - // "invoke is called on every run call" { - // val sideEffect = SideEffect() - // val io = IO { sideEffect.increment(); 1 } - // io.unsafeRunSync() - // io.unsafeRunSync() - // - // sideEffect.counter shouldBe 2 - // } - // - // "unsafeRunTimed times out with None result" { - // val never = IO.async { } - // val result = never.unsafeRunTimed(100.milliseconds) - // result shouldBe None - // } - // - // "IO.binding should for comprehend over IO" { - // val result = IO.monad().binding { - // val x = IO.just(1).bind() - // val y = bind { IO { x + 1 } } - // y - // }.fix() - // result.unsafeRunSync() shouldBe 2 - // } + "should catch exceptions within run block with runAsync" { + try { + val exception = MyException() + val ioa = IO { throw exception } + ioa.runAsync { either -> + either.fold({ throw it }, { fail("") }) + }.unsafeRunSync() + fail("Should rethrow the exception") + } catch (throwable: AssertionError) { + fail("${throwable.message}") + } catch (throwable: Throwable) { + // Success + } + } + + with(IO.monad()) { + + "should map values correctly on success" { + val run = IO.just(1).map() { it + 1 }.unsafeRunSync() + + val expected = 2 + + run shouldBe expected + } + + "should flatMap values correctly on success" { + val run = just(1).flatMap { num -> IO { num + 1 } }.unsafeRunSync() + + val expected = 2 + + run shouldBe expected + } + } + + "invoke is called on every run call" { + val sideEffect = SideEffect() + val io = IO { sideEffect.increment(); 1 } + io.unsafeRunSync() + io.unsafeRunSync() + + sideEffect.counter shouldBe 2 + } + + "unsafeRunTimed times out with None result" { + val never = IO.async { } + val result = never.unsafeRunTimed(100.milliseconds) + result shouldBe None + } + + "IO.binding should for comprehend over IO" { + val result = IO.monad().binding { + val x = IO.just(1).bind() + val y = bind { IO { x + 1 } } + y + }.fix() + result.unsafeRunSync() shouldBe 2 + } } } From f120361e5ba47216da75d1116bd69f66b69ca9b3 Mon Sep 17 00:00:00 2001 From: pakoito Date: Sun, 22 Jul 2018 18:57:45 +0100 Subject: [PATCH 09/26] Reset Utils to Master --- .../src/main/kotlin/arrow/effects/internal/Utils.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/Utils.kt b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/Utils.kt index 3e67f3a7604..e61cedba9eb 100644 --- a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/Utils.kt +++ b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/Utils.kt @@ -4,8 +4,8 @@ import arrow.core.Either import arrow.core.None import arrow.core.Option import arrow.core.Some -import arrow.effects.IO import arrow.effects.typeclasses.Duration +import arrow.effects.IO import java.util.* import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.locks.AbstractQueuedSynchronizer From 6671ae3a834711d6fd12e3bed0cfd8b38ba58b0a Mon Sep 17 00:00:00 2001 From: pakoito Date: Sun, 22 Jul 2018 19:09:59 +0100 Subject: [PATCH 10/26] Add up to parMap9 --- .../src/main/kotlin/arrow/effects/IO.kt | 42 +++++++++++++------ .../arrow/effects/internal/IOParallel.kt | 5 ++- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IO.kt b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IO.kt index bbc39024dc2..6fc2e086271 100644 --- a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IO.kt +++ b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IO.kt @@ -59,33 +59,49 @@ sealed class IO : IOOf { } fun parMap(ctx: CoroutineContext, ioA: IO, ioB: IO, f: (A, B) -> C): IO = - IO.async { cc -> - par2(ctx, ioA, ioB, f, cc) - } + IO.async(par2(ctx, ioA, ioB, f)) fun parMap3(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, f: (A, B, C) -> D): IO = - IO.async { cc -> - par3(ctx, ioA, ioB, ioC, f, cc) - } + IO.async(par3(ctx, ioA, ioB, ioC, f)) fun parMap4(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, f: (A, B, C, D) -> E): IO = parMap(ctx, - parMap(ctx, ioA, ioB, { a, b -> a toT b }), - parMap(ctx, ioC, ioD, { c, d -> c toT d }), + parMap(ctx, ioA, ioB, ::Tuple2), + parMap(ctx, ioC, ioD, ::Tuple2), { ab, cd -> f(ab.a, ab.b, cd.a, cd.b) }) fun parMap5(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, f: (A, B, C, D, E) -> F): IO = parMap(ctx, - parMap(ctx, ioA, ioB, { a, b -> a toT b }), + parMap(ctx, ioA, ioB, ::Tuple2), parMap3(ctx, ioC, ioD, ioE, { c, d, e -> Tuple3(c, d, e) }), { ab, cde -> f(ab.a, ab.b, cde.a, cde.b, cde.c) }) fun parMap6(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, ioF: IO, f: (A, B, C, D, E, F) -> G): IO = + parMap(ctx, + parMap3(ctx, ioA, ioB, ioC, { a, b, c -> Tuple3(a, b, c) }), + parMap3(ctx, ioD, ioE, ioF, { d, e, ff -> Tuple3(d, e, ff) }), + { abc, def -> f(abc.a, abc.b, abc.c, def.a, def.b, def.c) }) + + fun parMap7(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, ioF: IO, ioG: IO, f: (A, B, C, D, E, F, G) -> H): IO = + parMap3(ctx, + parMap3(ctx, ioA, ioB, ioC, { a, b, c -> Tuple3(a, b, c) }), + parMap(ctx, ioD, ioE, ::Tuple2), + parMap(ctx, ioF, ioG, ::Tuple2), + { abc, de, fg -> f(abc.a, abc.b, abc.c, de.a, de.b, fg.a, fg.b) }) + + fun parMap8(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, ioF: IO, ioG: IO, ioH: IO, f: (A, B, C, D, E, F, G, H) -> I): IO = + parMap3(ctx, + parMap3(ctx, ioA, ioB, ioC, ::Tuple3), + parMap3(ctx, ioD, ioE, ioF, ::Tuple3), + parMap(ctx, ioG, ioH, ::Tuple2), + { abc, def, gh -> f(abc.a, abc.b, abc.c, def.a, def.b, def.c, gh.a, gh.b) }) + + fun parMap9(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, ioF: IO, ioG: IO, ioH: IO, ioI: IO, f: (A, B, C, D, E, F, G, H, I) -> J): IO = parMap3(ctx, - parMap(ctx, ioA, ioB, { a, b -> a toT b }), - parMap(ctx, ioC, ioD, { c, d -> c toT d }), - parMap(ctx, ioE, ioF, { e, ff -> e toT ff }), - { ab, cd, ef -> f(ab.a, ab.b, cd.a, cd.b, ef.a, ef.b) }) + parMap3(ctx, ioA, ioB, ioC, ::Tuple3), + parMap3(ctx, ioD, ioE, ioF, ::Tuple3), + parMap3(ctx, ioG, ioH, ioI, ::Tuple3), + { abc, def, ghi -> f(abc.a, abc.b, abc.c, def.a, def.b, def.c, ghi.a, ghi.b, ghi.c) }) } abstract fun map(f: (A) -> B): IO diff --git a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/IOParallel.kt b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/IOParallel.kt index e2687476f45..f9adb06887f 100644 --- a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/IOParallel.kt +++ b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/IOParallel.kt @@ -5,13 +5,14 @@ import arrow.core.Tuple3 import arrow.core.left import arrow.core.right import arrow.effects.IO +import arrow.effects.typeclasses.Proc import kotlin.coroutines.experimental.Continuation import kotlin.coroutines.experimental.CoroutineContext import kotlin.coroutines.experimental.startCoroutine import kotlin.coroutines.experimental.suspendCoroutine /* See par3 */ -internal fun par2(ctx: CoroutineContext, ioA: IO, ioB: IO, f: (A, B) -> C, cc: (Either) -> Unit) { +internal fun par2(ctx: CoroutineContext, ioA: IO, ioB: IO, f: (A, B) -> C): Proc = { cc -> val a: suspend () -> Either = { suspendCoroutine { ca: Continuation> -> ioA.map { it.left() }.unsafeRunAsync { @@ -35,7 +36,7 @@ internal fun par2(ctx: CoroutineContext, ioA: IO, ioB: IO, f: (A * Every time you start 4+ elements, each pair or triple has to be combined with another one at the same depth. * Elements at higher depths that are synchronous can prevent elements at a higher depth to start. * Thus, we need to provide solutions for even and uneven amounts of IOs for all to be started at the same depth. */ -internal fun par3(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, f: (A, B, C) -> D, cc: (Either) -> Unit) { +internal fun par3(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, f: (A, B, C) -> D): Proc = { cc -> val a: suspend () -> Treither = { suspendCoroutine { ca: Continuation> -> ioA.map { Treither.Left(it) }.unsafeRunAsync { From 375be5a9729fc2eb49b1dc9d9fc0e2d3232a1463 Mon Sep 17 00:00:00 2001 From: pakoito Date: Sun, 22 Jul 2018 19:16:02 +0100 Subject: [PATCH 11/26] Move everything to IOParallel. Unify name. --- .../src/main/kotlin/arrow/effects/IO.kt | 47 +---------------- .../main/kotlin/arrow/effects/IOParallel.kt | 52 +++++++++++++++++++ .../{IOParallel.kt => ParallelUtils.kt} | 0 3 files changed, 53 insertions(+), 46 deletions(-) create mode 100644 modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IOParallel.kt rename modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/{IOParallel.kt => ParallelUtils.kt} (100%) diff --git a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IO.kt b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IO.kt index 6fc2e086271..f2d6833aabb 100644 --- a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IO.kt +++ b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IO.kt @@ -5,8 +5,6 @@ import arrow.core.Either.Left import arrow.effects.internal.Platform.maxStackDepthSize import arrow.effects.internal.Platform.onceOnly import arrow.effects.internal.Platform.unsafeResync -import arrow.effects.internal.par2 -import arrow.effects.internal.par3 import arrow.effects.typeclasses.Duration import arrow.effects.typeclasses.Proc import arrow.higherkind @@ -58,50 +56,7 @@ sealed class IO : IOOf { } } - fun parMap(ctx: CoroutineContext, ioA: IO, ioB: IO, f: (A, B) -> C): IO = - IO.async(par2(ctx, ioA, ioB, f)) - - fun parMap3(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, f: (A, B, C) -> D): IO = - IO.async(par3(ctx, ioA, ioB, ioC, f)) - - fun parMap4(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, f: (A, B, C, D) -> E): IO = - parMap(ctx, - parMap(ctx, ioA, ioB, ::Tuple2), - parMap(ctx, ioC, ioD, ::Tuple2), - { ab, cd -> f(ab.a, ab.b, cd.a, cd.b) }) - - fun parMap5(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, f: (A, B, C, D, E) -> F): IO = - parMap(ctx, - parMap(ctx, ioA, ioB, ::Tuple2), - parMap3(ctx, ioC, ioD, ioE, { c, d, e -> Tuple3(c, d, e) }), - { ab, cde -> f(ab.a, ab.b, cde.a, cde.b, cde.c) }) - - fun parMap6(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, ioF: IO, f: (A, B, C, D, E, F) -> G): IO = - parMap(ctx, - parMap3(ctx, ioA, ioB, ioC, { a, b, c -> Tuple3(a, b, c) }), - parMap3(ctx, ioD, ioE, ioF, { d, e, ff -> Tuple3(d, e, ff) }), - { abc, def -> f(abc.a, abc.b, abc.c, def.a, def.b, def.c) }) - - fun parMap7(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, ioF: IO, ioG: IO, f: (A, B, C, D, E, F, G) -> H): IO = - parMap3(ctx, - parMap3(ctx, ioA, ioB, ioC, { a, b, c -> Tuple3(a, b, c) }), - parMap(ctx, ioD, ioE, ::Tuple2), - parMap(ctx, ioF, ioG, ::Tuple2), - { abc, de, fg -> f(abc.a, abc.b, abc.c, de.a, de.b, fg.a, fg.b) }) - - fun parMap8(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, ioF: IO, ioG: IO, ioH: IO, f: (A, B, C, D, E, F, G, H) -> I): IO = - parMap3(ctx, - parMap3(ctx, ioA, ioB, ioC, ::Tuple3), - parMap3(ctx, ioD, ioE, ioF, ::Tuple3), - parMap(ctx, ioG, ioH, ::Tuple2), - { abc, def, gh -> f(abc.a, abc.b, abc.c, def.a, def.b, def.c, gh.a, gh.b) }) - - fun parMap9(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, ioF: IO, ioG: IO, ioH: IO, ioI: IO, f: (A, B, C, D, E, F, G, H, I) -> J): IO = - parMap3(ctx, - parMap3(ctx, ioA, ioB, ioC, ::Tuple3), - parMap3(ctx, ioD, ioE, ioF, ::Tuple3), - parMap3(ctx, ioG, ioH, ioI, ::Tuple3), - { abc, def, ghi -> f(abc.a, abc.b, abc.c, def.a, def.b, def.c, ghi.a, ghi.b, ghi.c) }) + /* For parMap, look into IOParallel */ } abstract fun map(f: (A) -> B): IO diff --git a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IOParallel.kt b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IOParallel.kt new file mode 100644 index 00000000000..96eee8f5837 --- /dev/null +++ b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IOParallel.kt @@ -0,0 +1,52 @@ +package arrow.effects + +import arrow.core.Tuple2 +import arrow.core.Tuple3 +import arrow.effects.internal.par2 +import arrow.effects.internal.par3 +import kotlin.coroutines.experimental.CoroutineContext + +fun IO.Companion.parMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, f: (A, B) -> C): IO = + IO.async(par2(ctx, ioA, ioB, f)) + +fun IO.Companion.parMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, f: (A, B, C) -> D): IO = + IO.async(par3(ctx, ioA, ioB, ioC, f)) + +fun IO.Companion.parMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, f: (A, B, C, D) -> E): IO = + parMapN(ctx, + parMapN(ctx, ioA, ioB, ::Tuple2), + parMapN(ctx, ioC, ioD, ::Tuple2), + { ab, cd -> f(ab.a, ab.b, cd.a, cd.b) }) + +fun IO.Companion.parMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, f: (A, B, C, D, E) -> F): IO = + parMapN(ctx, + parMapN(ctx, ioA, ioB, ::Tuple2), + parMapN(ctx, ioC, ioD, ioE, { c, d, e -> Tuple3(c, d, e) }), + { ab, cde -> f(ab.a, ab.b, cde.a, cde.b, cde.c) }) + +fun IO.Companion.parMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, ioF: IO, f: (A, B, C, D, E, F) -> G): IO = + parMapN(ctx, + parMapN(ctx, ioA, ioB, ioC, { a, b, c -> Tuple3(a, b, c) }), + parMapN(ctx, ioD, ioE, ioF, { d, e, ff -> Tuple3(d, e, ff) }), + { abc, def -> f(abc.a, abc.b, abc.c, def.a, def.b, def.c) }) + +fun IO.Companion.parMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, ioF: IO, ioG: IO, f: (A, B, C, D, E, F, G) -> H): IO = + parMapN(ctx, + parMapN(ctx, ioA, ioB, ioC, { a, b, c -> Tuple3(a, b, c) }), + parMapN(ctx, ioD, ioE, ::Tuple2), + parMapN(ctx, ioF, ioG, ::Tuple2), + { abc, de, fg -> f(abc.a, abc.b, abc.c, de.a, de.b, fg.a, fg.b) }) + +fun IO.Companion.parMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, ioF: IO, ioG: IO, ioH: IO, f: (A, B, C, D, E, F, G, H) -> I): IO = + parMapN(ctx, + parMapN(ctx, ioA, ioB, ioC, ::Tuple3), + parMapN(ctx, ioD, ioE, ioF, ::Tuple3), + parMapN(ctx, ioG, ioH, ::Tuple2), + { abc, def, gh -> f(abc.a, abc.b, abc.c, def.a, def.b, def.c, gh.a, gh.b) }) + +fun IO.Companion.parMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, ioF: IO, ioG: IO, ioH: IO, ioI: IO, f: (A, B, C, D, E, F, G, H, I) -> J): IO = + parMapN(ctx, + parMapN(ctx, ioA, ioB, ioC, ::Tuple3), + parMapN(ctx, ioD, ioE, ioF, ::Tuple3), + parMapN(ctx, ioG, ioH, ioI, ::Tuple3), + { abc, def, ghi -> f(abc.a, abc.b, abc.c, def.a, def.b, def.c, ghi.a, ghi.b, ghi.c) }) \ No newline at end of file diff --git a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/IOParallel.kt b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/ParallelUtils.kt similarity index 100% rename from modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/IOParallel.kt rename to modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/ParallelUtils.kt From bc19d808cb4ac9c48348d03bbaf9c7429acc76aa Mon Sep 17 00:00:00 2001 From: pakoito Date: Sun, 22 Jul 2018 19:18:55 +0100 Subject: [PATCH 12/26] Cleanup of unnecessary lambdas --- .../src/main/kotlin/arrow/effects/IOParallel.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IOParallel.kt b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IOParallel.kt index 96eee8f5837..4fbbb453931 100644 --- a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IOParallel.kt +++ b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IOParallel.kt @@ -21,18 +21,18 @@ fun IO.Companion.parMapN(ctx: CoroutineContext, ioA: IO, ioB: fun IO.Companion.parMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, f: (A, B, C, D, E) -> F): IO = parMapN(ctx, parMapN(ctx, ioA, ioB, ::Tuple2), - parMapN(ctx, ioC, ioD, ioE, { c, d, e -> Tuple3(c, d, e) }), + parMapN(ctx, ioC, ioD, ioE, ::Tuple3), { ab, cde -> f(ab.a, ab.b, cde.a, cde.b, cde.c) }) fun IO.Companion.parMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, ioF: IO, f: (A, B, C, D, E, F) -> G): IO = parMapN(ctx, - parMapN(ctx, ioA, ioB, ioC, { a, b, c -> Tuple3(a, b, c) }), - parMapN(ctx, ioD, ioE, ioF, { d, e, ff -> Tuple3(d, e, ff) }), + parMapN(ctx, ioA, ioB, ioC, ::Tuple3), + parMapN(ctx, ioD, ioE, ioF, ::Tuple3), { abc, def -> f(abc.a, abc.b, abc.c, def.a, def.b, def.c) }) fun IO.Companion.parMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, ioF: IO, ioG: IO, f: (A, B, C, D, E, F, G) -> H): IO = parMapN(ctx, - parMapN(ctx, ioA, ioB, ioC, { a, b, c -> Tuple3(a, b, c) }), + parMapN(ctx, ioA, ioB, ioC, ::Tuple3), parMapN(ctx, ioD, ioE, ::Tuple2), parMapN(ctx, ioF, ioG, ::Tuple2), { abc, de, fg -> f(abc.a, abc.b, abc.c, de.a, de.b, fg.a, fg.b) }) From 17bfc6301a86a354672630cc518618cc8cddbe68 Mon Sep 17 00:00:00 2001 From: pakoito Date: Sun, 22 Jul 2018 19:19:40 +0100 Subject: [PATCH 13/26] EOL --- .../arrow-effects/src/main/kotlin/arrow/effects/IOParallel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IOParallel.kt b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IOParallel.kt index 4fbbb453931..6c9f1dca438 100644 --- a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IOParallel.kt +++ b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IOParallel.kt @@ -49,4 +49,4 @@ fun IO.Companion.parMapN(ctx: CoroutineContext, i parMapN(ctx, ioA, ioB, ioC, ::Tuple3), parMapN(ctx, ioD, ioE, ioF, ::Tuple3), parMapN(ctx, ioG, ioH, ioI, ::Tuple3), - { abc, def, ghi -> f(abc.a, abc.b, abc.c, def.a, def.b, def.c, ghi.a, ghi.b, ghi.c) }) \ No newline at end of file + { abc, def, ghi -> f(abc.a, abc.b, abc.c, def.a, def.b, def.c, ghi.a, ghi.b, ghi.c) }) From 2781bededb3900d81a5877c12b8a0e6257af372f Mon Sep 17 00:00:00 2001 From: pakoito Date: Sun, 22 Jul 2018 19:21:29 +0100 Subject: [PATCH 14/26] Fix detekt --- .../arrow/effects/internal/ParallelUtils.kt | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/ParallelUtils.kt b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/ParallelUtils.kt index f9adb06887f..d80b8aa860c 100644 --- a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/ParallelUtils.kt +++ b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/ParallelUtils.kt @@ -155,16 +155,14 @@ private sealed class Treither { abstract fun fold(fa: (A) -> D, fb: (B) -> D, fc: (C) -> D): D } -private fun asyncIOContinuation(ctx: CoroutineContext, cc: (Either) -> Unit): Continuation { - return object : Continuation { - override val context: CoroutineContext = ctx +private fun asyncIOContinuation(ctx: CoroutineContext, cc: (Either) -> Unit): Continuation = object : Continuation { + override val context: CoroutineContext = ctx - override fun resume(value: A) { - cc(value.right()) - } + override fun resume(value: A) { + cc(value.right()) + } - override fun resumeWithException(exception: Throwable) { - cc(exception.left()) - } + override fun resumeWithException(exception: Throwable) { + cc(exception.left()) } } From 9ab7b5e9dd7a18e11beb37612ca6839ba26bf6b4 Mon Sep 17 00:00:00 2001 From: pakoito Date: Sun, 22 Jul 2018 19:40:32 +0100 Subject: [PATCH 15/26] Unify behavior for par2 and par3 --- infographic/arrow-infographic.txt | 86 +++++++++---------- .../arrow/effects/internal/ParallelUtils.kt | 49 ++++------- 2 files changed, 61 insertions(+), 74 deletions(-) diff --git a/infographic/arrow-infographic.txt b/infographic/arrow-infographic.txt index 52043dd8849..ff70183b42a 100644 --- a/infographic/arrow-infographic.txt +++ b/infographic/arrow-infographic.txt @@ -8,62 +8,62 @@ #fill: #64B5F6 #.typeclasses: fill=#64B5F6 visual=database bold #.instances: fill=#B9F6CA visual=class italic bold dashed +[Applicative]<-[ApplicativeError] [Functor]<-[Applicative] +[MonadDefer]<-[Async] +[Async]<-[Async Instances|IOAsyncInstance] +[Async]<-[Effect] +[Effect]<-[Effect Instances|IOEffectInstance] +[MonadError]<-[MonadDefer] +[MonadDefer]<-[MonadDefer Instances|IOMonadDeferInstance] [ApplicativeError]<-[MonadError] [Monad]<-[MonadError] -[Functor]<-[Comonad] -[Applicative]<-[ApplicativeError] -[Functor]<-[Traverse] -[Foldable]<-[Traverse] [Applicative]<-[Monad] [Semigroup]<-[Monoid] [Monad]<-[Bimonad] [Comonad]<-[Bimonad] +[Functor]<-[Comonad] +[Functor]<-[Traverse] +[Foldable]<-[Traverse] [SemigroupK]<-[MonoidK] -[MonadError]<-[MonadDefer] -[MonadDefer]<-[Async] -[Async]<-[Effect] -[Monad]<-[MonadFilter] -[FunctorFilter]<-[MonadFilter] -[Monad]<-[MonadWriter] -[Functor]<-[FunctorFilter] +[MonoidK]<-[MonoidK Instances|ListKMonoidKInstance|OptionTMonoidKInstance|SequenceKMonoidKInstance|SetKMonoidKInstance|WriterTMonoidKInstance] +[SemigroupK]<-[SemigroupK Instances|EitherSemigroupKInstance|ListKSemigroupKInstance|NonEmptyListSemigroupKInstance|OptionTSemigroupKInstance|SequenceKSemigroupKInstance|SetKSemigroupKInstance|StateTSemigroupKInstance|ValidatedSemigroupKInstance|WriterTSemigroupKInstance] +[Bimonad]<-[Bimonad Instances|EvalBimonadInstance|Function0BimonadInstance|IdBimonadInstance|NonEmptyListBimonadInstance] +[Foldable]<-[Foldable Instances|ConstFoldableInstance|EitherFoldableInstance|IdFoldableInstance|OptionFoldableInstance|TryFoldableInstance|Tuple2FoldableInstance|CoproductFoldableInstance|IorFoldableInstance|ListKFoldableInstance|MapKFoldableInstance|NonEmptyListFoldableInstance|OptionTFoldableInstance|SequenceKFoldableInstance|SetKFoldableInstance|SortedMapKFoldableInstance|ValidatedFoldableInstance] +[Traverse]<-[Traverse Instances|ConstTraverseInstance|EitherTraverseInstance|IdTraverseInstance|OptionTraverseInstance|TryTraverseInstance|Tuple2TraverseInstance|CoproductTraverseInstance|IorTraverseInstance|ListKTraverseInstance|MapKTraverseInstance|NonEmptyListTraverseInstance|OptionTTraverseInstance|SequenceKTraverseInstance|SortedMapKTraverseInstance|ValidatedTraverseInstance] +[Eq]<-[Eq Instances|ConstEqInstance|EitherEqInstance|IdEqInstance|OptionEqInstance|TryEqInstance|Tuple10EqInstance|Tuple2EqInstance|Tuple3EqInstance|Tuple4EqInstance|Tuple5EqInstance|Tuple6EqInstance|Tuple7EqInstance|Tuple8EqInstance|Tuple9EqInstance|IorEqInstance|ListKEqInstance|MapKEqInstance|NonEmptyListEqInstance|SequenceKEqInstance|SetKEqInstance|ValidatedEqInstance] +[Show]<-[Show Instances|ConstShowInstance|EitherShowInstance|IdShowInstance|OptionShowInstance|TryShowInstance|Tuple10ShowInstance|Tuple2ShowInstance|Tuple3ShowInstance|Tuple4ShowInstance|Tuple5ShowInstance|Tuple6ShowInstance|Tuple7ShowInstance|Tuple8ShowInstance|Tuple9ShowInstance|IorShowInstance|ListKShowInstance|MapKShowInstance|NonEmptyListShowInstance|SequenceKShowInstance|SetKShowInstance|SortedMapKShowInstance|ValidatedShowInstance] +[ApplicativeError]<-[ApplicativeError Instances|IOApplicativeErrorInstance|EitherApplicativeErrorInstance|OptionApplicativeErrorInstance|TryApplicativeErrorInstance|KleisliApplicativeErrorInstance|StateTApplicativeErrorInstance|ValidatedApplicativeErrorInstance] +[MonadError]<-[MonadError Instances|IOMonadErrorInstance|EitherMonadErrorInstance|OptionMonadErrorInstance|TryMonadErrorInstance|KleisliMonadErrorInstance|StateTMonadErrorInstance] +[Monoid]<-[Monoid Instances|IOMonoidInstance|ConstMonoidInstance|EitherMonoidInstance|OptionMonoidInstance|TryMonoidInstance|Tuple2MonoidInstance|ListKMonoidInstance|MapKMonoidInstance|SequenceKMonoidInstance|SetKMonoidInstance|SortedMapKMonoidInstance] +[Semigroup]<-[Semigroup Instances|IOMonoidInstance|IOSemigroupInstance|ConstSemigroupInstance|EitherSemigroupInstance|OptionSemigroupInstance|TrySemigroupInstance|ListKSemigroupInstance|MapKSemigroupInstance|NonEmptyListSemigroupInstance|SequenceKSemigroupInstance|SetKSemigroupInstance|SortedMapKSemigroupInstance] +[Comonad]<-[Comonad Instances|EvalComonadInstance|Function0ComonadInstance|IdComonadInstance|Tuple2ComonadInstance|CofreeComonadInstance|CoproductComonadInstance|NonEmptyListComonadInstance] +[Applicative]<-[Applicative Instances|IOApplicativeInstance|ConstApplicativeInstance|EitherApplicativeInstance|EvalApplicativeInstance|Function0ApplicativeInstance|Function1ApplicativeInstance|IdApplicativeInstance|OptionApplicativeInstance|TryApplicativeInstance|Tuple2ApplicativeInstance|FreeApplicativeApplicativeInstance|FreeApplicativeInstance|IorApplicativeInstance|KleisliApplicativeInstance|ListKApplicativeInstance|NonEmptyListApplicativeInstance|OptionTApplicativeInstance|SequenceKApplicativeInstance|StateTApplicativeInstance|ValidatedApplicativeInstance|WriterTApplicativeInstance] +[Functor]<-[Functor Instances|IOFunctorInstance|ConstFunctorInstance|EitherFunctorInstance|EvalFunctorInstance|Function0FunctorInstance|Function1FunctorInstance|IdFunctorInstance|OptionFunctorInstance|TryFunctorInstance|Tuple2FunctorInstance|CofreeFunctorInstance|CoyonedaFunctorInstance|FreeApplicativeFunctorInstance|FreeFunctorInstance|YonedaFunctorInstance|CoproductFunctorInstance|IorFunctorInstance|KleisliFunctorInstance|ListKFunctorInstance|MapKFunctorInstance|NonEmptyListFunctorInstance|OptionTFunctorInstance|SequenceKFunctorInstance|SortedMapKFunctorInstance|StateTFunctorInstance|ValidatedFunctorInstance|WriterTFunctorInstance] +[Monad]<-[Monad Instances|IOMonadInstance|EitherMonadInstance|EvalMonadInstance|Function0MonadInstance|Function1MonadInstance|IdMonadInstance|OptionMonadInstance|TryMonadInstance|Tuple2MonadInstance|FreeMonadInstance|IorMonadInstance|KleisliMonadInstance|ListKMonadInstance|NonEmptyListMonadInstance|OptionTMonadInstance|SequenceKMonadInstance|StateTMonadInstance|WriterTMonadInstance] [Monad]<-[MonadReader] +[MonadReader]<-[MonadReader Instances|Function1MonadReaderInstance|KleisliMonadReaderInstance] +[Functor]<-[FunctorFilter] +[FunctorFilter]<-[FunctorFilter Instances|ListKFunctorFilterInstance|OptionTFunctorFilterInstance] [Traverse]<-[TraverseFilter] [FunctorFilter]<-[TraverseFilter] -[Monad]<-[MonadState] +[TraverseFilter]<-[TraverseFilter Instances|ConstTraverseFilterInstance|OptionTraverseFilterInstance|OptionTTraverseFilterInstance] [MonadFilter]<-[MonadCombine] [Alternative]<-[MonadCombine] -[Recursive]<-[Birecursive] -[Corecursive]<-[Birecursive] -[MonoidK]<-[MonoidK Instances|OptionTMonoidKInstance|ListKMonoidKInstance|SetKMonoidKInstance|SequenceKMonoidKInstance|WriterTMonoidKInstance] -[SemigroupK]<-[SemigroupK Instances|EitherSemigroupKInstance|OptionTSemigroupKInstance|SequenceKSemigroupKInstance|WriterTSemigroupKInstance|StateTSemigroupKInstance|NonEmptyListSemigroupKInstance|SetKSemigroupKInstance|ValidatedSemigroupKInstance|ListKSemigroupKInstance] -[Eq]<-[Eq Instances|Tuple7EqInstance|Tuple5EqInstance|Tuple2EqInstance|EitherEqInstance|Tuple9EqInstance|Tuple3EqInstance|TryEqInstance|Tuple6EqInstance|ConstEqInstance|IdEqInstance|OptionEqInstance|Tuple10EqInstance|Tuple4EqInstance|Tuple8EqInstance|NonEmptyListEqInstance|ListKEqInstance|SetKEqInstance|IorEqInstance|SequenceKEqInstance|ValidatedEqInstance|MapKEqInstance] -[Show]<-[Show Instances|Tuple3ShowInstance|IdShowInstance|OptionShowInstance|Tuple8ShowInstance|ConstShowInstance|EitherShowInstance|Tuple4ShowInstance|TryShowInstance|Tuple2ShowInstance|Tuple9ShowInstance|Tuple6ShowInstance|Tuple5ShowInstance|Tuple10ShowInstance|Tuple7ShowInstance|IorShowInstance|MapKShowInstance|SetKShowInstance|SortedMapKShowInstance|SequenceKShowInstance|ListKShowInstance|ValidatedShowInstance|NonEmptyListShowInstance] -[Bimonad]<-[Bimonad Instances|EvalBimonadInstance|Function0BimonadInstance|IdBimonadInstance|NonEmptyListBimonadInstance] -[Traverse]<-[Traverse Instances|EitherTraverseInstance|OptionTraverseInstance|Tuple2TraverseInstance|TryTraverseInstance|IdTraverseInstance|ConstTraverseInstance|IorTraverseInstance|MapKTraverseInstance|ValidatedTraverseInstance|CoproductTraverseInstance|ListKTraverseInstance|SequenceKTraverseInstance|OptionTTraverseInstance|NonEmptyListTraverseInstance|SortedMapKTraverseInstance|FlowableKTraverseInstance|ObservableKTraverseInstance|FluxKTraverseInstance] -[Foldable]<-[Foldable Instances|ConstFoldableInstance|IdFoldableInstance|EitherFoldableInstance|TryFoldableInstance|OptionFoldableInstance|Tuple2FoldableInstance|OptionTFoldableInstance|MapKFoldableInstance|SetKFoldableInstance|ListKFoldableInstance|NonEmptyListFoldableInstance|IorFoldableInstance|SequenceKFoldableInstance|SortedMapKFoldableInstance|CoproductFoldableInstance|ValidatedFoldableInstance|MaybeKFoldableInstance|FlowableKFoldableInstance|ObservableKFoldableInstance|FluxKFoldableInstance] -[Comonad]<-[Comonad Instances|Tuple2ComonadInstance|IdComonadInstance|Function0ComonadInstance|EvalComonadInstance|CoproductComonadInstance|NonEmptyListComonadInstance|CofreeComonadInstance] -[MonadFilter]<-[MonadFilter Instances|WriterTMonadFilterInstance|OptionMonadFilterInstance|ListKMonadFilterInstance] -[MonadWriter]<-[MonadWriter Instances|WriterTMonadWriterInstance] -[FunctorFilter]<-[FunctorFilter Instances|ListKFunctorFilterInstance|OptionTFunctorFilterInstance] -[MonadReader]<-[MonadReader Instances|Function1MonadReaderInstance|KleisliMonadReaderInstance] -[TraverseFilter]<-[TraverseFilter Instances|OptionTraverseFilterInstance|OptionTTraverseFilterInstance|ConstTraverseFilterInstance] -[MonadState]<-[MonadState Instances|StateTMonadStateInstance] [MonadCombine]<-[MonadCombine Instances|ListKMonadCombineInstance|StateTMonadCombineInstance] -[FilterIndex]<-[FilterIndex Instances|SequenceKFilterIndexInstance|MapKFilterIndexInstance|ListKFilterIndexInstance|NonEmptyListFilterIndexInstance] -[Index]<-[Index Instances|MapKIndexInstance|SequenceKIndexInstance|NonEmptyListIndexInstance|ListKIndexInstance|NonEmptyListFilterIndexInstance|MapKFilterIndexInstance|SequenceKFilterIndexInstance|ListKFilterIndexInstance] +[Monad]<-[MonadState] +[MonadState]<-[MonadState Instances|StateTMonadStateInstance] +[Monad]<-[MonadFilter] +[FunctorFilter]<-[MonadFilter] +[MonadFilter]<-[MonadFilter Instances|ListKMonadFilterInstance|OptionMonadFilterInstance|WriterTMonadFilterInstance] +[Monad]<-[MonadWriter] +[MonadWriter]<-[MonadWriter Instances|WriterTMonadWriterInstance] +[FilterIndex]<-[FilterIndex Instances|ListKFilterIndexInstance|MapKFilterIndexInstance|NonEmptyListFilterIndexInstance|SequenceKFilterIndexInstance] +[Index]<-[Index Instances|ListKIndexInstance|MapKIndexInstance|NonEmptyListIndexInstance|SequenceKIndexInstance] [At]<-[At Instances|MapKAtInstance|SetKAtInstance] -[Each]<-[Each Instances|ListKEachInstance|OptionEachInstance|EitherEachInstance|TryEachInstance|SequenceKEachInstance|NonEmptyListEachInstance|MapKEachInstance] -[Corecursive]<-[Corecursive Instances|MuCorecursiveInstance|FixCorecursiveInstance|NuCorecursiveInstance] +[Each]<-[Each Instances|EitherEachInstance|ListKEachInstance|MapKEachInstance|NonEmptyListEachInstance|OptionEachInstance|SequenceKEachInstance|TryEachInstance] +[Recursive]<-[Birecursive] +[Corecursive]<-[Birecursive] [Birecursive]<-[Birecursive Instances|FixBirecursiveInstance|MuBirecursiveInstance|NuBirecursiveInstance] -[Recursive]<-[Recursive Instances|MuRecursiveInstance|FixRecursiveInstance|NuRecursiveInstance] -[MonadDefer]<-[MonadDefer Instances|IOMonadDeferInstance|DeferredKMonadDeferInstance|ObservableKMonadDeferInstance|MaybeKMonadDeferInstance|SingleKMonadDeferInstance|FlowableKMonadDeferInstance|FluxKMonadDeferInstance|MonoKMonadDeferInstance] -[Async]<-[Async Instances|IOAsyncInstance|DeferredKAsyncInstance|MaybeKAsyncInstance|SingleKAsyncInstance|ObservableKAsyncInstance|FlowableKAsyncInstance|MonoKAsyncInstance|FluxKAsyncInstance] -[Effect]<-[Effect Instances|IOEffectInstance|DeferredKEffectInstance|MaybeKEffectInstance|ObservableKEffectInstance|SingleKEffectInstance|FlowableKEffectInstance|FluxKEffectInstance|MonoKEffectInstance] -[Semigroup]<-[Semigroup Instances|ConstSemigroupInstance|OptionSemigroupInstance|SortedMapKSemigroupInstance|ListKSemigroupInstance|MapKSemigroupInstance|SetKSemigroupInstance|SequenceKSemigroupInstance|NonEmptyListSemigroupInstance|IOSemigroupInstance|IOMonoidInstance|EitherSemigroupInstance|TrySemigroupInstance] -[Monoid]<-[Monoid Instances|OptionMonoidInstance|ConstMonoidInstance|Tuple2MonoidInstance|SequenceKMonoidInstance|SortedMapKMonoidInstance|ListKMonoidInstance|MapKMonoidInstance|SetKMonoidInstance|IOMonoidInstance|TryMonoidInstance|EitherMonoidInstance] -[MonadError]<-[MonadError Instances|EitherMonadErrorInstance|TryMonadErrorInstance|OptionMonadErrorInstance|StateTMonadErrorInstance|KleisliMonadErrorInstance|IOMonadErrorInstance|DeferredKMonadErrorInstance|MaybeKMonadErrorInstance|ObservableKMonadErrorInstance|SingleKMonadErrorInstance|FlowableKMonadErrorInstance|FluxKMonadErrorInstance|MonoKMonadErrorInstance] -[ApplicativeError]<-[ApplicativeError Instances|OptionApplicativeErrorInstance|TryApplicativeErrorInstance|EitherApplicativeErrorInstance|StateTApplicativeErrorInstance|KleisliApplicativeErrorInstance|ValidatedApplicativeErrorInstance|IOApplicativeErrorInstance|DeferredKApplicativeErrorInstance|SingleKApplicativeErrorInstance|ObservableKApplicativeErrorInstance|MaybeKApplicativeErrorInstance|FlowableKApplicativeErrorInstance|FluxKApplicativeErrorInstance|MonoKApplicativeErrorInstance] -[Applicative]<-[Applicative Instances|Function1ApplicativeInstance|IdApplicativeInstance|Tuple2ApplicativeInstance|Function0ApplicativeInstance|ConstApplicativeInstance|OptionApplicativeInstance|EvalApplicativeInstance|EitherApplicativeInstance|TryApplicativeInstance|ListKApplicativeInstance|ValidatedApplicativeInstance|IorApplicativeInstance|WriterTApplicativeInstance|NonEmptyListApplicativeInstance|KleisliApplicativeInstance|StateTApplicativeInstance|SequenceKApplicativeInstance|OptionTApplicativeInstance|IOApplicativeInstance|FreeApplicativeInstance|FreeApplicativeApplicativeInstance|DeferredKApplicativeInstance|MaybeKApplicativeInstance|ObservableKApplicativeInstance|FlowableKApplicativeInstance|SingleKApplicativeInstance|FluxKApplicativeInstance|MonoKApplicativeInstance] -[Monad]<-[Monad Instances|Function1MonadInstance|Function0MonadInstance|EitherMonadInstance|IdMonadInstance|TryMonadInstance|Tuple2MonadInstance|EvalMonadInstance|OptionMonadInstance|NonEmptyListMonadInstance|OptionTMonadInstance|WriterTMonadInstance|SequenceKMonadInstance|IorMonadInstance|StateTMonadInstance|KleisliMonadInstance|ListKMonadInstance|IOMonadInstance|FreeMonadInstance|DeferredKMonadInstance|SingleKMonadInstance|MaybeKMonadInstance|FlowableKMonadInstance|ObservableKMonadInstance|FluxKMonadInstance|MonoKMonadInstance] -[Functor]<-[Functor Instances|ConstFunctorInstance|OptionFunctorInstance|Function1FunctorInstance|Tuple2FunctorInstance|TryFunctorInstance|IdFunctorInstance|EvalFunctorInstance|EitherFunctorInstance|Function0FunctorInstance|MapKFunctorInstance|SortedMapKFunctorInstance|CoproductFunctorInstance|OptionTFunctorInstance|IorFunctorInstance|StateTFunctorInstance|ValidatedFunctorInstance|SequenceKFunctorInstance|WriterTFunctorInstance|ListKFunctorInstance|KleisliFunctorInstance|NonEmptyListFunctorInstance|IOFunctorInstance|CofreeFunctorInstance|YonedaFunctorInstance|FreeFunctorInstance|CoyonedaFunctorInstance|FreeApplicativeFunctorInstance|DeferredKFunctorInstance|MaybeKFunctorInstance|SingleKFunctorInstance|FlowableKFunctorInstance|ObservableKFunctorInstance|MonoKFunctorInstance|FluxKFunctorInstance|IntListPatternFunctorInstance] \ No newline at end of file +[Corecursive]<-[Corecursive Instances|FixCorecursiveInstance|MuCorecursiveInstance|NuCorecursiveInstance] +[Recursive]<-[Recursive Instances|FixRecursiveInstance|MuRecursiveInstance|NuRecursiveInstance] \ No newline at end of file diff --git a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/ParallelUtils.kt b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/ParallelUtils.kt index d80b8aa860c..fadf1dffdb8 100644 --- a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/ParallelUtils.kt +++ b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/ParallelUtils.kt @@ -1,9 +1,6 @@ package arrow.effects.internal -import arrow.core.Either -import arrow.core.Tuple3 -import arrow.core.left -import arrow.core.right +import arrow.core.* import arrow.effects.IO import arrow.effects.typeclasses.Proc import kotlin.coroutines.experimental.Continuation @@ -68,30 +65,23 @@ private fun parContinuation(ctx: CoroutineContext, f: (A, B) -> C, cc: object : Continuation> { override val context: CoroutineContext = ctx - var intermediate: Either? = null + var intermediate: Tuple2 = null toT null override fun resume(value: Either) = synchronized(this) { - val result = intermediate - if (null == result) { - intermediate = value - } else { - value.fold({ a -> - result.fold({ - // Resumed twice on the same side, updating - intermediate = value - }, { b -> - cc.resume(f(a, b)) - }) - }, { b -> - result.fold({ a -> - cc.resume(f(a, b)) - }, { - // Resumed twice on the same side, updating - intermediate = value - }) - }) - } + val resA = intermediate.a + val resB = intermediate.b + value.fold({ a -> + intermediate = a toT resB + if (resB != null) { + cc.resume(f(a, resB)) + } + }, { b -> + intermediate = resA toT b + if (resA != null) { + cc.resume(f(resA, b)) + } + }) } override fun resumeWithException(exception: Throwable) { @@ -111,22 +101,19 @@ private fun triContinuation(ctx: CoroutineContext, f: (A, B, C) -> val resB = intermediate.b val resC = intermediate.c value.fold({ a -> + intermediate = Tuple3(a, resB, resC) if (resB != null && resC != null) { cc.resume(f(a, resB, resC)) - } else { - intermediate = Tuple3(a, resB, resC) } }, { b -> + intermediate = Tuple3(resA, b, resC) if (resA != null && resC != null) { cc.resume(f(resA, b, resC)) - } else { - intermediate = Tuple3(resA, b, resC) } }, { c -> + intermediate = Tuple3(resA, resB, c) if (resA != null && resB != null) { cc.resume(f(resA, resB, c)) - } else { - intermediate = Tuple3(resA, resB, c) } }) } From caade046d4f59b7aca1e6f7091ae00fa7f1063bb Mon Sep 17 00:00:00 2001 From: pakoito Date: Sun, 22 Jul 2018 19:44:30 +0100 Subject: [PATCH 16/26] Fix formatting --- .../arrow/effects/internal/ParallelUtils.kt | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/ParallelUtils.kt b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/ParallelUtils.kt index fadf1dffdb8..5ceb3c201cf 100644 --- a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/ParallelUtils.kt +++ b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/ParallelUtils.kt @@ -142,14 +142,15 @@ private sealed class Treither { abstract fun fold(fa: (A) -> D, fb: (B) -> D, fc: (C) -> D): D } -private fun asyncIOContinuation(ctx: CoroutineContext, cc: (Either) -> Unit): Continuation = object : Continuation { - override val context: CoroutineContext = ctx +private fun asyncIOContinuation(ctx: CoroutineContext, cc: (Either) -> Unit): Continuation = + object : Continuation { + override val context: CoroutineContext = ctx - override fun resume(value: A) { - cc(value.right()) - } + override fun resume(value: A) { + cc(value.right()) + } - override fun resumeWithException(exception: Throwable) { - cc(exception.left()) + override fun resumeWithException(exception: Throwable) { + cc(exception.left()) + } } -} From 510a741537e96ba914de068801ae224269017557 Mon Sep 17 00:00:00 2001 From: pakoito Date: Sun, 22 Jul 2018 20:02:47 +0100 Subject: [PATCH 17/26] Add a single parallelism test --- .../src/test/kotlin/arrow/effects/IOTest.kt | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt b/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt index c3164413b50..ed406cf5bf2 100644 --- a/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt +++ b/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt @@ -13,6 +13,7 @@ import io.kotlintest.KTestJUnitRunner import io.kotlintest.matchers.fail import io.kotlintest.matchers.shouldBe import io.kotlintest.matchers.shouldEqual +import kotlinx.coroutines.experimental.newSingleThreadContext import org.junit.runner.RunWith @RunWith(KTestJUnitRunner::class) @@ -242,6 +243,32 @@ class IOTest : UnitSpec() { result shouldBe None } + "parallel execution makes all IOs start at the same time" { + val order = mutableListOf() + + fun makePar(num: Long) = + IO.async { cc -> + IO.unit.continueOn(newSingleThreadContext("$num")) + // Sleep according to my number + .map { + Thread.sleep(num * 20) + it + } + // Add myself to order list + .map { + order.add(num) + num + }.unsafeRunAsync(cc) + } + + val result = + IO.parMapN(newSingleThreadContext("all"), makePar(6), makePar(3), makePar(2), makePar(4), makePar(1), makePar(5)) + { six, tree, two, four, one, five -> listOf(six, tree, two, four, one, five) } + .unsafeRunSync() + result shouldBe listOf(6L, 3, 2, 4, 1, 5) + order.toList() shouldBe listOf(1L, 2, 3, 4, 5, 6) + } + "IO.binding should for comprehend over IO" { val result = IO.monad().binding { val x = IO.just(1).bind() From 30b0902063d9f64bb312ee3e842e9541f394f707 Mon Sep 17 00:00:00 2001 From: pakoito Date: Sun, 22 Jul 2018 20:25:12 +0100 Subject: [PATCH 18/26] Add a new IO constructor for async blocks --- infographic/arrow-infographic.txt | 22 +++++++++---------- .../src/main/kotlin/arrow/effects/IO.kt | 3 +++ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/infographic/arrow-infographic.txt b/infographic/arrow-infographic.txt index ff70183b42a..fe6001a9437 100644 --- a/infographic/arrow-infographic.txt +++ b/infographic/arrow-infographic.txt @@ -11,11 +11,8 @@ [Applicative]<-[ApplicativeError] [Functor]<-[Applicative] [MonadDefer]<-[Async] -[Async]<-[Async Instances|IOAsyncInstance] [Async]<-[Effect] -[Effect]<-[Effect Instances|IOEffectInstance] [MonadError]<-[MonadDefer] -[MonadDefer]<-[MonadDefer Instances|IOMonadDeferInstance] [ApplicativeError]<-[MonadError] [Monad]<-[MonadError] [Applicative]<-[Monad] @@ -33,14 +30,7 @@ [Traverse]<-[Traverse Instances|ConstTraverseInstance|EitherTraverseInstance|IdTraverseInstance|OptionTraverseInstance|TryTraverseInstance|Tuple2TraverseInstance|CoproductTraverseInstance|IorTraverseInstance|ListKTraverseInstance|MapKTraverseInstance|NonEmptyListTraverseInstance|OptionTTraverseInstance|SequenceKTraverseInstance|SortedMapKTraverseInstance|ValidatedTraverseInstance] [Eq]<-[Eq Instances|ConstEqInstance|EitherEqInstance|IdEqInstance|OptionEqInstance|TryEqInstance|Tuple10EqInstance|Tuple2EqInstance|Tuple3EqInstance|Tuple4EqInstance|Tuple5EqInstance|Tuple6EqInstance|Tuple7EqInstance|Tuple8EqInstance|Tuple9EqInstance|IorEqInstance|ListKEqInstance|MapKEqInstance|NonEmptyListEqInstance|SequenceKEqInstance|SetKEqInstance|ValidatedEqInstance] [Show]<-[Show Instances|ConstShowInstance|EitherShowInstance|IdShowInstance|OptionShowInstance|TryShowInstance|Tuple10ShowInstance|Tuple2ShowInstance|Tuple3ShowInstance|Tuple4ShowInstance|Tuple5ShowInstance|Tuple6ShowInstance|Tuple7ShowInstance|Tuple8ShowInstance|Tuple9ShowInstance|IorShowInstance|ListKShowInstance|MapKShowInstance|NonEmptyListShowInstance|SequenceKShowInstance|SetKShowInstance|SortedMapKShowInstance|ValidatedShowInstance] -[ApplicativeError]<-[ApplicativeError Instances|IOApplicativeErrorInstance|EitherApplicativeErrorInstance|OptionApplicativeErrorInstance|TryApplicativeErrorInstance|KleisliApplicativeErrorInstance|StateTApplicativeErrorInstance|ValidatedApplicativeErrorInstance] -[MonadError]<-[MonadError Instances|IOMonadErrorInstance|EitherMonadErrorInstance|OptionMonadErrorInstance|TryMonadErrorInstance|KleisliMonadErrorInstance|StateTMonadErrorInstance] -[Monoid]<-[Monoid Instances|IOMonoidInstance|ConstMonoidInstance|EitherMonoidInstance|OptionMonoidInstance|TryMonoidInstance|Tuple2MonoidInstance|ListKMonoidInstance|MapKMonoidInstance|SequenceKMonoidInstance|SetKMonoidInstance|SortedMapKMonoidInstance] -[Semigroup]<-[Semigroup Instances|IOMonoidInstance|IOSemigroupInstance|ConstSemigroupInstance|EitherSemigroupInstance|OptionSemigroupInstance|TrySemigroupInstance|ListKSemigroupInstance|MapKSemigroupInstance|NonEmptyListSemigroupInstance|SequenceKSemigroupInstance|SetKSemigroupInstance|SortedMapKSemigroupInstance] [Comonad]<-[Comonad Instances|EvalComonadInstance|Function0ComonadInstance|IdComonadInstance|Tuple2ComonadInstance|CofreeComonadInstance|CoproductComonadInstance|NonEmptyListComonadInstance] -[Applicative]<-[Applicative Instances|IOApplicativeInstance|ConstApplicativeInstance|EitherApplicativeInstance|EvalApplicativeInstance|Function0ApplicativeInstance|Function1ApplicativeInstance|IdApplicativeInstance|OptionApplicativeInstance|TryApplicativeInstance|Tuple2ApplicativeInstance|FreeApplicativeApplicativeInstance|FreeApplicativeInstance|IorApplicativeInstance|KleisliApplicativeInstance|ListKApplicativeInstance|NonEmptyListApplicativeInstance|OptionTApplicativeInstance|SequenceKApplicativeInstance|StateTApplicativeInstance|ValidatedApplicativeInstance|WriterTApplicativeInstance] -[Functor]<-[Functor Instances|IOFunctorInstance|ConstFunctorInstance|EitherFunctorInstance|EvalFunctorInstance|Function0FunctorInstance|Function1FunctorInstance|IdFunctorInstance|OptionFunctorInstance|TryFunctorInstance|Tuple2FunctorInstance|CofreeFunctorInstance|CoyonedaFunctorInstance|FreeApplicativeFunctorInstance|FreeFunctorInstance|YonedaFunctorInstance|CoproductFunctorInstance|IorFunctorInstance|KleisliFunctorInstance|ListKFunctorInstance|MapKFunctorInstance|NonEmptyListFunctorInstance|OptionTFunctorInstance|SequenceKFunctorInstance|SortedMapKFunctorInstance|StateTFunctorInstance|ValidatedFunctorInstance|WriterTFunctorInstance] -[Monad]<-[Monad Instances|IOMonadInstance|EitherMonadInstance|EvalMonadInstance|Function0MonadInstance|Function1MonadInstance|IdMonadInstance|OptionMonadInstance|TryMonadInstance|Tuple2MonadInstance|FreeMonadInstance|IorMonadInstance|KleisliMonadInstance|ListKMonadInstance|NonEmptyListMonadInstance|OptionTMonadInstance|SequenceKMonadInstance|StateTMonadInstance|WriterTMonadInstance] [Monad]<-[MonadReader] [MonadReader]<-[MonadReader Instances|Function1MonadReaderInstance|KleisliMonadReaderInstance] [Functor]<-[FunctorFilter] @@ -66,4 +56,14 @@ [Corecursive]<-[Birecursive] [Birecursive]<-[Birecursive Instances|FixBirecursiveInstance|MuBirecursiveInstance|NuBirecursiveInstance] [Corecursive]<-[Corecursive Instances|FixCorecursiveInstance|MuCorecursiveInstance|NuCorecursiveInstance] -[Recursive]<-[Recursive Instances|FixRecursiveInstance|MuRecursiveInstance|NuRecursiveInstance] \ No newline at end of file +[Recursive]<-[Recursive Instances|FixRecursiveInstance|MuRecursiveInstance|NuRecursiveInstance] +[Async]<-[Async Instances|IOAsyncInstance] +[Effect]<-[Effect Instances|IOEffectInstance] +[MonadDefer]<-[MonadDefer Instances|IOMonadDeferInstance] +[ApplicativeError]<-[ApplicativeError Instances|IOApplicativeErrorInstance|EitherApplicativeErrorInstance|OptionApplicativeErrorInstance|TryApplicativeErrorInstance|KleisliApplicativeErrorInstance|StateTApplicativeErrorInstance|ValidatedApplicativeErrorInstance] +[MonadError]<-[MonadError Instances|IOMonadErrorInstance|EitherMonadErrorInstance|OptionMonadErrorInstance|TryMonadErrorInstance|KleisliMonadErrorInstance|StateTMonadErrorInstance] +[Monoid]<-[Monoid Instances|IOMonoidInstance|ConstMonoidInstance|EitherMonoidInstance|OptionMonoidInstance|TryMonoidInstance|Tuple2MonoidInstance|ListKMonoidInstance|MapKMonoidInstance|SequenceKMonoidInstance|SetKMonoidInstance|SortedMapKMonoidInstance] +[Semigroup]<-[Semigroup Instances|IOMonoidInstance|IOSemigroupInstance|ConstSemigroupInstance|EitherSemigroupInstance|OptionSemigroupInstance|TrySemigroupInstance|ListKSemigroupInstance|MapKSemigroupInstance|NonEmptyListSemigroupInstance|SequenceKSemigroupInstance|SetKSemigroupInstance|SortedMapKSemigroupInstance] +[Applicative]<-[Applicative Instances|IOApplicativeInstance|ConstApplicativeInstance|EitherApplicativeInstance|EvalApplicativeInstance|Function0ApplicativeInstance|Function1ApplicativeInstance|IdApplicativeInstance|OptionApplicativeInstance|TryApplicativeInstance|Tuple2ApplicativeInstance|FreeApplicativeApplicativeInstance|FreeApplicativeInstance|IorApplicativeInstance|KleisliApplicativeInstance|ListKApplicativeInstance|NonEmptyListApplicativeInstance|OptionTApplicativeInstance|SequenceKApplicativeInstance|StateTApplicativeInstance|ValidatedApplicativeInstance|WriterTApplicativeInstance] +[Functor]<-[Functor Instances|IOFunctorInstance|ConstFunctorInstance|EitherFunctorInstance|EvalFunctorInstance|Function0FunctorInstance|Function1FunctorInstance|IdFunctorInstance|OptionFunctorInstance|TryFunctorInstance|Tuple2FunctorInstance|CofreeFunctorInstance|CoyonedaFunctorInstance|FreeApplicativeFunctorInstance|FreeFunctorInstance|YonedaFunctorInstance|CoproductFunctorInstance|IorFunctorInstance|KleisliFunctorInstance|ListKFunctorInstance|MapKFunctorInstance|NonEmptyListFunctorInstance|OptionTFunctorInstance|SequenceKFunctorInstance|SortedMapKFunctorInstance|StateTFunctorInstance|ValidatedFunctorInstance|WriterTFunctorInstance] +[Monad]<-[Monad Instances|IOMonadInstance|EitherMonadInstance|EvalMonadInstance|Function0MonadInstance|Function1MonadInstance|IdMonadInstance|OptionMonadInstance|TryMonadInstance|Tuple2MonadInstance|FreeMonadInstance|IorMonadInstance|KleisliMonadInstance|ListKMonadInstance|NonEmptyListMonadInstance|OptionTMonadInstance|SequenceKMonadInstance|StateTMonadInstance|WriterTMonadInstance] \ No newline at end of file diff --git a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IO.kt b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IO.kt index f2d6833aabb..ac889a29883 100644 --- a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IO.kt +++ b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IO.kt @@ -36,6 +36,9 @@ sealed class IO : IOOf { } } + fun async(ctx: CoroutineContext, f: () -> A): IO = + IO.unit.continueOn(ctx).flatMap { Pure(f()) } + val unit: IO = just(Unit) From 266347e6c74f73a059cccb8e38dd59c4447961c4 Mon Sep 17 00:00:00 2001 From: pakoito Date: Sun, 22 Jul 2018 20:25:23 +0100 Subject: [PATCH 19/26] Add two more parallelism tests --- .../src/test/kotlin/arrow/effects/IOTest.kt | 59 +++++++++++++++---- 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt b/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt index ed406cf5bf2..9eb13ffabc7 100644 --- a/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt +++ b/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt @@ -247,18 +247,13 @@ class IOTest : UnitSpec() { val order = mutableListOf() fun makePar(num: Long) = - IO.async { cc -> - IO.unit.continueOn(newSingleThreadContext("$num")) - // Sleep according to my number - .map { - Thread.sleep(num * 20) - it - } - // Add myself to order list - .map { - order.add(num) - num - }.unsafeRunAsync(cc) + IO.async(newSingleThreadContext("$num")) { + // Sleep according to my number + Thread.sleep(num * 20) + }.map { + // Add myself to order list + order.add(num) + num } val result = @@ -269,6 +264,46 @@ class IOTest : UnitSpec() { order.toList() shouldBe listOf(1L, 2, 3, 4, 5, 6) } + "parallel execution preserves order for synchronous IOs" { + val order = mutableListOf() + + fun IO.order() = + map { + order.add(it) + it + } + + fun makePar(num: Long) = + IO.async(newSingleThreadContext("$num")) { + // Sleep according to my number + Thread.sleep(num * 20) + num + }.order() + + val result = + IO.parMapN(newSingleThreadContext("all"), makePar(6), IO.just(1L).order(), makePar(4), IO.defer { IO.just(2L) }.order(), makePar(5), IO { 3L }.order()) + { six, tree, two, four, one, five -> listOf(six, tree, two, four, one, five) } + .unsafeRunSync() + result shouldBe listOf(6L, 1, 4, 2, 5, 3) + order.toList() shouldBe listOf(1L, 2, 3, 4, 5, 6) + } + + "parallel mapping is done in the expected CoroutineContext" { + fun makePar(num: Long) = + IO.async(newSingleThreadContext("$num")) { + // Sleep according to my number + Thread.sleep(num * 20) + num + } + + val result = + IO.parMapN(newSingleThreadContext("all"), makePar(6), IO.just(1L), makePar(4), IO.defer { IO.just(2L) }, makePar(5), IO { 3L }) + { _, _, _, _, _, _ -> + Thread.currentThread().name + }.unsafeRunSync() + result shouldBe "all" + } + "IO.binding should for comprehend over IO" { val result = IO.monad().binding { val x = IO.just(1).bind() From 4da80376f08017f666084a2515c0313c4801d492 Mon Sep 17 00:00:00 2001 From: pakoito Date: Sun, 22 Jul 2018 20:26:43 +0100 Subject: [PATCH 20/26] Formatting --- .../arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt b/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt index 9eb13ffabc7..984869e2447 100644 --- a/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt +++ b/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt @@ -260,6 +260,7 @@ class IOTest : UnitSpec() { IO.parMapN(newSingleThreadContext("all"), makePar(6), makePar(3), makePar(2), makePar(4), makePar(1), makePar(5)) { six, tree, two, four, one, five -> listOf(six, tree, two, four, one, five) } .unsafeRunSync() + result shouldBe listOf(6L, 3, 2, 4, 1, 5) order.toList() shouldBe listOf(1L, 2, 3, 4, 5, 6) } @@ -284,6 +285,7 @@ class IOTest : UnitSpec() { IO.parMapN(newSingleThreadContext("all"), makePar(6), IO.just(1L).order(), makePar(4), IO.defer { IO.just(2L) }.order(), makePar(5), IO { 3L }.order()) { six, tree, two, four, one, five -> listOf(six, tree, two, four, one, five) } .unsafeRunSync() + result shouldBe listOf(6L, 1, 4, 2, 5, 3) order.toList() shouldBe listOf(1L, 2, 3, 4, 5, 6) } @@ -301,6 +303,7 @@ class IOTest : UnitSpec() { { _, _, _, _, _, _ -> Thread.currentThread().name }.unsafeRunSync() + result shouldBe "all" } From 5c136378e58711fae5a0bd3fb457d23f4309fb7c Mon Sep 17 00:00:00 2001 From: pakoito Date: Sun, 22 Jul 2018 20:33:22 +0100 Subject: [PATCH 21/26] Fix consistency --- .../src/main/kotlin/arrow/effects/IOParallel.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IOParallel.kt b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IOParallel.kt index 6c9f1dca438..cdf1612a610 100644 --- a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IOParallel.kt +++ b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IOParallel.kt @@ -20,9 +20,9 @@ fun IO.Companion.parMapN(ctx: CoroutineContext, ioA: IO, ioB: fun IO.Companion.parMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, f: (A, B, C, D, E) -> F): IO = parMapN(ctx, - parMapN(ctx, ioA, ioB, ::Tuple2), - parMapN(ctx, ioC, ioD, ioE, ::Tuple3), - { ab, cde -> f(ab.a, ab.b, cde.a, cde.b, cde.c) }) + parMapN(ctx, ioA, ioB, ioC, ::Tuple3), + parMapN(ctx, ioD, ioE, ::Tuple2), + { abc, de -> f(abc.a, abc.b, abc.c, de.a, de.b) }) fun IO.Companion.parMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, ioF: IO, f: (A, B, C, D, E, F) -> G): IO = parMapN(ctx, From d940fa0a018350c821a2c175c792b0c044b01d95 Mon Sep 17 00:00:00 2001 From: pakoito Date: Sun, 22 Jul 2018 20:40:27 +0100 Subject: [PATCH 22/26] Add test for parallelism of defer and suspend --- .../arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt b/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt index 984869e2447..8b5a7fc8358 100644 --- a/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt +++ b/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt @@ -307,6 +307,14 @@ class IOTest : UnitSpec() { result shouldBe "all" } + "parallel IO#defer and IO#suspend is done in the expected CoroutineContext" { + val result = + IO.parMapN(newSingleThreadContext("here"), IO { Thread.currentThread().name }, IO.defer { IO.just(Thread.currentThread().name) }, ::Tuple2) + .unsafeRunSync() + + result shouldBe ("here" toT "here") + } + "IO.binding should for comprehend over IO" { val result = IO.monad().binding { val x = IO.just(1).bind() From 9b99458a727a768b2504f5a26713fbc268e80cc1 Mon Sep 17 00:00:00 2001 From: pakoito Date: Sun, 22 Jul 2018 20:43:19 +0100 Subject: [PATCH 23/26] Add test for parallelism of async --- .../src/test/kotlin/arrow/effects/IOTest.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt b/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt index 8b5a7fc8358..069ebd93a35 100644 --- a/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt +++ b/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt @@ -307,12 +307,14 @@ class IOTest : UnitSpec() { result shouldBe "all" } - "parallel IO#defer and IO#suspend is done in the expected CoroutineContext" { + "parallel IO#defer, IO#suspend and IO#async are run in the expected CoroutineContext" { val result = - IO.parMapN(newSingleThreadContext("here"), IO { Thread.currentThread().name }, IO.defer { IO.just(Thread.currentThread().name) }, ::Tuple2) - .unsafeRunSync() + IO.parMapN(newSingleThreadContext("here"), + IO { Thread.currentThread().name }, IO.defer { IO.just(Thread.currentThread().name) }, IO.async { it(Thread.currentThread().name.right()) }, + ::Tuple3) + .unsafeRunSync() - result shouldBe ("here" toT "here") + result shouldBe Tuple3("here", "here", "here") } "IO.binding should for comprehend over IO" { From 5fafeb911040140996c6a7ade0031818d3879ee2 Mon Sep 17 00:00:00 2001 From: pakoito Date: Sun, 22 Jul 2018 20:58:09 +0100 Subject: [PATCH 24/26] Rename parMap to parallelMap for consistency --- .../main/kotlin/arrow/effects/IOParallel.kt | 66 +++++++++---------- .../arrow/effects/internal/ParallelUtils.kt | 4 +- .../src/test/kotlin/arrow/effects/IOTest.kt | 8 +-- 3 files changed, 39 insertions(+), 39 deletions(-) diff --git a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IOParallel.kt b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IOParallel.kt index cdf1612a610..cc0aec024df 100644 --- a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IOParallel.kt +++ b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IOParallel.kt @@ -2,51 +2,51 @@ package arrow.effects import arrow.core.Tuple2 import arrow.core.Tuple3 -import arrow.effects.internal.par2 -import arrow.effects.internal.par3 +import arrow.effects.internal.parMap2 +import arrow.effects.internal.parMap3 import kotlin.coroutines.experimental.CoroutineContext -fun IO.Companion.parMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, f: (A, B) -> C): IO = - IO.async(par2(ctx, ioA, ioB, f)) +fun IO.Companion.parallelMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, f: (A, B) -> C): IO = + IO.async(parMap2(ctx, ioA, ioB, f)) -fun IO.Companion.parMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, f: (A, B, C) -> D): IO = - IO.async(par3(ctx, ioA, ioB, ioC, f)) +fun IO.Companion.parallelMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, f: (A, B, C) -> D): IO = + IO.async(parMap3(ctx, ioA, ioB, ioC, f)) -fun IO.Companion.parMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, f: (A, B, C, D) -> E): IO = - parMapN(ctx, - parMapN(ctx, ioA, ioB, ::Tuple2), - parMapN(ctx, ioC, ioD, ::Tuple2), +fun IO.Companion.parallelMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, f: (A, B, C, D) -> E): IO = + parallelMapN(ctx, + parallelMapN(ctx, ioA, ioB, ::Tuple2), + parallelMapN(ctx, ioC, ioD, ::Tuple2), { ab, cd -> f(ab.a, ab.b, cd.a, cd.b) }) -fun IO.Companion.parMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, f: (A, B, C, D, E) -> F): IO = - parMapN(ctx, - parMapN(ctx, ioA, ioB, ioC, ::Tuple3), - parMapN(ctx, ioD, ioE, ::Tuple2), +fun IO.Companion.parallelMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, f: (A, B, C, D, E) -> F): IO = + parallelMapN(ctx, + parallelMapN(ctx, ioA, ioB, ioC, ::Tuple3), + parallelMapN(ctx, ioD, ioE, ::Tuple2), { abc, de -> f(abc.a, abc.b, abc.c, de.a, de.b) }) -fun IO.Companion.parMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, ioF: IO, f: (A, B, C, D, E, F) -> G): IO = - parMapN(ctx, - parMapN(ctx, ioA, ioB, ioC, ::Tuple3), - parMapN(ctx, ioD, ioE, ioF, ::Tuple3), +fun IO.Companion.parallelMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, ioF: IO, f: (A, B, C, D, E, F) -> G): IO = + parallelMapN(ctx, + parallelMapN(ctx, ioA, ioB, ioC, ::Tuple3), + parallelMapN(ctx, ioD, ioE, ioF, ::Tuple3), { abc, def -> f(abc.a, abc.b, abc.c, def.a, def.b, def.c) }) -fun IO.Companion.parMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, ioF: IO, ioG: IO, f: (A, B, C, D, E, F, G) -> H): IO = - parMapN(ctx, - parMapN(ctx, ioA, ioB, ioC, ::Tuple3), - parMapN(ctx, ioD, ioE, ::Tuple2), - parMapN(ctx, ioF, ioG, ::Tuple2), +fun IO.Companion.parallelMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, ioF: IO, ioG: IO, f: (A, B, C, D, E, F, G) -> H): IO = + parallelMapN(ctx, + parallelMapN(ctx, ioA, ioB, ioC, ::Tuple3), + parallelMapN(ctx, ioD, ioE, ::Tuple2), + parallelMapN(ctx, ioF, ioG, ::Tuple2), { abc, de, fg -> f(abc.a, abc.b, abc.c, de.a, de.b, fg.a, fg.b) }) -fun IO.Companion.parMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, ioF: IO, ioG: IO, ioH: IO, f: (A, B, C, D, E, F, G, H) -> I): IO = - parMapN(ctx, - parMapN(ctx, ioA, ioB, ioC, ::Tuple3), - parMapN(ctx, ioD, ioE, ioF, ::Tuple3), - parMapN(ctx, ioG, ioH, ::Tuple2), +fun IO.Companion.parallelMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, ioF: IO, ioG: IO, ioH: IO, f: (A, B, C, D, E, F, G, H) -> I): IO = + parallelMapN(ctx, + parallelMapN(ctx, ioA, ioB, ioC, ::Tuple3), + parallelMapN(ctx, ioD, ioE, ioF, ::Tuple3), + parallelMapN(ctx, ioG, ioH, ::Tuple2), { abc, def, gh -> f(abc.a, abc.b, abc.c, def.a, def.b, def.c, gh.a, gh.b) }) -fun IO.Companion.parMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, ioF: IO, ioG: IO, ioH: IO, ioI: IO, f: (A, B, C, D, E, F, G, H, I) -> J): IO = - parMapN(ctx, - parMapN(ctx, ioA, ioB, ioC, ::Tuple3), - parMapN(ctx, ioD, ioE, ioF, ::Tuple3), - parMapN(ctx, ioG, ioH, ioI, ::Tuple3), +fun IO.Companion.parallelMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, ioE: IO, ioF: IO, ioG: IO, ioH: IO, ioI: IO, f: (A, B, C, D, E, F, G, H, I) -> J): IO = + parallelMapN(ctx, + parallelMapN(ctx, ioA, ioB, ioC, ::Tuple3), + parallelMapN(ctx, ioD, ioE, ioF, ::Tuple3), + parallelMapN(ctx, ioG, ioH, ioI, ::Tuple3), { abc, def, ghi -> f(abc.a, abc.b, abc.c, def.a, def.b, def.c, ghi.a, ghi.b, ghi.c) }) diff --git a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/ParallelUtils.kt b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/ParallelUtils.kt index 5ceb3c201cf..044b8201985 100644 --- a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/ParallelUtils.kt +++ b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/ParallelUtils.kt @@ -9,7 +9,7 @@ import kotlin.coroutines.experimental.startCoroutine import kotlin.coroutines.experimental.suspendCoroutine /* See par3 */ -internal fun par2(ctx: CoroutineContext, ioA: IO, ioB: IO, f: (A, B) -> C): Proc = { cc -> +internal fun parMap2(ctx: CoroutineContext, ioA: IO, ioB: IO, f: (A, B) -> C): Proc = { cc -> val a: suspend () -> Either = { suspendCoroutine { ca: Continuation> -> ioA.map { it.left() }.unsafeRunAsync { @@ -33,7 +33,7 @@ internal fun par2(ctx: CoroutineContext, ioA: IO, ioB: IO, f: (A * Every time you start 4+ elements, each pair or triple has to be combined with another one at the same depth. * Elements at higher depths that are synchronous can prevent elements at a higher depth to start. * Thus, we need to provide solutions for even and uneven amounts of IOs for all to be started at the same depth. */ -internal fun par3(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, f: (A, B, C) -> D): Proc = { cc -> +internal fun parMap3(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, f: (A, B, C) -> D): Proc = { cc -> val a: suspend () -> Treither = { suspendCoroutine { ca: Continuation> -> ioA.map { Treither.Left(it) }.unsafeRunAsync { diff --git a/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt b/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt index 069ebd93a35..c2821482491 100644 --- a/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt +++ b/modules/effects/arrow-effects/src/test/kotlin/arrow/effects/IOTest.kt @@ -257,7 +257,7 @@ class IOTest : UnitSpec() { } val result = - IO.parMapN(newSingleThreadContext("all"), makePar(6), makePar(3), makePar(2), makePar(4), makePar(1), makePar(5)) + IO.parallelMapN(newSingleThreadContext("all"), makePar(6), makePar(3), makePar(2), makePar(4), makePar(1), makePar(5)) { six, tree, two, four, one, five -> listOf(six, tree, two, four, one, five) } .unsafeRunSync() @@ -282,7 +282,7 @@ class IOTest : UnitSpec() { }.order() val result = - IO.parMapN(newSingleThreadContext("all"), makePar(6), IO.just(1L).order(), makePar(4), IO.defer { IO.just(2L) }.order(), makePar(5), IO { 3L }.order()) + IO.parallelMapN(newSingleThreadContext("all"), makePar(6), IO.just(1L).order(), makePar(4), IO.defer { IO.just(2L) }.order(), makePar(5), IO { 3L }.order()) { six, tree, two, four, one, five -> listOf(six, tree, two, four, one, five) } .unsafeRunSync() @@ -299,7 +299,7 @@ class IOTest : UnitSpec() { } val result = - IO.parMapN(newSingleThreadContext("all"), makePar(6), IO.just(1L), makePar(4), IO.defer { IO.just(2L) }, makePar(5), IO { 3L }) + IO.parallelMapN(newSingleThreadContext("all"), makePar(6), IO.just(1L), makePar(4), IO.defer { IO.just(2L) }, makePar(5), IO { 3L }) { _, _, _, _, _, _ -> Thread.currentThread().name }.unsafeRunSync() @@ -309,7 +309,7 @@ class IOTest : UnitSpec() { "parallel IO#defer, IO#suspend and IO#async are run in the expected CoroutineContext" { val result = - IO.parMapN(newSingleThreadContext("here"), + IO.parallelMapN(newSingleThreadContext("here"), IO { Thread.currentThread().name }, IO.defer { IO.just(Thread.currentThread().name) }, IO.async { it(Thread.currentThread().name.right()) }, ::Tuple3) .unsafeRunSync() From 82bfa91e9d4f8a5bb6c79fb5ff9750a5fb57574b Mon Sep 17 00:00:00 2001 From: Paco Date: Sun, 22 Jul 2018 21:34:03 +0100 Subject: [PATCH 25/26] Fix doc --- .../src/main/kotlin/arrow/effects/internal/ParallelUtils.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/ParallelUtils.kt b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/ParallelUtils.kt index 044b8201985..56a3afef884 100644 --- a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/ParallelUtils.kt +++ b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/ParallelUtils.kt @@ -31,7 +31,7 @@ internal fun parMap2(ctx: CoroutineContext, ioA: IO, ioB: IO, f: /* Parallelization is only provided in pairs and triples. * Every time you start 4+ elements, each pair or triple has to be combined with another one at the same depth. - * Elements at higher depths that are synchronous can prevent elements at a higher depth to start. + * Elements at higher depths that are synchronous can prevent elements at a lower depth from starting. * Thus, we need to provide solutions for even and uneven amounts of IOs for all to be started at the same depth. */ internal fun parMap3(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, f: (A, B, C) -> D): Proc = { cc -> val a: suspend () -> Treither = { From cfd75e71c06c7953b90c6280689457300daeb219 Mon Sep 17 00:00:00 2001 From: pakoito Date: Tue, 24 Jul 2018 03:46:51 +0100 Subject: [PATCH 26/26] Generalise ParallelUtils for all Effects, specialize for IO --- .../main/kotlin/arrow/effects/IOParallel.kt | 4 +- .../arrow/effects/internal/ParallelUtils.kt | 41 +++++++++++-------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IOParallel.kt b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IOParallel.kt index cc0aec024df..daf8c2119af 100644 --- a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IOParallel.kt +++ b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/IOParallel.kt @@ -7,10 +7,10 @@ import arrow.effects.internal.parMap3 import kotlin.coroutines.experimental.CoroutineContext fun IO.Companion.parallelMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, f: (A, B) -> C): IO = - IO.async(parMap2(ctx, ioA, ioB, f)) + IO.async(IO.effect().parMap2(ctx, ioA, ioB, f, /* see parMap2 notes on this parameter */ { it.fix().unsafeRunSync() })) fun IO.Companion.parallelMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, f: (A, B, C) -> D): IO = - IO.async(parMap3(ctx, ioA, ioB, ioC, f)) + IO.async(IO.effect().parMap3(ctx, ioA, ioB, ioC, f, /* see parMap2 notes on this parameter */ { it.fix().unsafeRunSync() })) fun IO.Companion.parallelMapN(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, ioD: IO, f: (A, B, C, D) -> E): IO = parallelMapN(ctx, diff --git a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/ParallelUtils.kt b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/ParallelUtils.kt index 044b8201985..b691df20787 100644 --- a/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/ParallelUtils.kt +++ b/modules/effects/arrow-effects/src/main/kotlin/arrow/effects/internal/ParallelUtils.kt @@ -1,7 +1,8 @@ package arrow.effects.internal +import arrow.Kind import arrow.core.* -import arrow.effects.IO +import arrow.effects.typeclasses.Effect import arrow.effects.typeclasses.Proc import kotlin.coroutines.experimental.Continuation import kotlin.coroutines.experimental.CoroutineContext @@ -9,19 +10,21 @@ import kotlin.coroutines.experimental.startCoroutine import kotlin.coroutines.experimental.suspendCoroutine /* See par3 */ -internal fun parMap2(ctx: CoroutineContext, ioA: IO, ioB: IO, f: (A, B) -> C): Proc = { cc -> +internal fun Effect.parMap2(ctx: CoroutineContext, ioA: Kind, ioB: Kind, f: (A, B) -> C, + /* start is used because this should return Tuple3, but there's no good implementation of Future before Java8 */ + start: (Kind) -> Unit): Proc = { cc -> val a: suspend () -> Either = { suspendCoroutine { ca: Continuation> -> - ioA.map { it.left() }.unsafeRunAsync { - it.fold(ca::resumeWithException, ca::resume) - } + start(ioA.map { it.left() }.runAsync { + it.fold({ invoke { ca.resumeWithException(it) } }, { invoke { ca.resume(it) } }) + }) } } val b: suspend () -> Either = { suspendCoroutine { ca: Continuation> -> - ioB.map { it.right() }.unsafeRunAsync { - it.fold(ca::resumeWithException, ca::resume) - } + start(ioB.map { it.right() }.runAsync { + it.fold({ invoke { ca.resumeWithException(it) } }, { invoke { ca.resume(it) } }) + }) } } val parCont = parContinuation(ctx, f, asyncIOContinuation(ctx, cc)) @@ -33,26 +36,28 @@ internal fun parMap2(ctx: CoroutineContext, ioA: IO, ioB: IO, f: * Every time you start 4+ elements, each pair or triple has to be combined with another one at the same depth. * Elements at higher depths that are synchronous can prevent elements at a higher depth to start. * Thus, we need to provide solutions for even and uneven amounts of IOs for all to be started at the same depth. */ -internal fun parMap3(ctx: CoroutineContext, ioA: IO, ioB: IO, ioC: IO, f: (A, B, C) -> D): Proc = { cc -> +internal fun Effect.parMap3(ctx: CoroutineContext, ioA: Kind, ioB: Kind, ioC: Kind, f: (A, B, C) -> D, + /* start is used because this should return Tuple4, but there's no good implementation of Future before Java8 */ + start: (Kind) -> Unit): Proc = { cc -> val a: suspend () -> Treither = { suspendCoroutine { ca: Continuation> -> - ioA.map { Treither.Left(it) }.unsafeRunAsync { - it.fold(ca::resumeWithException, ca::resume) - } + start(ioA.map { Treither.Left(it) }.runAsync { + it.fold({ invoke { ca.resumeWithException(it) } }, { invoke { ca.resume(it) } }) + }) } } val b: suspend () -> Treither = { suspendCoroutine { ca: Continuation> -> - ioB.map { Treither.Middle(it) }.unsafeRunAsync { - it.fold(ca::resumeWithException, ca::resume) - } + start(ioB.map { Treither.Middle(it) }.runAsync { + it.fold({ invoke { ca.resumeWithException(it) } }, { invoke { ca.resume(it) } }) + }) } } val c: suspend () -> Treither = { suspendCoroutine { ca: Continuation> -> - ioC.map { Treither.Right(it) }.unsafeRunAsync { - it.fold(ca::resumeWithException, ca::resume) - } + start(ioC.map { Treither.Right(it) }.runAsync { + it.fold({ invoke { ca.resumeWithException(it) } }, { invoke { ca.resume(it) } }) + }) } } val triCont = triContinuation(ctx, f, asyncIOContinuation(ctx, cc))