Skip to content

Commit

Permalink
Clean up DSL, make certain sections inline (#42)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
deusaquilus authored Feb 16, 2023
1 parent 6f7b4bc commit 47f1bc2
Show file tree
Hide file tree
Showing 22 changed files with 474 additions and 260 deletions.
47 changes: 25 additions & 22 deletions zio-direct-pure/src/main/scala-3.x/zio/direct/pure/PureDsl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
107 changes: 54 additions & 53 deletions zio-direct-pure/src/main/scala-3.x/zio/direct/pure/PureMonad.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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._
Expand Down Expand Up @@ -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(
Expand All @@ -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")))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Loading

0 comments on commit 47f1bc2

Please sign in to comment.