diff --git a/core-tests/shared/src/test/scala/zio/prelude/CommutativeBothSpec.scala b/core-tests/shared/src/test/scala/zio/prelude/CommutativeBothSpec.scala index c8f59791a..d7c1c2ddb 100644 --- a/core-tests/shared/src/test/scala/zio/prelude/CommutativeBothSpec.scala +++ b/core-tests/shared/src/test/scala/zio/prelude/CommutativeBothSpec.scala @@ -5,6 +5,7 @@ import zio.test._ import zio.test.laws._ object CommutativeBothSpec extends ZIOBaseSpec { + import Fixtures._ def spec: Spec[Environment, Any] = suite("CommutativeBothSpec")( @@ -12,6 +13,7 @@ object CommutativeBothSpec extends ZIOBaseSpec { test("chunk")(checkAllLaws(CommutativeBothLaws)(GenF.chunk, Gen.chunkOf(Gen.int))), test("list")(checkAllLaws(CommutativeBothLaws)(GenF.list, Gen.int)), test("option")(checkAllLaws(CommutativeBothLaws)(GenF.option, Gen.int)), + test("optional")(checkAllLaws(CommutativeBothLaws)(optionalGenF, Gen.int)), test("vector")(checkAllLaws(CommutativeBothLaws)(GenF.vector, Gen.vectorOf(Gen.int))) ) ) diff --git a/core-tests/shared/src/test/scala/zio/prelude/Fixtures.scala b/core-tests/shared/src/test/scala/zio/prelude/Fixtures.scala index 6458cf7d7..fa98ee4ee 100644 --- a/core-tests/shared/src/test/scala/zio/prelude/Fixtures.scala +++ b/core-tests/shared/src/test/scala/zio/prelude/Fixtures.scala @@ -1,5 +1,6 @@ package zio.prelude +import zio.prelude.data.Optional import zio.test.laws.GenF import zio.test.{Gen, Sized} import zio.{Chunk, Trace} @@ -22,4 +23,14 @@ object Fixtures { implicit val chunkOptionInvariant: Invariant[ChunkOption] = Invariant[Chunk].compose[Option] + + def anyOptional[R, A](gen: Gen[R, A])(implicit trace: Trace): Gen[R, Optional[A]] = + Gen.option(gen).map(Optional.OptionIsNullable) + + val optionalGenF: GenF[Any, Optional] = + new GenF[Any, Optional] { + def apply[R1, A](gen: Gen[R1, A])(implicit trace: Trace): Gen[R1, Optional[A]] = + anyOptional(gen) + } + } diff --git a/core-tests/shared/src/test/scala/zio/prelude/ForEachSpec.scala b/core-tests/shared/src/test/scala/zio/prelude/ForEachSpec.scala index b06404c03..941360e54 100644 --- a/core-tests/shared/src/test/scala/zio/prelude/ForEachSpec.scala +++ b/core-tests/shared/src/test/scala/zio/prelude/ForEachSpec.scala @@ -54,6 +54,7 @@ object ForEachSpec extends ZIOBaseSpec { test("list")(checkAllLaws(ForEachLaws)(GenF.list, Gen.int)), test("map")(checkAllLaws(ForEachLaws)(GenFs.map(Gen.int), Gen.int)), test("option")(checkAllLaws(ForEachLaws)(GenF.option, Gen.int)), + test("optional")(checkAllLaws(ForEachLaws)(optionalGenF, Gen.int)), test("vector")(checkAllLaws(ForEachLaws)(GenF.vector, Gen.int)) ), suite("combinators")( diff --git a/core-tests/shared/src/test/scala/zio/prelude/IdentityBothSpec.scala b/core-tests/shared/src/test/scala/zio/prelude/IdentityBothSpec.scala index 234744ca3..28d121632 100644 --- a/core-tests/shared/src/test/scala/zio/prelude/IdentityBothSpec.scala +++ b/core-tests/shared/src/test/scala/zio/prelude/IdentityBothSpec.scala @@ -5,6 +5,7 @@ import zio.test._ import zio.test.laws._ object IdentityBothSpec extends ZIOBaseSpec { + import Fixtures._ def spec: Spec[Environment, Any] = suite("IdentityBothSpec")( @@ -12,6 +13,7 @@ object IdentityBothSpec extends ZIOBaseSpec { test("either")(checkAllLaws(IdentityBothLaws)(GenF.either(Gen.int), Gen.int)), test("list")(checkAllLaws(IdentityBothLaws)(GenF.list, Gen.int)), test("option")(checkAllLaws(IdentityBothLaws)(GenF.option, Gen.int)), + test("optional")(checkAllLaws(IdentityBothLaws)(optionalGenF, Gen.int)), test("try")(checkAllLaws(IdentityBothLaws)(GenFs.tryScala, Gen.int)) ) ) diff --git a/core-tests/shared/src/test/scala/zio/prelude/IdentityEitherSpec.scala b/core-tests/shared/src/test/scala/zio/prelude/IdentityEitherSpec.scala index 197731529..b01be6f02 100644 --- a/core-tests/shared/src/test/scala/zio/prelude/IdentityEitherSpec.scala +++ b/core-tests/shared/src/test/scala/zio/prelude/IdentityEitherSpec.scala @@ -5,6 +5,7 @@ import zio.test._ import zio.test.laws._ object IdentityEitherSpec extends ZIOBaseSpec { + import Fixtures._ def spec: Spec[Environment, Any] = suite("IdentityEitherSpec")( @@ -12,6 +13,7 @@ object IdentityEitherSpec extends ZIOBaseSpec { test("chunk")(checkAllLaws(IdentityEitherlaws)(GenF.chunk, Gen.int)), test("list")(checkAllLaws(IdentityEitherlaws)(GenF.list, Gen.int)), test("option")(checkAllLaws(IdentityEitherlaws)(GenF.option, Gen.int)), + test("optional")(checkAllLaws(IdentityEitherlaws)(optionalGenF, Gen.int)), test("set")(checkAllLaws(IdentityEitherlaws)(GenF.set, Gen.int)), test("vector")(checkAllLaws(IdentityEitherlaws)(GenF.vector, Gen.int)) ) diff --git a/core-tests/shared/src/test/scala/zio/prelude/IdentityFlattenSpec.scala b/core-tests/shared/src/test/scala/zio/prelude/IdentityFlattenSpec.scala index ff2034ca8..4736b1e59 100644 --- a/core-tests/shared/src/test/scala/zio/prelude/IdentityFlattenSpec.scala +++ b/core-tests/shared/src/test/scala/zio/prelude/IdentityFlattenSpec.scala @@ -5,6 +5,7 @@ import zio.test._ import zio.test.laws._ object IdentityFlattenSpec extends ZIOBaseSpec { + import zio.prelude.Fixtures._ def spec: Spec[Environment, Any] = suite("IdentityFlattenSpec")( @@ -13,6 +14,7 @@ object IdentityFlattenSpec extends ZIOBaseSpec { test("either")(checkAllLaws(IdentityFlattenLaws)(GenFs.either(Gen.int), Gen.int)), test("list")(checkAllLaws(IdentityFlattenLaws)(GenF.list, Gen.int)), test("option")(checkAllLaws(IdentityFlattenLaws)(GenF.option, Gen.int)), + test("optional")(checkAllLaws(IdentityFlattenLaws)(optionalGenF, Gen.int)), test("vector")(checkAllLaws(IdentityFlattenLaws)(GenF.vector, Gen.int)) ) ) diff --git a/core/shared/src/main/scala/zio/prelude/AssociativeBoth.scala b/core/shared/src/main/scala/zio/prelude/AssociativeBoth.scala index d4d7e46a4..289e647e4 100644 --- a/core/shared/src/main/scala/zio/prelude/AssociativeBoth.scala +++ b/core/shared/src/main/scala/zio/prelude/AssociativeBoth.scala @@ -18,6 +18,7 @@ package zio.prelude import zio._ import zio.prelude.coherent.CovariantIdentityBoth +import zio.prelude.data.Optional import zio.prelude.newtypes.{AndF, Failure, OrF} import zio.stm.ZSTM import zio.stream.{ZSink, ZStream} @@ -1185,9 +1186,31 @@ object AssociativeBoth extends AssociativeBothLowPriority { Some(()) def both[A, B](fa: => Option[A], fb: => Option[B]): Option[(A, B)] = - (fa, fb) match { - case (Some(a), Some(b)) => Some((a, b)) - case _ => None + fa match { + case Some(a) => + fb match { + case Some(b) => Some((a, b)) + case None => None + } + case None => None + } + } + + /** + * The [[IdentityBoth]] (with [[AssociativeBoth]]) instance for [[zio.prelude.data.Optional]]. + */ + implicit val OptionalIdentityBoth: IdentityBoth[Optional] = + new IdentityBoth[Optional] { + val any: Optional[Any] = Optional.Present(()) + + def both[A, B](fa: => Optional[A], fb: => Optional[B]): Optional[(A, B)] = + fa match { + case Optional.Present(a) => + fb match { + case Optional.Present(b) => Optional.Present((a, b)) + case Optional.Absent => Optional.Absent + } + case Optional.Absent => Optional.Absent } } diff --git a/core/shared/src/main/scala/zio/prelude/AssociativeEither.scala b/core/shared/src/main/scala/zio/prelude/AssociativeEither.scala index 1bb956e4d..2f1256cd6 100644 --- a/core/shared/src/main/scala/zio/prelude/AssociativeEither.scala +++ b/core/shared/src/main/scala/zio/prelude/AssociativeEither.scala @@ -17,6 +17,7 @@ package zio.prelude import zio._ +import zio.prelude.data.Optional import zio.prelude.newtypes.Failure import zio.stream.ZStream @@ -157,6 +158,17 @@ object AssociativeEither { None } + /** + * The [[IdentityEither]] (and [[AssociativeEither]]) instance for [[zio.prelude.data.Optional]]. + */ + implicit val OptionalIdentityEither: IdentityEither[Optional] = + new IdentityEither[Optional] { + def either[A, B](fa: => Optional[A], fb: => Optional[B]): Optional[Either[A, B]] = + fa.map(Left(_)) orElse fb.map(Right(_)) + + val none: Optional[Nothing] = Optional.Absent + } + /** * The `AssociativeEither` instance for `Schedule`. */ diff --git a/core/shared/src/main/scala/zio/prelude/AssociativeFlatten.scala b/core/shared/src/main/scala/zio/prelude/AssociativeFlatten.scala index 9f48340ee..19e5d7acb 100644 --- a/core/shared/src/main/scala/zio/prelude/AssociativeFlatten.scala +++ b/core/shared/src/main/scala/zio/prelude/AssociativeFlatten.scala @@ -17,6 +17,7 @@ package zio.prelude import zio._ +import zio.prelude.data.Optional import zio.stm.ZSTM import zio.stream.ZStream @@ -162,6 +163,16 @@ object AssociativeFlatten { def flatten[A](ffa: Option[Option[A]]): Option[A] = ffa.flatten } + /** + * The [[AssociativeFlatten]] and [[IdentityFlatten]] instance for [[zio.prelude.data.Optional]]. + */ + implicit val OptionalIdentityFlatten: IdentityFlatten[Optional] = + new IdentityFlatten[Optional] { + def any: Optional[Any] = Optional.Present(()) + + def flatten[A](ffa: Optional[Optional[A]]): Optional[A] = ffa.flatten + } + /** * The `AssociativeFlatten` and `IdentityFlatten` instance for `Try`. */ diff --git a/core/shared/src/main/scala/zio/prelude/CommutativeBoth.scala b/core/shared/src/main/scala/zio/prelude/CommutativeBoth.scala index 326c192ce..eb9a1ef03 100644 --- a/core/shared/src/main/scala/zio/prelude/CommutativeBoth.scala +++ b/core/shared/src/main/scala/zio/prelude/CommutativeBoth.scala @@ -17,6 +17,7 @@ package zio.prelude import zio._ +import zio.prelude.data.Optional import zio.prelude.newtypes.{AndF, Failure, OrF} import zio.stream.{ZSink, ZStream} @@ -84,9 +85,29 @@ object CommutativeBoth { implicit val OptionCommutativeBoth: CommutativeBoth[Option] = new CommutativeBoth[Option] { def both[A, B](fa: => Option[A], fb: => Option[B]): Option[(A, B)] = - (fa, fb) match { - case (Some(a), Some(b)) => Some((a, b)) - case _ => None + fa match { + case Some(a) => + fb match { + case Some(b) => Some((a, b)) + case None => None + } + case None => None + } + } + + /** + * The [[CommutativeBoth]] instance for [[zio.prelude.data.Optional]]. + */ + implicit val OptionalCommutativeBoth: CommutativeBoth[Optional] = + new CommutativeBoth[Optional] { + def both[A, B](fa: => Optional[A], fb: => Optional[B]): Optional[(A, B)] = + fa match { + case Optional.Present(a) => + fb match { + case Optional.Present(b) => Optional.Present((a, b)) + case Optional.Absent => Optional.Absent + } + case Optional.Absent => Optional.Absent } } diff --git a/core/shared/src/main/scala/zio/prelude/Derive.scala b/core/shared/src/main/scala/zio/prelude/Derive.scala index be5c35885..c90db6b2e 100644 --- a/core/shared/src/main/scala/zio/prelude/Derive.scala +++ b/core/shared/src/main/scala/zio/prelude/Derive.scala @@ -16,6 +16,7 @@ package zio.prelude +import zio.prelude.data.Optional import zio.{Cause, Chunk, Exit, NonEmptyChunk} import scala.util.Try @@ -100,6 +101,15 @@ object Derive { Equal.OptionEqual } + /** + * The [[DeriveEqual]] instance for [[zio.prelude.data.Optional]]. + */ + implicit val OptionalDeriveEqual: DeriveEqual[Optional] = + new DeriveEqual[Optional] { + def derive[A: Equal]: Equal[Optional[A]] = + Equal.OptionalEqual + } + /** * The `DeriveEqual` instance for `ParSeq`. */ diff --git a/core/shared/src/main/scala/zio/prelude/Equal.scala b/core/shared/src/main/scala/zio/prelude/Equal.scala index 761935f9d..62a979c02 100644 --- a/core/shared/src/main/scala/zio/prelude/Equal.scala +++ b/core/shared/src/main/scala/zio/prelude/Equal.scala @@ -18,6 +18,7 @@ package zio.prelude import zio.Exit.{Failure, Success} import zio.prelude.coherent.{HashOrd, HashPartialOrd} +import zio.prelude.data.Optional import zio.{Cause, Chunk, Duration => ZIODuration, Exit, FiberId, NonEmptyChunk, StackTrace} import scala.annotation.{implicitNotFound, nowarn} @@ -353,6 +354,16 @@ object Equal extends EqualVersionSpecific { case _ => false } + /** + * Derives an `Equal[Optional[A]]` given an `Equal[A]`. + */ + implicit def OptionalEqual[A: Equal]: Equal[Optional[A]] = + make { + case (Optional.Absent, Optional.Absent) => true + case (Optional.Present(a1), Optional.Present(a2)) => a1 === a2 + case _ => false + } + /** * `PartialOrd` and `Hash` (and thus also `Equal`) instance for `Set[A]` values. * Due to the limitations of Scala's `Set`, diff --git a/core/shared/src/main/scala/zio/prelude/Invariant.scala b/core/shared/src/main/scala/zio/prelude/Invariant.scala index baa8623f7..89efb04f6 100644 --- a/core/shared/src/main/scala/zio/prelude/Invariant.scala +++ b/core/shared/src/main/scala/zio/prelude/Invariant.scala @@ -18,6 +18,7 @@ package zio.prelude import zio._ import zio.prelude.coherent.CovariantIdentityBoth +import zio.prelude.data.Optional import zio.prelude.newtypes.Failure import zio.stm.ZSTM import zio.stream.{ZSink, ZStream} @@ -751,6 +752,15 @@ object Invariant extends LowPriorityInvariantImplicits with InvariantVersionSpec option.fold[G[Option[B]]](Option.empty.succeed)(a => f(a).map(Some(_))) } + /** + * The [[ForEach]] (and thus [[Covariant]] and [[Invariant]]) instance for [[zio.prelude.data.Optional]]. + */ + implicit val OptionalForEach: ForEach[Optional] = + new ForEach[Optional] { + def forEach[G[+_]: IdentityBoth: Covariant, A, B](option: Optional[A])(f: A => G[B]): G[Optional[B]] = + option.fold[G[Optional[B]]](Optional.Absent.succeed)(a => f(a).map(Optional.Present(_))) + } + /** * The `Covariant` (and thus `Invariant`) instance for `Schedule` */ diff --git a/core/shared/src/main/scala/zio/prelude/data/Optional.scala b/core/shared/src/main/scala/zio/prelude/data/Optional.scala index 8e13e5bbc..c9ad9282b 100644 --- a/core/shared/src/main/scala/zio/prelude/data/Optional.scala +++ b/core/shared/src/main/scala/zio/prelude/data/Optional.scala @@ -50,7 +50,7 @@ sealed trait Optional[+A] { self => case Optional.Absent => ifEmpty } - final def flatten[B](implicit ev: A <:< Option[B]): Option[B] = + final def flatten[B](implicit ev: A <:< Optional[B]): Optional[B] = self match { case Optional.Present(get) => ev(get) case Optional.Absent => None @@ -119,11 +119,8 @@ sealed trait Optional[+A] { self => case Optional.Absent => Optional.Absent } - final def orElse[B >: A](other: Optional[B]): Optional[B] = - self match { - case Optional.Present(_) => self - case Optional.Absent => other - } + final def orElse[B >: A](alternative: => Optional[B]): Optional[B] = + if (isEmpty) alternative else this final def iterator: Iterator[A] = self match {