Skip to content

Commit

Permalink
Remove implicits from typeclasses (for now), add tests and fix various (
Browse files Browse the repository at this point in the history
#41)

* Remove implicits from typeclasses (for now), add tests and fix various

* Fixing tests and Scala 2 impls

* Slight refactoring, fixing Scala 2
  • Loading branch information
deusaquilus authored Feb 13, 2023
1 parent cb3d8ff commit 6f7b4bc
Show file tree
Hide file tree
Showing 42 changed files with 1,329 additions and 344 deletions.
3 changes: 2 additions & 1 deletion project/BuildHelper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,9 @@ object BuildHelper {

def extraOptions(scalaVersion: String) =
CrossVersion.partialVersion(scalaVersion) match {
case Some((3, 0)) =>
case Some((3, 2)) =>
Seq(
//"-Xprint-suspension"
// "-language:implicitConversions",
// "-Xignore-scala2-macros"
)
Expand Down
56 changes: 56 additions & 0 deletions zio-direct-pure/src/main/scala-3.x/zio/direct/pure/PureDsl.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package zio.direct.pure

import zio.direct.deferCall
import zio.direct.directRunCall
import zio.direct.directGetCall
import zio.direct.directSetCall
import zio.direct.directLogCall
import zio.prelude.fx.ZPure
import zio.direct.core.NotDeferredException

// Using this plainly can possibly case a cyclical macro error? Not sure why
// class deferWithParams[W, S] extends deferCall[[R, E, A] =>> ZPure[W, S, S, R, E, A], ZPure[?, ?, ?, ?, ?, ?], S, W]
// object deferWithParams {
// def apply[W, S] = new deferWithParams[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
}

/** Helper method to do logging */
@directLogCall
def log(w: W): Unit = ZPure.log(w).eval

object Wrap {
def succeed[T](value: T) = ZPure.succeed[S, T](value)
def attempt[T](value: T) = ZPure.attempt[S, T](value)
}
}

type ZPureProxy[R, E, A] = ZPure[_, _, _, R, E, A]

extension [R, E, A](value: ZPureProxy[R, E, A]) {
@directRunCall
def eval: A = NotDeferredException.fromNamed("eval")
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ package zio.direct.pure

import zio.direct._
import zio.prelude.fx.ZPure
import zio.prelude.fx
import zio.ZIO

import MonadShape.Variance._
import MonadShape.Letter._
import zio.CanFail

implicit def pureMonadModel[W, S]: MonadModel[[R, E, A] =>> ZPure[W, S, S, R, E, A]] {
type Variances = MonadShape.Variances6[Unused, Unused, Unused, Contravariant, Covariant, Covariant]
type Letters = MonadShape.Letters6[Other, Other, Other, R, E, A]
} = new MonadModel[[R, E, A] =>> ZPure[W, S, S, R, E, A]] {
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
}

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]] {
Expand All @@ -22,27 +22,52 @@ implicit def zpureMonadSuccess[W, S]: MonadSuccess[[R, E, A] =>> ZPure[W, S, S,
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)
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.ensuring(finalizer)
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)
def orDie[R, E <: Throwable, A](first: ZPure[W, S, S, R, E, A]): ZPure[W, S, S, R, Nothing, 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 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 zstreamMonadSequence[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]] {
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]] =
// TODO Same problem again. Need a different type for the finalization
???
ZPure.forEach((in: Iterable[A]))(f).map(col => bf.fromSpecific(in)(col))
}

implicit def zstreamMonadSequencePar[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]] {
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]] =
// TODO Same problem again. Need a different type for the finalization
???
zpureMonadSequence.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]
}

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)
}
15 changes: 0 additions & 15 deletions zio-direct-pure/src/main/scala-3.x/zio/direct/stream/PureDsl.scala

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package zio.direct.pure

// This needs to be in a separate file or Scala 3 will
// complain about "Cyclic macro dependencies"
case class MyState(value: String)
147 changes: 147 additions & 0 deletions zio-direct-pure/src/test/scala-3.x/zio/direct/pure/PureSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package zio.direct.pure

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 zio.prelude.fx.ZPure


