From 47f1bc2d9048c315640bece4811963517c072499 Mon Sep 17 00:00:00 2001 From: Alexander Ioffe Date: Wed, 15 Feb 2023 20:18:24 -0500 Subject: [PATCH] Clean up DSL, make certain sections inline (#42) * Various refactorings * Refactor to inline defs * Maybe add this compile-time option * Implementing for guture * Simplified API spec, tests working * Type compute format should only happen if enabled * Performance enhancements for type-calculation * Additional performance enhacements --- .../scala-3.x/zio/direct/pure/PureDsl.scala | 47 +++---- .../scala-3.x/zio/direct/pure/PureMonad.scala | 107 ++++++++-------- .../scala-3.x/zio/direct/pure/PureSpec.scala | 13 +- .../zio/direct/stream/StreamDsl.scala | 9 +- .../zio/direct/stream/StreamMonad.scala | 88 ++++++------- .../zio/direct/future/FutureSpec.scala | 116 ++++++++++++++++++ .../src/main/scala-3.x/zio/direct/Dsl.scala | 21 ++-- .../scala-3.x/zio/direct/MonadModel.scala | 1 - .../main/scala-3.x/zio/direct/ZioDsl.scala | 12 ++ .../main/scala-3.x/zio/direct/ZioMonad.scala | 62 +++++----- .../zio/direct/core/Transformer.scala | 3 +- .../zio/direct/core/metaprog/Embedder.scala | 19 +-- .../zio/direct/core/metaprog/WithF.scala | 60 +++------ .../zio/direct/core/metaprog/WithIR.scala | 8 +- .../direct/core/metaprog/WithZioType.scala | 9 +- .../direct/core/norm/WithComputeType.scala | 25 ++-- .../direct/core/norm/WithDecomposeTree.scala | 6 +- .../core/norm/WithReconstructTree.scala | 14 ++- .../zio/direct/future/FutureDsl.scala | 36 ++++++ .../zio/direct/future/FutureMonad.scala | 61 +++++++++ .../scala-3.x/zio/direct/list/ListDsl.scala | 11 +- .../scala-3.x/zio/direct/list/ListMonad.scala | 6 - 22 files changed, 474 insertions(+), 260 deletions(-) create mode 100644 zio-direct-test/src/test/scala-3.x/zio/direct/future/FutureSpec.scala create mode 100644 zio-direct/src/main/scala-3.x/zio/direct/ZioDsl.scala create mode 100644 zio-direct/src/main/scala-3.x/zio/direct/future/FutureDsl.scala create mode 100644 zio-direct/src/main/scala-3.x/zio/direct/future/FutureMonad.scala diff --git a/zio-direct-pure/src/main/scala-3.x/zio/direct/pure/PureDsl.scala b/zio-direct-pure/src/main/scala-3.x/zio/direct/pure/PureDsl.scala index 575f73a..6835086 100644 --- a/zio-direct-pure/src/main/scala-3.x/zio/direct/pure/PureDsl.scala +++ b/zio-direct-pure/src/main/scala-3.x/zio/direct/pure/PureDsl.scala @@ -14,29 +14,32 @@ import zio.direct.core.NotDeferredException // def apply[W, S] = new deferWithParams[W, S] // } +class deferWithParams[W, S] extends deferCall[[R, E, A] =>> ZPure[W, S, S, R, E, A], ZPure[?, ?, ?, ?, ?, ?], S, W, PureMonad.PureMonadModel] { + transparent inline def success = PureMonad.Success[W, S] + transparent inline def fallible = Some(PureMonad.Fallible[W, S]) + transparent inline def sequence = PureMonad.Sequence[W, S] + transparent inline def sequencePar = PureMonad.SequencePar[W, S] + transparent inline def state = Some(PureMonad.State[W, S]) + transparent inline def log = Some(PureMonad.Log[W, S]) +} + class deferWith[W, S] { - object defer extends deferCall[[R, E, A] =>> ZPure[W, S, S, R, E, A], ZPure[?, ?, ?, ?, ?, ?], S, W, PureMonadModel]( - zpureMonadSuccess[W, S], - Some(zpureMonadFallible[W, S]), // MUCH better perf when this is removed - zpureMonadSequence[W, S], - zpureMonadSequencePar[W, S], - Some(zpureMonadState[W, S]), - Some(zpureMonadLog[W, S]) - ) - object State { - // Note that initially it was attempted to implement these things using `transparent inline def` - // (just `inline def` does not work) however that implementation significantly slowed down - // auto-completion speed or Metals dialog so instead the annotation method was introduced. - // Also this method should have a similar annotation in Scala-2. - - /** Helper method to set the state */ - @directSetCall - def set(s: S): Unit = ZPure.set(s).eval - - /** Helper method to get the state */ - @directGetCall - def get(): S = ZPure.get[S].eval - } + val defer = new deferWithParams[W, S] + + // Note that initially it was attempted to implement setState and getState using `transparent inline def` + // (just `inline def` does not work) the approach was much simpler as it looked like: + // transparent inline def setState(inline s: State) = summon[MonadState[F]].set(s) + // however that implementation significantly slowed down + // auto-completion speed or Metals dialog so instead the annotation method was introduced. + // Also this method should have a similar annotation in Scala-2. + + /** Helper method to set the state */ + @directSetCall + def setState(s: S): Unit = ZPure.set(s).eval + + /** Helper method to get the state */ + @directGetCall + def getState(): S = ZPure.get[S].eval /** Helper method to do logging */ @directLogCall diff --git a/zio-direct-pure/src/main/scala-3.x/zio/direct/pure/PureMonad.scala b/zio-direct-pure/src/main/scala-3.x/zio/direct/pure/PureMonad.scala index 125de50..a8fbf31 100644 --- a/zio-direct-pure/src/main/scala-3.x/zio/direct/pure/PureMonad.scala +++ b/zio-direct-pure/src/main/scala-3.x/zio/direct/pure/PureMonad.scala @@ -9,65 +9,66 @@ import MonadShape.Variance._ import MonadShape.Letter._ import zio.CanFail -type PureMonadModel = MonadModel { - type Variances = MonadShape.Variances6[Unused, Unused, Unused, Contravariant, Covariant, Covariant] - type Letters = MonadShape.Letters6[Other, Other, Other, R, E, A] - type IsFallible = true -} +object PureMonad { + type PureMonadModel = MonadModel { + type Variances = MonadShape.Variances6[Unused, Unused, Unused, Contravariant, Covariant, Covariant] + type Letters = MonadShape.Letters6[Other, Other, Other, R, E, A] + } -implicit def zpureMonadSuccess[W, S]: MonadSuccess[[R, E, A] =>> ZPure[W, S, S, R, E, A]] = new MonadSuccess[[R, E, A] =>> ZPure[W, S, S, R, E, A]] { - def unit[A](a: => A): ZPure[W, S, S, Any, Nothing, A] = ZPure.succeed[S, A](a) - def map[R, E, A, B](first: ZPure[W, S, S, R, E, A])(andThen: A => B): ZPure[W, S, S, R, E, B] = first.map[B](andThen) - def flatMap[R, E, A, B](first: ZPure[W, S, S, R, E, A])(andThen: A => ZPure[W, S, S, R, E, B]): ZPure[W, S, S, R, E, B] = first.flatMap[W, S, R, E, B](andThen) - def flatten[R, E, A, R1 <: R, E1 >: E](first: ZPure[W, S, S, R, E, ZPure[W, S, S, R1, E1, A]]): ZPure[W, S, S, R1, E1, A] = first.flatten -} + def Success[W, S]: MonadSuccess[[R, E, A] =>> ZPure[W, S, S, R, E, A]] = new MonadSuccess[[R, E, A] =>> ZPure[W, S, S, R, E, A]] { + def unit[A](a: => A): ZPure[W, S, S, Any, Nothing, A] = ZPure.succeed[S, A](a) + def map[R, E, A, B](first: ZPure[W, S, S, R, E, A])(andThen: A => B): ZPure[W, S, S, R, E, B] = first.map[B](andThen) + def flatMap[R, E, A, B](first: ZPure[W, S, S, R, E, A])(andThen: A => ZPure[W, S, S, R, E, B]): ZPure[W, S, S, R, E, B] = first.flatMap[W, S, R, E, B](andThen) + def flatten[R, E, A, R1 <: R, E1 >: E](first: ZPure[W, S, S, R, E, ZPure[W, S, S, R1, E1, A]]): ZPure[W, S, S, R1, E1, A] = first.flatten + } -/** - * MonadFalliable implementation for ZPure. - * NOTE: Be sure to always 'plug' the CanFail slots manually. Otherwise when the macro synthesizes - * calls using catchSome, ensuring, etc... the additional time it will take to "typecheck" the CanFail - * will horribly slow-down compile-times. Especially if there are various other macros in the - * same file that are also doing type-checks. - */ -implicit def zpureMonadFallible[W, S]: MonadFallible[[R, E, A] =>> ZPure[W, S, S, R, E, A]] = new MonadFallible[[R, E, A] =>> ZPure[W, S, S, R, E, A]] { - def fail[E](e: => E): ZPure[Nothing, Any, Nothing, Any, E, Nothing] = ZPure.fail(e) - def attempt[A](a: => A): ZPure[W, S, S, Any, Throwable, A] = ZPure.attempt[S, A](a) - def catchSome[R, E, A](first: ZPure[W, S, S, R, E, A])(andThen: PartialFunction[E, ZPure[W, S, S, R, E, A]]): ZPure[W, S, S, R, E, A] = - first.catchSome[W, S, S, R, E, A](andThen)(CanFail) + /** + * MonadFalliable implementation for ZPure. + * NOTE: Be sure to always 'plug' the CanFail slots manually. Otherwise when the macro synthesizes + * calls using catchSome, ensuring, etc... the additional time it will take to "typecheck" the CanFail + * will horribly slow-down compile-times. Especially if there are various other macros in the + * same file that are also doing type-checks. + */ + def Fallible[W, S]: MonadFallible[[R, E, A] =>> ZPure[W, S, S, R, E, A]] = new MonadFallible[[R, E, A] =>> ZPure[W, S, S, R, E, A]] { + def fail[E](e: => E): ZPure[Nothing, Any, Nothing, Any, E, Nothing] = ZPure.fail(e) + def attempt[A](a: => A): ZPure[W, S, S, Any, Throwable, A] = ZPure.attempt[S, A](a) + def catchSome[R, E, A](first: ZPure[W, S, S, R, E, A])(andThen: PartialFunction[E, ZPure[W, S, S, R, E, A]]): ZPure[W, S, S, R, E, A] = + first.catchSome[W, S, S, R, E, A](andThen)(CanFail) - def ensuring[R, E, A](f: ZPure[W, S, S, R, E, A])(finalizer: ZPure[W, S, S, R, Nothing, Any]): ZPure[W, S, S, R, E, A] = - f.foldCauseM( - (cause: fx.Cause[E]) => finalizer.flatMap(_ => ZPure.failCause(cause)), - success => finalizer.flatMap(_ => ZPure.succeed(success)) - )(CanFail) + def ensuring[R, E, A](f: ZPure[W, S, S, R, E, A])(finalizer: ZPure[W, S, S, R, Nothing, Any]): ZPure[W, S, S, R, E, A] = + f.foldCauseM( + (cause: fx.Cause[E]) => finalizer.flatMap(_ => ZPure.failCause(cause)), + success => finalizer.flatMap(_ => ZPure.succeed(success)) + )(CanFail) - def mapError[R, E, A, E2](first: ZPure[W, S, S, R, E, A])(f: E => E2): ZPure[W, S, S, R, E2, A] = first.mapError(f)(CanFail) - def orDie[R, E <: Throwable, A](first: ZPure[W, S, S, R, E, A]): ZPure[W, S, S, R, Nothing, A] = - first.foldCauseM( - (cause: fx.Cause[E]) => throw cause.first, - success => ZPure.succeed(success) - )(CanFail) -} + def mapError[R, E, A, E2](first: ZPure[W, S, S, R, E, A])(f: E => E2): ZPure[W, S, S, R, E2, A] = first.mapError(f)(CanFail) + def orDie[R, E <: Throwable, A](first: ZPure[W, S, S, R, E, A]): ZPure[W, S, S, R, Nothing, A] = + first.foldCauseM( + (cause: fx.Cause[E]) => throw cause.first, + success => ZPure.succeed(success) + )(CanFail) + } -implicit def zpureMonadSequence[W, S]: MonadSequence[[R, E, A] =>> ZPure[W, S, S, R, E, A]] = new MonadSequence[[R, E, A] =>> ZPure[W, S, S, R, E, A]] { - def foreach[R, E, A, B, Collection[+Element] <: Iterable[Element]]( - in: Collection[A] - )(f: A => ZPure[W, S, S, R, E, B])(implicit bf: scala.collection.BuildFrom[Collection[A], B, Collection[B]]): ZPure[W, S, S, R, E, Collection[B]] = - ZPure.forEach((in: Iterable[A]))(f).map(col => bf.fromSpecific(in)(col)) -} + def Sequence[W, S]: MonadSequence[[R, E, A] =>> ZPure[W, S, S, R, E, A]] = new MonadSequence[[R, E, A] =>> ZPure[W, S, S, R, E, A]] { + def foreach[R, E, A, B, Collection[+Element] <: Iterable[Element]]( + in: Collection[A] + )(f: A => ZPure[W, S, S, R, E, B])(implicit bf: scala.collection.BuildFrom[Collection[A], B, Collection[B]]): ZPure[W, S, S, R, E, Collection[B]] = + ZPure.forEach((in: Iterable[A]))(f).map(col => bf.fromSpecific(in)(col)) + } -implicit def zpureMonadSequencePar[W, S]: MonadSequenceParallel[[R, E, A] =>> ZPure[W, S, S, R, E, A]] = new MonadSequenceParallel[[R, E, A] =>> ZPure[W, S, S, R, E, A]] { - def foreachPar[R, E, A, B, Collection[+Element] <: Iterable[Element]]( - in: Collection[A] - )(f: A => ZPure[W, S, S, R, E, B])(implicit bf: scala.collection.BuildFrom[Collection[A], B, Collection[B]]): ZPure[W, S, S, R, E, Collection[B]] = - zpureMonadSequence.foreach(in)(f) -} + def SequencePar[W, S]: MonadSequenceParallel[[R, E, A] =>> ZPure[W, S, S, R, E, A]] = new MonadSequenceParallel[[R, E, A] =>> ZPure[W, S, S, R, E, A]] { + def foreachPar[R, E, A, B, Collection[+Element] <: Iterable[Element]]( + in: Collection[A] + )(f: A => ZPure[W, S, S, R, E, B])(implicit bf: scala.collection.BuildFrom[Collection[A], B, Collection[B]]): ZPure[W, S, S, R, E, Collection[B]] = + Sequence.foreach(in)(f) + } -implicit def zpureMonadState[W, S]: MonadState[[R, E, A] =>> ZPure[W, S, S, R, E, A], S] = new MonadState[[R, E, A] =>> ZPure[W, S, S, R, E, A], S] { - override def set(s: S): ZPure[W, S, S, Any, Nothing, Unit] = ZPure.set(s) - override def get: ZPure[W, S, S, Any, Nothing, S] = ZPure.get[S] -} + def State[W, S]: MonadState[[R, E, A] =>> ZPure[W, S, S, R, E, A], S] = new MonadState[[R, E, A] =>> ZPure[W, S, S, R, E, A], S] { + override def set(s: S): ZPure[W, S, S, Any, Nothing, Unit] = ZPure.set(s) + override def get: ZPure[W, S, S, Any, Nothing, S] = ZPure.get[S] + } -implicit def zpureMonadLog[W, S]: MonadLog[[R, E, A] =>> ZPure[W, S, S, R, E, A], W] = new MonadLog[[R, E, A] =>> ZPure[W, S, S, R, E, A], W] { - def log(w: W): ZPure[W, S, S, Any, Nothing, Unit] = ZPure.log[S, W](w) + def Log[W, S]: MonadLog[[R, E, A] =>> ZPure[W, S, S, R, E, A], W] = new MonadLog[[R, E, A] =>> ZPure[W, S, S, R, E, A], W] { + def log(w: W): ZPure[W, S, S, Any, Nothing, Unit] = ZPure.log[S, W](w) + } } diff --git a/zio-direct-pure/src/test/scala-3.x/zio/direct/pure/PureSpec.scala b/zio-direct-pure/src/test/scala-3.x/zio/direct/pure/PureSpec.scala index c948df6..d5f4279 100644 --- a/zio-direct-pure/src/test/scala-3.x/zio/direct/pure/PureSpec.scala +++ b/zio-direct-pure/src/test/scala-3.x/zio/direct/pure/PureSpec.scala @@ -11,7 +11,6 @@ import zio.stream.ZStream import zio.Chunk import zio.prelude.fx.ZPure - object PureSpec extends DeferRunSpec { val dc = deferWith[String, MyState] import dc._ @@ -46,15 +45,15 @@ object PureSpec extends DeferRunSpec { assert(out.provideState(MyState("init")).run)(equalTo(("init", "init", "bar", "foo"))) }, test("Simple Sequence with State - using primitives and logging") { - val out = // ddd + val out = defer { - val s1 = State.get().value + val s1 = getState().value val a = ZPure.succeed[MyState, String](s1).eval log(a) - State.set(MyState("foo")) + setState(MyState("foo")) val b = ZPure.succeed[MyState, String]("bar").eval log(b) - val s2 = State.get().value + val s2 = getState().value (s1, a, b, s2) } assert(out.runAll(MyState("init")))(equalTo( @@ -65,11 +64,11 @@ object PureSpec extends DeferRunSpec { val out = defer { if (ZPure.succeed[MyState, Int](2).eval == 2) val v = ZPure.succeed[MyState, String]("foo").eval - State.set(MyState(v)) + setState(MyState(v)) v else val v = ZPure.succeed[MyState, String]("bar").eval - State.set(MyState(v)) + setState(MyState(v)) v } assert(out.run(MyState("init")))(equalTo((MyState("foo"), "foo"))) diff --git a/zio-direct-streams/src/main/scala-3.x/zio/direct/stream/StreamDsl.scala b/zio-direct-streams/src/main/scala-3.x/zio/direct/stream/StreamDsl.scala index b17cadb..335514e 100644 --- a/zio-direct-streams/src/main/scala-3.x/zio/direct/stream/StreamDsl.scala +++ b/zio-direct-streams/src/main/scala-3.x/zio/direct/stream/StreamDsl.scala @@ -5,7 +5,14 @@ import zio.direct.directRunCall import zio.stream.ZStream import zio.direct.core.NotDeferredException -object defer extends deferCall[ZStream, ZStream[?, ?, ?], Nothing, Nothing, StreamMonadModel](zstreamMonadSuccess, Some(zstreamMonadFallible), zstreamMonadSequence, zstreamMonadSequencePar, None, None) +object defer extends deferCall[ZStream, ZStream[?, ?, ?], Nothing, Nothing, StreamMonad.StreamMonadModel] { + transparent inline def success = StreamMonad.Success + transparent inline def fallible = Some(StreamMonad.Fallible) + transparent inline def sequence = StreamMonad.Sequence + transparent inline def sequencePar = StreamMonad.SequencePar + transparent inline def state = None + transparent inline def log = None +} extension [R, E, A](value: ZStream[R, E, A]) { @directRunCall diff --git a/zio-direct-streams/src/main/scala-3.x/zio/direct/stream/StreamMonad.scala b/zio-direct-streams/src/main/scala-3.x/zio/direct/stream/StreamMonad.scala index c33943e..ea9fd43 100644 --- a/zio-direct-streams/src/main/scala-3.x/zio/direct/stream/StreamMonad.scala +++ b/zio-direct-streams/src/main/scala-3.x/zio/direct/stream/StreamMonad.scala @@ -8,47 +8,49 @@ import MonadShape.Variance._ import MonadShape.Letter._ import zio.Chunk -type StreamMonadModel = MonadModel { - type Variances = MonadShape.Variances3[Contravariant, Covariant, Covariant] - type Letters = MonadShape.Letters3[R, E, A] - type IsFallible = true -} - -implicit val zstreamMonadSuccess: MonadSuccess[ZStream] = new MonadSuccess[ZStream] { - def unit[A](a: => A): ZStream[Any, Nothing, A] = ZStream.succeed[A](a) - def map[R, E, A, B](first: ZStream[R, E, A])(andThen: A => B): ZStream[R, E, B] = first.map[B](andThen) - def flatMap[R, E, A, B](first: ZStream[R, E, A])(andThen: A => ZStream[R, E, B]): ZStream[R, E, B] = first.flatMap[R, E, B](andThen) - def flatten[R, E, A, R1 <: R, E1 >: E](first: ZStream[R, E, ZStream[R1, E1, A]]): ZStream[R1, E1, A] = first.flatten -} - -implicit val zstreamMonadFallible: MonadFallible[ZStream] = new MonadFallible[ZStream] { - def fail[E](e: => E): ZStream[Any, E, Nothing] = ZStream.fail(e) - def attempt[A](a: => A): ZStream[Any, Throwable, A] = ZStream.fromZIO(ZIO.attempt[A](a)) - def catchSome[R, E, A](first: ZStream[R, E, A])(andThen: PartialFunction[E, ZStream[R, E, A]]): ZStream[R, E, A] = first.catchSome[R, E, A](andThen) - // finalizer here is a ZIO. How should this be encapsulated? does it need a special type? - def ensuring[R, E, A](f: ZStream[R, E, A])(finalizer: ZStream[R, Nothing, Any]): ZStream[R, E, A] = f.ensuring(finalizer.runHead) - def mapError[R, E, A, E2](first: ZStream[R, E, A])(f: E => E2): ZStream[R, E2, A] = first.mapError(f) - def orDie[R, E <: Throwable, A](first: ZStream[R, E, A]): ZStream[R, Nothing, A] = first.orDie -} - -implicit val zstreamMonadSequence: MonadSequence[ZStream] = new MonadSequence[ZStream] { - // basically the equivalent of `gens.foldRight[Gen[R, List[A]]](Gen.const(List.empty))(_.zipWith(_)(_ :: _))` - private def crossN[R, E, A, B, C](streams: Chunk[ZStream[R, E, A]]): ZStream[R, E, Chunk[A]] = - streams.foldLeft[ZStream[R, E, Chunk[A]]](ZStream.succeed(Chunk.empty)) { (left, right) => left.cross(right).map { case (l, r) => l :+ r } } - - def foreach[R, E, A, B, Collection[+Element] <: Iterable[Element]]( - in: Collection[A] - )(f: A => ZStream[R, E, B])(implicit bf: scala.collection.BuildFrom[Collection[A], B, Collection[B]]): ZStream[R, E, Collection[B]] = - // If exceptions thrown here, want to catch them when we wrap this - lazy val crossedChunks = crossN(Chunk.fromIterable(in.map(f))) - lazy val output = crossedChunks.map(chunk => bf.fromSpecific(in)(chunk)) - ZStream(output).flatten -} - -implicit val zstreamMonadSequencePar: MonadSequenceParallel[ZStream] = new MonadSequenceParallel[ZStream] { - def foreachPar[R, E, A, B, Collection[+Element] <: Iterable[Element]]( - in: Collection[A] - )(f: A => ZStream[R, E, B])(implicit bf: scala.collection.BuildFrom[Collection[A], B, Collection[B]]): ZStream[R, E, Collection[B]] = - // TODO Same problem again. Need a different type for the finalization - zstreamMonadSequence.foreach(in)(f) +object StreamMonad { + + type StreamMonadModel = MonadModel { + type Variances = MonadShape.Variances3[Contravariant, Covariant, Covariant] + type Letters = MonadShape.Letters3[R, E, A] + } + + val Success: MonadSuccess[ZStream] = new MonadSuccess[ZStream] { + def unit[A](a: => A): ZStream[Any, Nothing, A] = ZStream.succeed[A](a) + def map[R, E, A, B](first: ZStream[R, E, A])(andThen: A => B): ZStream[R, E, B] = first.map[B](andThen) + def flatMap[R, E, A, B](first: ZStream[R, E, A])(andThen: A => ZStream[R, E, B]): ZStream[R, E, B] = first.flatMap[R, E, B](andThen) + def flatten[R, E, A, R1 <: R, E1 >: E](first: ZStream[R, E, ZStream[R1, E1, A]]): ZStream[R1, E1, A] = first.flatten + } + + val Fallible: MonadFallible[ZStream] = new MonadFallible[ZStream] { + def fail[E](e: => E): ZStream[Any, E, Nothing] = ZStream.fail(e) + def attempt[A](a: => A): ZStream[Any, Throwable, A] = ZStream.fromZIO(ZIO.attempt[A](a)) + def catchSome[R, E, A](first: ZStream[R, E, A])(andThen: PartialFunction[E, ZStream[R, E, A]]): ZStream[R, E, A] = first.catchSome[R, E, A](andThen) + // finalizer here is a ZIO. How should this be encapsulated? does it need a special type? + def ensuring[R, E, A](f: ZStream[R, E, A])(finalizer: ZStream[R, Nothing, Any]): ZStream[R, E, A] = f.ensuring(finalizer.runHead) + def mapError[R, E, A, E2](first: ZStream[R, E, A])(f: E => E2): ZStream[R, E2, A] = first.mapError(f) + def orDie[R, E <: Throwable, A](first: ZStream[R, E, A]): ZStream[R, Nothing, A] = first.orDie + } + + val Sequence: MonadSequence[ZStream] = new MonadSequence[ZStream] { + // basically the equivalent of `gens.foldRight[Gen[R, List[A]]](Gen.const(List.empty))(_.zipWith(_)(_ :: _))` + private def crossN[R, E, A, B, C](streams: Chunk[ZStream[R, E, A]]): ZStream[R, E, Chunk[A]] = + streams.foldLeft[ZStream[R, E, Chunk[A]]](ZStream.succeed(Chunk.empty)) { (left, right) => left.cross(right).map { case (l, r) => l :+ r } } + + def foreach[R, E, A, B, Collection[+Element] <: Iterable[Element]]( + in: Collection[A] + )(f: A => ZStream[R, E, B])(implicit bf: scala.collection.BuildFrom[Collection[A], B, Collection[B]]): ZStream[R, E, Collection[B]] = + // If exceptions thrown here, want to catch them when we wrap this + lazy val crossedChunks = crossN(Chunk.fromIterable(in.map(f))) + lazy val output = crossedChunks.map(chunk => bf.fromSpecific(in)(chunk)) + ZStream(output).flatten + } + + val SequencePar: MonadSequenceParallel[ZStream] = new MonadSequenceParallel[ZStream] { + def foreachPar[R, E, A, B, Collection[+Element] <: Iterable[Element]]( + in: Collection[A] + )(f: A => ZStream[R, E, B])(implicit bf: scala.collection.BuildFrom[Collection[A], B, Collection[B]]): ZStream[R, E, Collection[B]] = + // TODO Same problem again. Need a different type for the finalization + Sequence.foreach(in)(f) + } } diff --git a/zio-direct-test/src/test/scala-3.x/zio/direct/future/FutureSpec.scala b/zio-direct-test/src/test/scala-3.x/zio/direct/future/FutureSpec.scala new file mode 100644 index 0000000..a0fe521 --- /dev/null +++ b/zio-direct-test/src/test/scala-3.x/zio/direct/future/FutureSpec.scala @@ -0,0 +1,116 @@ +package zio.direct.future + +import zio.direct.DeferRunSpec +import zio.test._ +import zio.test.Assertion._ +import zio.direct.Use +import zio.direct.core.util.Messages +import scala.annotation.nowarn +import zio.direct.DeferRunSpec +import zio.stream.ZStream +import zio.Chunk +import scala.concurrent.Future +import zio.ZIO +import scala.concurrent.ExecutionContext +import scala.concurrent.Await + +object FutureSpec extends DeferRunSpec { + val e = new Exception("blah") + val e1 = new Exception("blahblah") + + def assertFuture[A](ecToFuture: ExecutionContext => Future[A])(assertion: Assertion[A]): ZIO[Any, Throwable, TestResult] = + assertZIO(ZIO.fromFuture(ecToFuture(_)))(assertion) + + val spec = suite("VariaSpec")( + test("Simple Sequence") { + def out(implicit ec: ExecutionContext) = + defer { + val a = Future(1) + val b = Future("foo") + (a.run, b.run) + } + assertIsType[Future[(Int, String)]](out(???)) andAssert + assertFuture(out)(equalTo((1, "foo"))) + }, + test("Impure/Impure If-statement") { + def out(implicit ec: ExecutionContext) = + defer { + if (Future[Int](2).run == 2) + val v = Future("foo").run + v + else + val v = Future("bar").run + v + } + assertFuture(out)(equalTo("foo")) + }, + test("Impure/Impure Pat-match") { + def out(implicit ec: ExecutionContext) = + defer { + Future("a").run match { + case "a" => Future(1).run + case "b" => Future(2).run + } + } + assertFuture(out)(equalTo(1)) + }, + test("Try/Catch caught") { + def out(implicit ec: ExecutionContext) = // + defer { + try { + val num = Future(1).run + if (num == 1) { + throw new FooError + } else { + num + } + } catch { + case _: Throwable => Future(18).run + } + } + + assertFuture(out)(equalTo(18)) + + // assert(Await.result(out(scala.concurrent.ExecutionContext.global), scala.concurrent.duration.Duration.Inf))(equalTo(18)) + }, + test("Try/Catch NOT caught") { + val fooError = new FooError + def out(implicit ec: ExecutionContext) = + defer { + try { + val num = Future(3).run + if (num == 3) { + throw fooError + } else { + num + } + } catch { + case _: BarError => Future(3).run + } + } + assertZIO(ZIO.fromFuture(out).exit)(fails(equalTo(fooError))) + } + // test("Throw-fail") { + // val fooError = new FooError + // def out(implicit ec: ExecutionContext) = + // defer(Use.withNoCheck) { + // throw fooError + // } + // assert(out.runAll(init))(equalTo( + // (Chunk(), Left(zio.prelude.fx.Cause(fooError))) + // )) + // }, + // test("List Impure, body Impure") { + // var v = 1 + // def out(implicit ec: ExecutionContext) = + // defer(Use.withLenientCheck) { + // for (i <- Future(List(1, 2, 3)).run) { + // Future(v += i).run + // } + // } + // // assert(out.provideState(init).run) doesn't run anything, possibly because the after the .run it's a Unit-type + // out.provideState(init).run + // assert(v)(equalTo(7)) + // } + ) +} diff --git a/zio-direct/src/main/scala-3.x/zio/direct/Dsl.scala b/zio-direct/src/main/scala-3.x/zio/direct/Dsl.scala index cf8788d..c0fa746 100644 --- a/zio-direct/src/main/scala-3.x/zio/direct/Dsl.scala +++ b/zio-direct/src/main/scala-3.x/zio/direct/Dsl.scala @@ -22,14 +22,14 @@ class directLogCall extends scala.annotation.StaticAnnotation def unsafe[T](value: T): T = NotDeferredException.fromNamed("unsafe") -trait deferCall[F[_, _, _], F_out, S, W, MM <: MonadModel]( - success: MonadSuccess[F], - fallible: Option[MonadFallible[F]], - sequence: MonadSequence[F], - sequencePar: MonadSequenceParallel[F], - state: Option[MonadState[F, S]], - log: Option[MonadLog[F, W]] -) { +trait deferCall[F[_, _, _], F_out, S, W, MM <: MonadModel] { + transparent inline def success: MonadSuccess[F] + transparent inline def fallible: Option[MonadFallible[F]] + transparent inline def sequence: MonadSequence[F] + transparent inline def sequencePar: MonadSequenceParallel[F] + transparent inline def state: Option[MonadState[F, S]] + transparent inline def log: Option[MonadLog[F, W]] + transparent inline def impl[T]( inline value: T, inline info: InfoBehavior, @@ -78,8 +78,6 @@ trait deferCall[F[_, _, _], F_out, S, W, MM <: MonadModel]( } } -object defer extends deferCall[ZIO, ZIO[?, ?, ?], Nothing, Nothing, ZioMonadModel](zioMonadSuccess, Some(zioMonadFallible), zioMonadSequence, zioMonadSequenceParallel, None, None) - extension [R, E, A](value: ZIO[R, E, A]) { @directRunCall def run: A = NotDeferredException.fromNamed("run") @@ -88,9 +86,6 @@ extension [R, E, A](value: ZIO[R, E, A]) { object Dsl { import InfoBehavior._ - // def implZIO[T: Type](value: Expr[T], infoExpr: Expr[InfoBehavior], useTree: Expr[Use])(using q: Quotes) = - // impl[T, ZIO, ZIO[?, ?, ?]](value, infoExpr, useTree) - case class DirectMonadInput[F[_, _, _]: Type, S: Type, W: Type]( success: Expr[MonadSuccess[F]], fallible: Expr[Option[MonadFallible[F]]], diff --git a/zio-direct/src/main/scala-3.x/zio/direct/MonadModel.scala b/zio-direct/src/main/scala-3.x/zio/direct/MonadModel.scala index 5b0253c..68b4d8f 100644 --- a/zio-direct/src/main/scala-3.x/zio/direct/MonadModel.scala +++ b/zio-direct/src/main/scala-3.x/zio/direct/MonadModel.scala @@ -17,7 +17,6 @@ import zio.direct.core.metaprog.RefineInstructions trait MonadModel { type Variances <: MonadShape.Variances type Letters <: MonadShape.Letters - type IsFallible <: Boolean } trait MonadSuccess[F[_, _, _]] { diff --git a/zio-direct/src/main/scala-3.x/zio/direct/ZioDsl.scala b/zio-direct/src/main/scala-3.x/zio/direct/ZioDsl.scala new file mode 100644 index 0000000..8e2f191 --- /dev/null +++ b/zio-direct/src/main/scala-3.x/zio/direct/ZioDsl.scala @@ -0,0 +1,12 @@ +package zio.direct + +import zio.ZIO + +object defer extends deferCall[ZIO, ZIO[?, ?, ?], Nothing, Nothing, ZioMonad.ZioMonadModel] { + transparent inline def success = ZioMonad.Success + transparent inline def fallible = Some(ZioMonad.Fallible) + transparent inline def sequence = ZioMonad.Sequence + transparent inline def sequencePar = ZioMonad.SequenceParallel + transparent inline def state = None + transparent inline def log = None +} diff --git a/zio-direct/src/main/scala-3.x/zio/direct/ZioMonad.scala b/zio-direct/src/main/scala-3.x/zio/direct/ZioMonad.scala index 94c73b0..03df669 100644 --- a/zio-direct/src/main/scala-3.x/zio/direct/ZioMonad.scala +++ b/zio-direct/src/main/scala-3.x/zio/direct/ZioMonad.scala @@ -7,39 +7,39 @@ import MonadShape.Letter._ import zio.CanFail import zio.IsSubtypeOfError -type ZioMonadModel = MonadModel { - type Variances = MonadShape.Variances3[Contravariant, Covariant, Covariant] - type Letters = MonadShape.Letters3[R, E, A] - type IsFallible = true -} +object ZioMonad { + type ZioMonadModel = MonadModel { + type Variances = MonadShape.Variances3[Contravariant, Covariant, Covariant] + type Letters = MonadShape.Letters3[R, E, A] + } -// TODO summon better traces i.e. from the non-deferred code? -implicit val zioMonadSuccess: MonadSuccess[ZIO] = new MonadSuccess[ZIO] { - def unit[A](a: => A): ZIO[Any, Nothing, A] = ZIO.succeed[A](a) - def map[R, E, A, B](first: ZIO[R, E, A])(andThen: A => B): ZIO[R, E, B] = first.map[B](andThen) - def flatMap[R, E, A, B](first: ZIO[R, E, A])(andThen: A => ZIO[R, E, B]): ZIO[R, E, B] = first.flatMap[R, E, B](andThen) - def flatten[R, E, A, R1 <: R, E1 >: E](first: ZIO[R, E, ZIO[R1, E1, A]]): ZIO[R1, E1, A] = first.flatten -} + val Success: MonadSuccess[ZIO] = new MonadSuccess[ZIO] { + def unit[A](a: => A): ZIO[Any, Nothing, A] = ZIO.succeed[A](a) + def map[R, E, A, B](first: ZIO[R, E, A])(andThen: A => B): ZIO[R, E, B] = first.map[B](andThen) + def flatMap[R, E, A, B](first: ZIO[R, E, A])(andThen: A => ZIO[R, E, B]): ZIO[R, E, B] = first.flatMap[R, E, B](andThen) + def flatten[R, E, A, R1 <: R, E1 >: E](first: ZIO[R, E, ZIO[R1, E1, A]]): ZIO[R1, E1, A] = first.flatten + } -implicit val zioMonadFallible: MonadFallible[ZIO] = new MonadFallible[ZIO] { - def fail[E](e: => E): ZIO[Any, E, Nothing] = ZIO.fail(e) - def attempt[A](a: => A): ZIO[Any, Throwable, A] = ZIO.attempt[A](a) - def catchSome[R, E, A](first: ZIO[R, E, A])(andThen: PartialFunction[E, ZIO[R, E, A]]): ZIO[R, E, A] = first.catchSome[R, E, A](andThen)(CanFail, summon[zio.Trace]) - def ensuring[R, E, A](f: ZIO[R, E, A])(finalizer: ZIO[R, Nothing, Any]): ZIO[R, E, A] = f.ensuring(finalizer)(summon[zio.Trace]) - def mapError[R, E, A, E2](first: ZIO[R, E, A])(f: E => E2): ZIO[R, E2, A] = first.mapError(f)(CanFail, summon[zio.Trace]) - def orDie[R, E <: Throwable, A](first: ZIO[R, E, A]): ZIO[R, Nothing, A] = first.orDie -} + val Fallible: MonadFallible[ZIO] = new MonadFallible[ZIO] { + def fail[E](e: => E): ZIO[Any, E, Nothing] = ZIO.fail(e) + def attempt[A](a: => A): ZIO[Any, Throwable, A] = ZIO.attempt[A](a) + def catchSome[R, E, A](first: ZIO[R, E, A])(andThen: PartialFunction[E, ZIO[R, E, A]]): ZIO[R, E, A] = first.catchSome[R, E, A](andThen)(CanFail, summon[zio.Trace]) + def ensuring[R, E, A](f: ZIO[R, E, A])(finalizer: ZIO[R, Nothing, Any]): ZIO[R, E, A] = f.ensuring(finalizer)(summon[zio.Trace]) + def mapError[R, E, A, E2](first: ZIO[R, E, A])(f: E => E2): ZIO[R, E2, A] = first.mapError(f)(CanFail, summon[zio.Trace]) + def orDie[R, E <: Throwable, A](first: ZIO[R, E, A]): ZIO[R, Nothing, A] = first.orDie + } -implicit val zioMonadSequence: MonadSequence[ZIO] = new MonadSequence[ZIO] { - def foreach[R, E, A, B, Collection[+Element] <: Iterable[Element]]( - in: Collection[A] - )(f: A => ZIO[R, E, B])(implicit bf: scala.collection.BuildFrom[Collection[A], B, Collection[B]]): ZIO[R, E, Collection[B]] = - ZIO.foreach(in)(f) -} + val Sequence: MonadSequence[ZIO] = new MonadSequence[ZIO] { + def foreach[R, E, A, B, Collection[+Element] <: Iterable[Element]]( + in: Collection[A] + )(f: A => ZIO[R, E, B])(implicit bf: scala.collection.BuildFrom[Collection[A], B, Collection[B]]): ZIO[R, E, Collection[B]] = + ZIO.foreach(in)(f) + } -implicit val zioMonadSequenceParallel: MonadSequenceParallel[ZIO] = new MonadSequenceParallel[ZIO] { - def foreachPar[R, E, A, B, Collection[+Element] <: Iterable[Element]]( - in: Collection[A] - )(f: A => ZIO[R, E, B])(implicit bf: scala.collection.BuildFrom[Collection[A], B, Collection[B]]): ZIO[R, E, Collection[B]] = - ZIO.foreachPar(in)(f) + val SequenceParallel: MonadSequenceParallel[ZIO] = new MonadSequenceParallel[ZIO] { + def foreachPar[R, E, A, B, Collection[+Element] <: Iterable[Element]]( + in: Collection[A] + )(f: A => ZIO[R, E, B])(implicit bf: scala.collection.BuildFrom[Collection[A], B, Collection[B]]): ZIO[R, E, Collection[B]] = + ZIO.foreachPar(in)(f) + } } diff --git a/zio-direct/src/main/scala-3.x/zio/direct/core/Transformer.scala b/zio-direct/src/main/scala-3.x/zio/direct/core/Transformer.scala index 363684b..7e0177a 100644 --- a/zio-direct/src/main/scala-3.x/zio/direct/core/Transformer.scala +++ b/zio-direct/src/main/scala-3.x/zio/direct/core/Transformer.scala @@ -106,7 +106,8 @@ class Transformer[F[_, _, _]: Type, F_out: Type, S: Type, W: Type, MM <: MonadMo computedType.asTypeTuple match { case ('[r], '[e], '[a]) => - val computedTypeMsg = s"Computed Type: ${Format.TypeOf[F[r, e, a]]}" + // make sure this is lazy, don't want to do formatting unless absolutely needed + lazy val computedTypeMsg = s"Computed Type: ${Format.TypeOf[F[r, e, a]]}" if (instructions.info.showComputedType) ownerPositionOpt match { diff --git a/zio-direct/src/main/scala-3.x/zio/direct/core/metaprog/Embedder.scala b/zio-direct/src/main/scala-3.x/zio/direct/core/metaprog/Embedder.scala index ca92775..e66b7c3 100644 --- a/zio-direct/src/main/scala-3.x/zio/direct/core/metaprog/Embedder.scala +++ b/zio-direct/src/main/scala-3.x/zio/direct/core/metaprog/Embedder.scala @@ -32,14 +32,13 @@ object Embedder { import quotes.reflect._ typeUnion match { case TypeUnion.OrType => - if (a =:= TypeRepr.of[Nothing] && b =:= TypeRepr.of[Nothing]) TypeRepr.of[Nothing] + if (a =:= b) + a + else if (a =:= TypeRepr.of[Nothing] && b =:= TypeRepr.of[Nothing]) TypeRepr.of[Nothing] else if (a =:= TypeRepr.of[Nothing]) b else if (b =:= TypeRepr.of[Nothing]) a else - // only widen if you absolutely need to - (a.widen.asType, b.widen.asType) match - case ('[at], '[bt]) => - TypeRepr.of[at | bt] + OrType(a, b) case TypeUnion.LeastUpper => Embedder.computeCommonBaseClass(a.widen, b.widen) } @@ -49,7 +48,9 @@ object Embedder { // if either type is Any, specialize to the thing that is narrower import quotes.reflect._ val out = - if (a =:= TypeRepr.of[Any] && b =:= TypeRepr.of[Any]) + if (a =:= b) + a + else if (a =:= TypeRepr.of[Any] && b =:= TypeRepr.of[Any]) TypeRepr.of[Any] else if (a =:= TypeRepr.of[Any]) b @@ -57,9 +58,9 @@ object Embedder { a else // only widen if you absolutely need to - (a.widen.asType, b.widen.asType) match - case ('[at], '[bt]) => - TypeRepr.of[at with bt] + val aw = if (a.isSingleton) a.widen else a + val bw = if (b.isSingleton) b.widen else b + AndType(aw, bw) out } diff --git a/zio-direct/src/main/scala-3.x/zio/direct/core/metaprog/WithF.scala b/zio-direct/src/main/scala-3.x/zio/direct/core/metaprog/WithF.scala index 1158e70..35f86e8 100644 --- a/zio-direct/src/main/scala-3.x/zio/direct/core/metaprog/WithF.scala +++ b/zio-direct/src/main/scala-3.x/zio/direct/core/metaprog/WithF.scala @@ -10,10 +10,15 @@ trait MacroBase { case class MonadModelData( variancesListType: TypeRepr, - lettersListType: TypeRepr, - isFalliable: Boolean + lettersListType: TypeRepr ) + private def constBoolean[T: Type]: Option[Boolean] = + val flagType = TypeRepr.of[T] + if (flagType =:= TypeRepr.of[true]) Some(true) + else if (flagType =:= TypeRepr.of[false]) Some(false) + else None + def computeMonadModelData[MM <: MonadModel: Type]: MonadModelData = { // dealias the type e.g. ZioMonadType to get the Variance/Letters variables underneath val monadModelType = TypeRepr.of[MM].dealias @@ -26,15 +31,7 @@ trait MacroBase { Type.of[MM] match case '[MonadModel { type Letters = list }] => TypeRepr.of[list] - val isFallible = - Type.of[MM] match - case '[MonadModel { type IsFallible = flag }] => - val flagType = TypeRepr.of[flag] - if (flagType =:= TypeRepr.of[true]) true - else if (flagType =:= TypeRepr.of[false]) false - else report.errorAndAbort(s"The type IsFallible needs to be specified on the Monad-Model: ${monadModelType.show}. Currently: ${flagType.show}") - - MonadModelData(monadModelVariancesList, monadModelLettersList, isFallible) + MonadModelData(monadModelVariancesList, monadModelLettersList) } } @@ -74,45 +71,28 @@ trait WithF extends MacroBase { val monadSuccess: Expr[MonadSuccess[F]] = directMonadInput.success val monadFailure: Option[Expr[MonadFallible[F]]] = - if (monadModelData.isFalliable) - Some('{ - ${ directMonadInput.fallible }.getOrElse { - throw new IllegalArgumentException(ErrorForWithF.make($printEffect, "", "MonadFallible")) - } - }) - else - None + directMonadInput.fallible match + case '{ Some($value) } => Some(value.asExprOf[MonadFallible[F]]) + case '{ Option($value) } => Some(value.asExprOf[MonadFallible[F]]) + case _ => None val monadSequence: Expr[MonadSequence[F]] = directMonadInput.sequence val monadSequencePar: Expr[MonadSequenceParallel[F]] = directMonadInput.sequencePar val monadState: Option[Expr[MonadState[F, S]]] = - if (!(TypeRepr.of[S] =:= TypeRepr.of[Nothing])) - Some( - '{ - ${ directMonadInput.state }.getOrElse { - throw new IllegalArgumentException(ErrorForWithF.make($printEffect, $printState, "MonadState")) - } - } - ) - else - None + directMonadInput.state match + case '{ Some($value) } => Some(value.asExprOf[MonadState[F, S]]) + case '{ Option($value) } => Some(value.asExprOf[MonadState[F, S]]) + case other => None val monadLog: Option[Expr[MonadLog[F, W]]] = - if (!(TypeRepr.of[W] =:= TypeRepr.of[Nothing])) - Some( - '{ - ${ directMonadInput.log }.getOrElse { - throw new IllegalArgumentException(ErrorForWithF.make($printEffect, $printLog, "MonadLog")) - } - } - ) - else - None + directMonadInput.log match + case '{ Some($value) } => Some(value.asExprOf[MonadLog[F, W]]) + case '{ Option($value) } => Some(value.asExprOf[MonadLog[F, W]]) + case _ => None DirectMonad[F, S, W](monadSuccess, monadFailure, monadSequence, monadSequencePar, monadState, monadLog) } } - } object ErrorForWithF { diff --git a/zio-direct/src/main/scala-3.x/zio/direct/core/metaprog/WithIR.scala b/zio-direct/src/main/scala-3.x/zio/direct/core/metaprog/WithIR.scala index 71fa523..cc88a7e 100644 --- a/zio-direct/src/main/scala-3.x/zio/direct/core/metaprog/WithIR.scala +++ b/zio-direct/src/main/scala-3.x/zio/direct/core/metaprog/WithIR.scala @@ -64,7 +64,7 @@ trait WithIR { private case class Id(code: Term) } case class Block(head: Statement, tail: Monadic) extends Monadic - case class Match(scrutinee: IR, caseDefs: IR.Match.CaseDefs) extends Monadic + case class Match(scrutinee: IR, caseDefs: IR.Match.CaseDefs, resultType: TypeRepr) extends Monadic object Match { case class CaseDefs(cases: List[IR.Match.CaseDef]) case class CaseDef(pattern: Tree, guard: Option[Term], rhs: Monadic) @@ -101,7 +101,7 @@ trait WithIR { } case class Block(head: Statement, tail: Monadic)(val zpe: ZioType) extends Monadic - case class Match(scrutinee: IRT, caseDefs: IRT.Match.CaseDefs)(val zpe: ZioType) extends Monadic + case class Match(scrutinee: IRT, caseDefs: IRT.Match.CaseDefs, resultType: TypeRepr)(val zpe: ZioType) extends Monadic case class If(cond: IRT, ifTrue: IRT, ifFalse: IRT)(val zpe: ZioType) extends Monadic case class Pure(code: Term)(val zpe: ZioType) extends IRT with Leaf object Pure { @@ -230,8 +230,8 @@ trait WithIR { case v: IR.Monad => apply(v) case IR.Block(head, tail) => IR.Block(head, apply(tail)) - case IR.Match(scrutinee, caseDefs) => - IR.Match(scrutinee, apply(caseDefs)) + case IR.Match(scrutinee, caseDefs, rs) => + IR.Match(scrutinee, apply(caseDefs), rs) case IR.If(cond, ifTrue, ifFalse) => IR.If(cond, apply(ifTrue), apply(ifFalse)) case IR.And(left, right) => IR.And(apply(left), apply(right)) case IR.Or(left, right) => IR.Or(apply(left), apply(right)) diff --git a/zio-direct/src/main/scala-3.x/zio/direct/core/metaprog/WithZioType.scala b/zio-direct/src/main/scala-3.x/zio/direct/core/metaprog/WithZioType.scala index 229536d..30ed699 100644 --- a/zio-direct/src/main/scala-3.x/zio/direct/core/metaprog/WithZioType.scala +++ b/zio-direct/src/main/scala-3.x/zio/direct/core/metaprog/WithZioType.scala @@ -248,7 +248,7 @@ trait WithZioType extends MacroBase { case effectType(r, e, a) => ZioType(effectType)(r, e, a) case _ => - report.errorAndAbort(s"The type of ${Format.Term(zio)} is not a ${effectType.tpe.show}. It is: ${Format.TypeRepr(zio.tpe)}") + report.errorAndAbort(s"The type of ${Format.Term(zio)} is not a ${effectType.tpe.show}. It is: ${Format.TypeRepr(zio.tpe.widen)}") // In this case the error is considered to be Nothing (since we are not wrapping error handling for pure values) // and the environment type is considered to be Any (it will be removed via later `ZioType.union` calls if it can be specialized). @@ -285,7 +285,12 @@ trait WithZioType extends MacroBase { def composeN(zioTypes: List[ZioType])(implicit typeUnion: TypeUnion): ZioType = val et = validateSameEffect(zioTypes, "N-composition") val (rs, es, as) = zioTypes.map(zt => (zt.r, zt.e, zt.a)).unzip3 - ZioType(validateSameEffect(zioTypes, "N-composition"))(et.composeRs(rs), et.composeEs(es), et.composeAs(as)) + ZioType(et)(et.composeRs(rs), et.composeEs(es), et.composeAs(as)) + + def composeRsEsN(zioTypes: List[ZioType], a: TypeRepr)(implicit typeUnion: TypeUnion): ZioType = + val et = validateSameEffect(zioTypes, "N-composition") + val (rs, es) = zioTypes.map(zt => (zt.r, zt.e)).unzip + ZioType(et)(et.composeRs(rs), et.composeEs(es), a) private def validateSameEffect(types: List[ZioType], label: String): ZioEffectType = { types.map(_.effectType).reduce((a, b) => { diff --git a/zio-direct/src/main/scala-3.x/zio/direct/core/norm/WithComputeType.scala b/zio-direct/src/main/scala-3.x/zio/direct/core/norm/WithComputeType.scala index 1f2c81f..98d74fc 100644 --- a/zio-direct/src/main/scala-3.x/zio/direct/core/norm/WithComputeType.scala +++ b/zio-direct/src/main/scala-3.x/zio/direct/core/norm/WithComputeType.scala @@ -28,9 +28,9 @@ trait WithComputeType { IRT.Match.CaseDef(ir.pattern, ir.guard, rhsIRT)(rhsIRT.zpe) // TODO add caseDefs-type to IRT.Match and get rid of this - def applyCaseDefs(caseDefs: IR.Match.CaseDefs): IRT.Match.CaseDefs = + def applyCaseDefs(caseDefs: IR.Match.CaseDefs, actualA: TypeRepr): IRT.Match.CaseDefs = val newCases = caseDefs.cases.map(applyCaseDef(_)) - val zpe = ZioType.composeN(newCases.map(_.zpe)).transformA(_.widen) + val zpe = ZioType.composeRsEsN(newCases.map(_.zpe), actualA) IRT.Match.CaseDefs(newCases)(zpe) def apply(ir: IR.Monadic): IRT.Monadic = @@ -117,14 +117,9 @@ trait WithComputeType { case IR.Try(tryBlock, caseDefsOpt, resultType, finallyBlock) => val tryBlockIRT = apply(tryBlock) - // We get better results by doing into the case-defs and computing the union-type of them - // than we do by getting the information from outputType because outputType is limited - // by Scala's ability to understand the try-catch. For example, if we infer from outputType, - // the error-type will never be more concrete that `Throwable`. - // (Note, widen the error type because even if it's a concrete int type - // e.g. `catch { case e: Throwable => 111 }` we don't necessarily know - // that this error will actually happen therefore it's not sensical to make it a singleton type) - val caseDefsOptIRT = caseDefsOpt.map(applyCaseDefs(_)) + // We need to compute the environment & error type of the case-defs (in case they have effect.run calls) + // but we actually know the full A-type that is going to come out of them so just pass that right in + val caseDefsOptIRT = caseDefsOpt.map(caseDef => applyCaseDefs(caseDef, resultType)) // If there is a catch clause we need to consider it's environment/error types. Otherwise just the try-clause val tpe = @@ -181,15 +176,15 @@ trait WithComputeType { def apply(ir: IR.Match): IRT.Match = ir match - case IR.Match(scrutinee, caseDefs) => + case IR.Match(scrutinee, caseDefs, resultType) => // Ultimately the scrutinee will be used if it is pure or lifted, either way we can // treat it as a value that will be flatMapped (or Mapped) against the caseDef values. val scrutineeIRT = apply(scrutinee) - // NOTE: We can possibly do the same thing as IR.Try and pass through the Match result tpe, then - // then use that to figure out what the type should be - val caseDefIRTs = applyCaseDefs(caseDefs) + // We need to accumulate environment & error params on the case-defs but we know + // what the full-type of them is going to be so pass that right in + val caseDefIRTs = applyCaseDefs(caseDefs, resultType) val zpe = scrutineeIRT.zpe.flatMappedWith(caseDefIRTs.zpe) - IRT.Match(scrutineeIRT, caseDefIRTs)(zpe) + IRT.Match(scrutineeIRT, caseDefIRTs, resultType)(zpe) def apply(ir: IR.Parallel): IRT.Parallel = ir match diff --git a/zio-direct/src/main/scala-3.x/zio/direct/core/norm/WithDecomposeTree.scala b/zio-direct/src/main/scala-3.x/zio/direct/core/norm/WithDecomposeTree.scala index 0ba81f4..e0a2abd 100644 --- a/zio-direct/src/main/scala-3.x/zio/direct/core/norm/WithDecomposeTree.scala +++ b/zio-direct/src/main/scala-3.x/zio/direct/core/norm/WithDecomposeTree.scala @@ -305,7 +305,7 @@ trait WithDecomposeTree { } else { report.errorAndAbort(s"Invalid match statement with no cases:\n${Format.Term(m)}") } - Some(IR.Match(IR.Pure(value), caseDefs)) + Some(IR.Match(IR.Pure(value), caseDefs, m.tpe)) case _ => None } @@ -334,9 +334,7 @@ trait WithDecomposeTree { Unsupported.Error.withTree( term, s"""|Cannot call a ${effectName}-effect on the effect-type ${Format.TypeRepr(et.tpe)}. - |There is no instance of a MonadState defined for it. Perhaps you need to import a context? - |For example, for zio-direct-pure: - | import zio.direct.pure._ + |There is no instance of a MonadState defined for it. |""".stripMargin ) diff --git a/zio-direct/src/main/scala-3.x/zio/direct/core/norm/WithReconstructTree.scala b/zio-direct/src/main/scala-3.x/zio/direct/core/norm/WithReconstructTree.scala index e1bacb8..1b915d8 100644 --- a/zio-direct/src/main/scala-3.x/zio/direct/core/norm/WithReconstructTree.scala +++ b/zio-direct/src/main/scala-3.x/zio/direct/core/norm/WithReconstructTree.scala @@ -306,7 +306,7 @@ trait WithReconstructTree { // Note: // The `e` type can change because you can specify a ZIO in the response to the try // e.g: (x:ZIO[Any, Throwable, A]).catchSome { case io: IOException => y:ZIO[Any, OtherExcpetion, A] } - val methodType = MethodType(List("tryLamParam"))(_ => List(TypeRepr.of[zioTry_E]), _ => TypeRepr.of[zioOut]) + val methodType = MethodType(List("tryLamParam"))(_ => List(TypeRepr.of[Throwable]), _ => TypeRepr.of[zioOut]) val methSym = Symbol.newMethod(Symbol.spliceOwner, "tryLam", methodType) // Now we actually make the method with the body: @@ -314,23 +314,25 @@ trait WithReconstructTree { val method = DefDef(methSym, sm => Some(Match(sm(0)(0).asInstanceOf[Term], scalaCaseDefs.map(_.changeOwner(methSym))))) // NOTE: Be sure that error here is the same one as used to define tryLamParam. Otherwise, AbstractMethodError errors // saying that .isDefinedAt is abstract will happen. - val pfTree = TypeRepr.of[PartialFunction[zioTry_E, zioOut]] + // NOTE: Using Throwable here instead of zioTry_E fixes Mega-Phase issues + // that are happening here: https://github.com/zio/zio-direct/commit/5044bb354fddf24f638d54750e7310a2565d4c31 + val pfTree = TypeRepr.of[PartialFunction[Throwable, zioOut]] // Assemble the peices together into a closure val closure = Closure(Ref(methSym), Some(pfTree)) - val functionBlock = '{ ${ Block(List(method), closure).asExpr }.asInstanceOf[PartialFunction[zioTry_E, zioOut]] } - val tryExpr = '{ ${ tryTerm.expr }.asInstanceOf[zioTry] } + val functionBlock = Block(List(method), closure) + // val tryExpr = '{ ${ tryTerm.expr }.asInstanceOf[zioTry] } // val monadExpr = '{ ${ tryTerm.asExpr }.asInstanceOf[zioRET].catchSome { ${ functionBlock } } } // val monadZioType = tryBlock.zpe.flatMappedWith(caseDefs.zpe).transformA(_ => tryBlock.) // Use the wholeTryZpe. The R, E should ahve been unified in the IRT type computation in WithComputeType // and the chosen A type should have been determined by scala and also used in WithComputeType (in the apply(ir: IR.Try) case there) - val monadZioValue = InvokeWith(wholeTryZpe).CatchSome(monadFailure)(tryExpr.asTerm.toZioValue(tryBlock.zpe), functionBlock.toZioValue(caseDefs.zpe)) + val monadZioValue = InvokeWith(wholeTryZpe).CatchSome(monadFailure)(tryTerm, functionBlock.toZioValue(caseDefs.zpe)) (wholeTryZpe, monadZioValue) } def reconstructMatch(irt: IRT.Match): ZioValue = - val IRT.Match(scrutinee, caseDefs) = irt + val IRT.Match(scrutinee, caseDefs, resultType) = irt scrutinee match case value: IRT.Monadic => val monad = apply(value) diff --git a/zio-direct/src/main/scala-3.x/zio/direct/future/FutureDsl.scala b/zio-direct/src/main/scala-3.x/zio/direct/future/FutureDsl.scala new file mode 100644 index 0000000..13e98e4 --- /dev/null +++ b/zio-direct/src/main/scala-3.x/zio/direct/future/FutureDsl.scala @@ -0,0 +1,36 @@ +package zio.direct.future + +import scala.compiletime.summonInline +import zio.direct._ +import MonadShape.Variance._ +import MonadShape.Letter._ +import scala.concurrent.ExecutionContext +import scala.concurrent.Future +import zio.direct.core.NotDeferredException +import scala.util.Failure +import scala.util.Success + +//transparent inline def defer(implicit inline ctx: ExecutionContext) = new deferCallImpl(ctx) + +object defer extends deferCall[[R, E, A] =>> Future[A], Future[?], Nothing, Nothing, FutureMonadModel] { + transparent inline def success = futureSuccess(summonInline[ExecutionContext]) + transparent inline def fallible = Some(futureFallible(summonInline[ExecutionContext])) + transparent inline def sequence = futureSequence(summonInline[ExecutionContext]) + transparent inline def sequencePar = futureSequencePar(summonInline[ExecutionContext]) + transparent inline def state = None + transparent inline def log = None +} + +extension [A](value: Future[A]) { + @directRunCall + def run: A = NotDeferredException.fromNamed("run") +} + +type ThreeFuture[R, E, A] = Future[A] +type FutureMonadModel = MonadModel { + type Variances = MonadShape.Variances1[Covariant] + type Letters = MonadShape.Letters1[A] + type IsFallible = true + type IsStateful = false + type IsLogging = false +} diff --git a/zio-direct/src/main/scala-3.x/zio/direct/future/FutureMonad.scala b/zio-direct/src/main/scala-3.x/zio/direct/future/FutureMonad.scala new file mode 100644 index 0000000..24ce05a --- /dev/null +++ b/zio-direct/src/main/scala-3.x/zio/direct/future/FutureMonad.scala @@ -0,0 +1,61 @@ +package zio.direct.future + +import scala.compiletime.summonInline +import zio.direct._ +import MonadShape.Variance._ +import MonadShape.Letter._ +import scala.concurrent.ExecutionContext +import scala.concurrent.Future +import zio.direct.core.NotDeferredException +import scala.util.Failure +import scala.util.Success + +def futureSuccess(implicit ctx: ExecutionContext): MonadSuccess[ThreeFuture] = new MonadSuccess[ThreeFuture] { + def unit[A](a: => A): ThreeFuture[Any, Nothing, A] = Future(a) + def map[R, E, A, B](first: ThreeFuture[R, E, A])(andThen: A => B): ThreeFuture[R, E, B] = first.map[B](andThen) + def flatMap[R, E, A, B](first: ThreeFuture[R, E, A])(andThen: A => ThreeFuture[R, E, B]): ThreeFuture[R, E, B] = first.flatMap[B](andThen) + def flatten[R, E, A, R1 <: R, E1 >: E](first: ThreeFuture[R, E, ThreeFuture[R1, E1, A]]): ThreeFuture[R1, E1, A] = first.flatten +} + +def futureFallible(implicit ctx: ExecutionContext): MonadFallible[ThreeFuture] = new MonadFallible[ThreeFuture] { + def fail[E](e: => E): ThreeFuture[Any, Nothing, Nothing] = + e match { + case t: Throwable => Future.failed(t) + case _ => Future.failed(new RuntimeException(s"Tried to fail future with non-throwable: ${e}")) + } + def attempt[A](a: => A): Future[A] = Future(a) + + def catchSome[R, E, A](first: Future[A])(andThen: PartialFunction[E, Future[A]]): Future[A] = + val pf: PartialFunction[Throwable, E] = { + case e: Throwable => e.asInstanceOf[E] + } + first.recoverWith(pf.andThen(andThen)) + + def ensuring[R, E, A](f: Future[A])(finalizer: Future[Any]): Future[A] = + f.transformWith { + case Success(value) => finalizer.flatMap(_ => Future(value)) + case Failure(exception) => finalizer.flatMap(_ => Future.failed(exception)) + } + + def mapError[R, E, A, E2](first: Future[A])(f: E => E2): Future[A] = + first.transform { + case s: Success[A] => s + case Failure(exception) => Failure(f(exception.asInstanceOf[E]).asInstanceOf[Throwable]) + } + + def orDie[R, E <: Throwable, A](first: Future[A]): Future[A] = first +} + +def futureSequence(implicit ctx: ExecutionContext): MonadSequence[ThreeFuture] = new MonadSequence[ThreeFuture] { + inline def foreach[R, E, A, B, Collection[+Element] <: Iterable[Element]]( + in: Collection[A] + )(f: A => ThreeFuture[R, E, B])(implicit bf: scala.collection.BuildFrom[Collection[A], B, Collection[B]]): ThreeFuture[R, E, Collection[B]] = + Future.sequence(in.map(f(_))).map(list => bf.fromSpecific(in)(list)) +} + +def futureSequencePar(implicit ctx: ExecutionContext): MonadSequenceParallel[ThreeFuture] = new MonadSequenceParallel[ThreeFuture] { + def foreachPar[R, E, A, B, Collection[+Element] <: Iterable[Element]]( + in: Collection[A] + )(f: A => ThreeFuture[R, E, B])(implicit bf: scala.collection.BuildFrom[Collection[A], B, Collection[B]]): ThreeFuture[R, E, Collection[B]] = + futureSequence.foreach(in)(f) +} diff --git a/zio-direct/src/main/scala-3.x/zio/direct/list/ListDsl.scala b/zio-direct/src/main/scala-3.x/zio/direct/list/ListDsl.scala index 07ccb5f..09c077c 100644 --- a/zio-direct/src/main/scala-3.x/zio/direct/list/ListDsl.scala +++ b/zio-direct/src/main/scala-3.x/zio/direct/list/ListDsl.scala @@ -4,9 +4,16 @@ import zio.direct.deferCall import zio.direct.directRunCall import zio.direct.core.NotDeferredException -object select extends deferCall[[R, E, A] =>> List[A], List[?], Nothing, Nothing, ListMonadModel](listMonadSuccess, None, listMonadSequence, listMonadSequencePar, None, None) +object select extends deferCall[[R, E, A] =>> List[A], List[?], Nothing, Nothing, ListMonadModel] { + transparent inline def success = listMonadSuccess + transparent inline def fallible = None + transparent inline def sequence = listMonadSequence + transparent inline def sequencePar = listMonadSequencePar + transparent inline def state = None + transparent inline def log = None +} -extension [R, E, A](value: List[A]) { +extension [A](value: List[A]) { @directRunCall def from: A = NotDeferredException.fromNamed("from") } diff --git a/zio-direct/src/main/scala-3.x/zio/direct/list/ListMonad.scala b/zio-direct/src/main/scala-3.x/zio/direct/list/ListMonad.scala index a037e28..af7eaef 100644 --- a/zio-direct/src/main/scala-3.x/zio/direct/list/ListMonad.scala +++ b/zio-direct/src/main/scala-3.x/zio/direct/list/ListMonad.scala @@ -1,8 +1,6 @@ package zio.direct.list import zio.direct._ -import zio.ZIO - import MonadShape.Variance._ import MonadShape.Letter._ @@ -11,7 +9,6 @@ type ThreeList[R, E, A] = List[A] type ListMonadModel = MonadModel { type Variances = MonadShape.Variances1[Covariant] type Letters = MonadShape.Letters1[A] - type IsFallible = false } implicit inline def listMonadSuccess: MonadSuccess[ThreeList] = ListMonadSuccess @@ -57,9 +54,6 @@ object ListMonadSequence extends MonadSequence[ThreeList] { )(f: A => ThreeList[R, E, B])(implicit bf: scala.collection.BuildFrom[Collection[A], B, Collection[B]]): ThreeList[R, E, Collection[B]] = val traversed = traverseThreeList[R, E, A, B](in.toList, f) val out = traversed.map(list => bf.fromSpecific(in)(list)) - // println(s"==== IN: ${in}") - // println(s"==== Traversed: ${traversed}") - // println(s"==== Out: ${out}") out }