-
Notifications
You must be signed in to change notification settings - Fork 115
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Further Generalize ZPure #461
Conversation
override def tag: Int = Tags.Log | ||
} | ||
|
||
private final case class GetLog[W, S]() extends ZPure[W, S, S, Any, Nothing, Chunk[W]] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure this is the optimal encoding of getting the log.
@jdegoes This is ready for another review. |
* `Cause` is a data type that represents the potentially multiple ways that a | ||
* computation can fail. | ||
*/ | ||
sealed trait Cause[+E] { self => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is more general than just failure. Especially if we add an empty, it becomes a free semiring. It maybe should live in zio.prelude
directly and not speak about errors, just "events" or something, in parallel and sequence. It can be used for errors but could be used for tests and other things. Anywhere you need a tree of parallel / sequential operations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we could definitely generalize it and then just have Cause
be a type alias for it.
@jdegoes I generalized |
@jdegoes I added a second type parameter to |
* return a new collection of events that represents this collection of | ||
* events in parallel with that collection of events. | ||
*/ | ||
final def &&[Z1 >: Z <: Unit, A1 >: A](that: Semiring[Z1, A1]): Semiring[Z1, A1] = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're losing information right now that combining an empty collection with a nonempty collection results in a nonempty collection. Potentially we could could use overloading or type level programming to recover this.
* events, collecting them back into a single collection of events. | ||
*/ | ||
final def flatMap[Z1 >: Z <: Unit, B](f: A => Semiring[Z1, B]): Semiring[Z1, B] = | ||
fold(Semiring.empty, f)(_ ++ _, _ && _).asInstanceOf[Semiring[Z1, B]] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We know that the resulting collection can only be empty if Z
or Z1
is Unit
but we still have to handle the case where the initial collection is empty so we have to cast here.
* events. | ||
*/ | ||
final def foreach[F[+_]: IdentityBoth: Covariant, B](f: A => F[B]): F[Semiring[Z, B]] = | ||
fold[F[Semiring[Unit, B]]](Semiring.empty.succeed, a => f(a).map(Semiring.single))( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar issue as above.
} | ||
} | ||
|
||
case object Empty extends Semiring[Unit, Nothing] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pattern matches against Empty
don't work well because it is not known to be a valid subtype if Z
is abstract, since Z
could be Nothing
. Maybe if we go down this route we should make the constructors private and only expose them through folding and the operators we provide?
@@ -516,38 +541,42 @@ sealed trait ZPure[-S1, +S2, -R, +E, +A] { self => | |||
final def run(s: S1)(implicit ev1: Any <:< R, ev2: E <:< Nothing): (S2, A) = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the additional functionality we are adding there is a risk of having a ton of different ways to run
a ZPure
value. Also, I would like to improve the ergonomics in the many cases where there is no state type.
I was thinking about trying to make the following the primary method to run ZPure
values:
final def run(implicit ev1: Unit <:< S1, ev2: Any <:< R, ev3: E <:< Nothing): A
Then we could have a runLog
variant that exposes the value and the log. Users would provide the input state through a provideState
operator if they had not already set an initial state, similar to how you provide the environment to a ZIO
effect before running it, and would normally handle their errors before running it, even by just doing zPure.either
. We could perhaps have a runEither
variant and maybe a runState
variant for when you are just doing state but I would like to avoid users having to always provide a Unit
state type when running computations and having every possible variant of state, logging, error, and value outputs.
|
||
object Semiring { | ||
|
||
final case class Both[+Z <: Unit, +A](left: Semiring[Z, A], right: Semiring[Z, A]) extends Semiring[Z, A] { self => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could potentially make this:
final case class Both[+A](left: Semiring[Nothing, A], right: Semiring[Nothing, A]) extends Semiring[Nothing, A]
Then in ++
and &&
we could just ignore empty values in composition. Basically say that if there is an empty element it has to occur at the root, there can't be empty elements at arbitrary nodes in the tree.
Ready to merge, although let's change the name |
This PR explores further generalizing ZPure in two ways:
W
representing a log that can be written to in the course of executing the computationzipWithPar
operator that would not do actual concurrency but would perform both computations, even if the first one failed, and accumulate all errors in aCause
data structure.The idea is that with these changes we would be able to delete the
Validation
data type and it would just become:ZPure
would also be able to model the concept of non-fatal errors through theW
type, potentially allowingThese
to be a more unbiased data representing the concept of either or both versus explicitly modeling non-fatal errors.