object PureSpec extends DeferRunSpec {
val dc = deferWith[String, MyState]
import dc._
val init = MyState("init")

val e = new Exception("blah")
val e1 = new Exception("blahblah")

val spec = suite("VariaSpec")(
test("Simple Sequence") {
val out =
deferWith[Nothing, Any].defer {
val a = ZPure.succeed(1)
val b = ZPure.succeed("foo")
(a.eval, b.eval)
}
assertIsType[ZPure[Nothing, Any, Any, Any, Nothing, (Int, String)]](out) andAssert
assert(out.run)(equalTo((1, "foo"))) //
},
test("Simple Sequence with State") {
val out =
defer {
val s1 = ZPure.get[MyState].eval.value
val a = ZPure.succeed[MyState, String](s1).eval
ZPure.set(MyState("foo")).eval
val b = ZPure.succeed[MyState, String]("bar").eval
val s2 = ZPure.get[MyState].eval.value
(s1, a, b, s2)
}

assertIsType[ZPure[String, MyState, MyState, Any, Nothing, (String, String, String, String)]](out) andAssert
assert(out.provideState(MyState("init")).run)(equalTo(("init", "init", "bar", "foo")))
},
test("Simple Sequence with State - using primitives and logging") {
val out = // ddd
defer {
val s1 = State.get().value
val a = ZPure.succeed[MyState, String](s1).eval
log(a)
State.set(MyState("foo"))
val b = ZPure.succeed[MyState, String]("bar").eval
log(b)
val s2 = State.get().value
(s1, a, b, s2)
}
assert(out.runAll(MyState("init")))(equalTo(
(Chunk("init", "bar"), Right((MyState("foo"), ("init", "init", "bar", "foo"))))
))
},
test("Impure/Impure If-statement") {
val out = defer {
if (ZPure.succeed[MyState, Int](2).eval == 2)
val v = ZPure.succeed[MyState, String]("foo").eval
State.set(MyState(v))
v
else
val v = ZPure.succeed[MyState, String]("bar").eval
State.set(MyState(v))
v
}
assert(out.run(MyState("init")))(equalTo((MyState("foo"), "foo")))
},
test("Impure/Impure Pat-match") {
val out =
defer {
ZPure.succeed[MyState, String]("a").eval match {
case "a" => ZPure.succeed[MyState, Int](1).eval
case "b" => ZPure.succeed[MyState, Int](2).eval
}
}
assert(out.run(init))(equalTo((init, 1)))
},
test("Try/Catch caught") {
val out =
defer {
try {
val num = ZPure.succeed[MyState, Int](1).eval
if (num == 1) {
throw new FooError
} else {
num
}
} catch {
case _: FooError => ZPure.succeed[MyState, Int](18).eval
}
}
assertIsType[ZPure[String, MyState, MyState, Any, FooError, Int]](out) andAssert
assert(out.catchAll(e => throw e).run(init))(equalTo((init, 18)))
},
test("Try/Catch NOT caught") {
val fooError = new FooError
val out =
defer {
try {
val num = Wrap.succeed(3).eval
if (num == 3) {
throw fooError
} else {
num
}
} catch {
case _: BarError => Wrap.succeed(33).eval
}
}
assert(out.runAll(init))(equalTo(
// NOTE that the Chunk() is the logging, not the init
(Chunk(), Left(zio.prelude.fx.Cause(fooError)))
))
},
test("Throw-fail") {
val fooError = new FooError
val out =
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
val out =
defer(Use.withLenientCheck) {
for (i <- Wrap.succeed(List(1, 2, 3)).eval) {
Wrap.succeed(v += i).eval
}
}
// 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))
}
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import zio.direct.directRunCall
import zio.stream.ZStream
import zio.direct.core.NotDeferredException

object defer extends deferCall[ZStream, ZStream[?, ?, ?]]
object defer extends deferCall[ZStream, ZStream[?, ?, ?], Nothing, Nothing, StreamMonadModel](zstreamMonadSuccess, Some(zstreamMonadFallible), zstreamMonadSequence, zstreamMonadSequencePar, None, 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 @@ -6,12 +6,12 @@ import zio.ZIO

import MonadShape.Variance._
import MonadShape.Letter._
implicit val streamMonadModel: MonadModel[ZStream] {
type Variances = MonadShape.Variances3[Contravariant, Covariant, Covariant]
type Letters = MonadShape.Letters3[R, E, A]
} = new MonadModel[ZStream] {
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] {
Expand All @@ -32,17 +32,23 @@ implicit val zstreamMonadFallible: MonadFallible[ZStream] = new MonadFallible[ZS
}

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]] =
// TODO Same problem again. Need a different type for the finalization
ZStream.fromZIO(ZIO.foreach(in)((a: A) => f(a).runHead.map(_.get)))
// 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
ZStream.fromZIO(ZIO.foreachPar(in)((a: A) => f(a).runHead.map(_.get)))
zstreamMonadSequence.foreach(in)(f)
}
Loading

0 comments on commit 6f7b4bc

Please sign in to comment.