diff --git a/core/shared/src/main/scala/monocle/Fold.scala b/core/shared/src/main/scala/monocle/Fold.scala index ae0f96996..5e6bde40c 100644 --- a/core/shared/src/main/scala/monocle/Fold.scala +++ b/core/shared/src/main/scala/monocle/Fold.scala @@ -91,6 +91,12 @@ abstract class Fold[S, A] extends Serializable { self => def each[C](implicit evEach: Each[A, C]): Fold[S, C] = composeTraversal(evEach.each) + /** Select all the elements which satisfies the predicate. + * This combinator can break the fusion property see Optional.filter for more details. + */ + def filter(predicate: A => Boolean): Fold[S, A] = + self.andThen(Optional.filter(predicate)) + def some[A1](implicit ev1: A =:= Option[A1]): Fold[S, A1] = adapt[Option[A1]] composePrism (std.option.pSome) diff --git a/core/shared/src/main/scala/monocle/Getter.scala b/core/shared/src/main/scala/monocle/Getter.scala index d7a8186fb..42de598c6 100644 --- a/core/shared/src/main/scala/monocle/Getter.scala +++ b/core/shared/src/main/scala/monocle/Getter.scala @@ -56,6 +56,12 @@ abstract class Getter[S, A] extends Serializable { self => def each[C](implicit evEach: Each[A, C]): Fold[S, C] = composeTraversal(evEach.each) + /** Select all the elements which satisfies the predicate. + * This combinator can break the fusion property see Optional.filter for more details. + */ + def filter(predicate: A => Boolean): Fold[S, A] = + self.andThen(Optional.filter(predicate)) + def some[A1](implicit ev1: A =:= Option[A1]): Fold[S, A1] = adapt[Option[A1]] composePrism (std.option.pSome) diff --git a/core/shared/src/main/scala/monocle/Iso.scala b/core/shared/src/main/scala/monocle/Iso.scala index 573589028..025dda8da 100644 --- a/core/shared/src/main/scala/monocle/Iso.scala +++ b/core/shared/src/main/scala/monocle/Iso.scala @@ -411,6 +411,12 @@ final case class IsoSyntax[S, A](private val self: Iso[S, A]) extends AnyVal { def each[C](implicit evEach: Each[A, C]): Traversal[S, C] = self composeTraversal evEach.each + /** Select all the elements which satisfies the predicate. + * This combinator can break the fusion property see Optional.filter for more details. + */ + def filter(predicate: A => Boolean): Optional[S, A] = + self.andThen(Optional.filter(predicate)) + def withDefault[A1: Eq](defaultValue: A1)(implicit evOpt: A =:= Option[A1]): Iso[S, A1] = self.adapt[Option[A1], Option[A1]] composeIso (std.option.withDefault(defaultValue)) diff --git a/core/shared/src/main/scala/monocle/Lens.scala b/core/shared/src/main/scala/monocle/Lens.scala index a91d8ec29..c37326e65 100644 --- a/core/shared/src/main/scala/monocle/Lens.scala +++ b/core/shared/src/main/scala/monocle/Lens.scala @@ -322,6 +322,12 @@ final case class LensSyntax[S, A](private val self: Lens[S, A]) extends AnyVal { def each[C](implicit evEach: Each[A, C]): Traversal[S, C] = self composeTraversal evEach.each + /** Select all the elements which satisfies the predicate. + * This combinator can break the fusion property see Optional.filter for more details. + */ + def filter(predicate: A => Boolean): Optional[S, A] = + self.andThen(Optional.filter(predicate)) + def withDefault[A1: Eq](defaultValue: A1)(implicit evOpt: A =:= Option[A1]): Lens[S, A1] = self.adapt[Option[A1], Option[A1]] composeIso (std.option.withDefault(defaultValue)) diff --git a/core/shared/src/main/scala/monocle/Optional.scala b/core/shared/src/main/scala/monocle/Optional.scala index ffaf7b4fa..e4ca7f9c0 100644 --- a/core/shared/src/main/scala/monocle/Optional.scala +++ b/core/shared/src/main/scala/monocle/Optional.scala @@ -299,6 +299,33 @@ object Optional { def void[S, A]: Optional[S, A] = Optional[S, A](_ => None)(_ => identity) + /** Select all the elements which satisfies the predicate. + * {{{ + * val positiveNumbers = Traversal.fromTraverse[List, Int] composeOptional filter[Int](_ >= 0) + * + * positiveNumbers.getAll(List(1,2,-3,4,-5)) == List(1,2,4) + * positiveNumbers.modify(_ * 10)(List(1,2,-3,4,-5)) == List(10,20,-3,40,-5) + * }}} + * + * `filter` can break the fusion property, if `replace` or `modify` do not preserve the predicate. + * For example, here the first `modify` (`x - 3`) transform the positive number 1 into the + * negative number -2. + * {{{ + * val positiveNumbers = Traversal.fromTraverse[List, Int] composeOptional Optional.filter[Int](_ >= 0) + * val list = List(1, 5, -3) + * val firstStep = positiveNumbers.modify(_ - 3)(list) // List(-2, 2, -3) + * val secondStep = positiveNumbers.modify(_ * 2)(firstStep) // List(-2, 4, -3) + * val bothSteps = positiveNumbers.modify(x => (x - 3) * 2)(list) // List(-4, 4, -3) + * secondStep != bothSteps + * }}} + * + * @see This method is called `filtered` in Haskell Lens. + */ + def filter[A](predicate: A => Boolean): Optional[A, A] = + Optional[A, A](value => if (predicate(value)) Some(value) else None)(newValue => + current => if (predicate(current)) newValue else current + ) + /** alias for [[POptional]] apply restricted to monomorphic update */ def apply[S, A](_getOption: S => Option[A])(_set: A => S => S): Optional[S, A] = new Optional[S, A] { @@ -338,6 +365,12 @@ final case class OptionalSyntax[S, A](private val self: Optional[S, A]) extends def each[C](implicit evEach: Each[A, C]): Traversal[S, C] = self composeTraversal evEach.each + /** Select all the elements which satisfies the predicate. + * This combinator can break the fusion property see Optional.filter for more details. + */ + def filter(predicate: A => Boolean): Optional[S, A] = + self.andThen(Optional.filter(predicate)) + def withDefault[A1: Eq](defaultValue: A1)(implicit evOpt: A =:= Option[A1]): Optional[S, A1] = self.adapt[Option[A1], Option[A1]] composeIso (std.option.withDefault(defaultValue)) diff --git a/core/shared/src/main/scala/monocle/Prism.scala b/core/shared/src/main/scala/monocle/Prism.scala index e0f401226..4c02b5b88 100644 --- a/core/shared/src/main/scala/monocle/Prism.scala +++ b/core/shared/src/main/scala/monocle/Prism.scala @@ -377,6 +377,12 @@ final case class PrismSyntax[S, A](private val self: Prism[S, A]) extends AnyVal def each[C](implicit evEach: Each[A, C]): Traversal[S, C] = self composeTraversal evEach.each + /** Select all the elements which satisfies the predicate. + * This combinator can break the fusion property see Optional.filter for more details. + */ + def filter(predicate: A => Boolean): Optional[S, A] = + self.andThen(Optional.filter(predicate)) + def withDefault[A1: Eq](defaultValue: A1)(implicit evOpt: A =:= Option[A1]): Prism[S, A1] = self.adapt[Option[A1], Option[A1]] composeIso (std.option.withDefault(defaultValue)) diff --git a/core/shared/src/main/scala/monocle/Setter.scala b/core/shared/src/main/scala/monocle/Setter.scala index 75f97e13d..665939ac3 100644 --- a/core/shared/src/main/scala/monocle/Setter.scala +++ b/core/shared/src/main/scala/monocle/Setter.scala @@ -201,6 +201,12 @@ final case class SetterSyntax[S, A](private val self: Setter[S, A]) extends AnyV def each[C](implicit evEach: Each[A, C]): Setter[S, C] = self composeTraversal evEach.each + /** Select all the elements which satisfies the predicate. + * This combinator can break the fusion property see Optional.filter for more details. + */ + def filter(predicate: A => Boolean): Setter[S, A] = + self.andThen(Optional.filter(predicate)) + def withDefault[A1: Eq](defaultValue: A1)(implicit evOpt: A =:= Option[A1]): Setter[S, A1] = self.adapt[Option[A1], Option[A1]] composeIso (std.option.withDefault(defaultValue)) diff --git a/core/shared/src/main/scala/monocle/Traversal.scala b/core/shared/src/main/scala/monocle/Traversal.scala index d0dff4b68..1ed419776 100644 --- a/core/shared/src/main/scala/monocle/Traversal.scala +++ b/core/shared/src/main/scala/monocle/Traversal.scala @@ -348,6 +348,12 @@ final case class TraversalSyntax[S, A](private val self: Traversal[S, A]) extend def each[C](implicit evEach: Each[A, C]): Traversal[S, C] = self composeTraversal evEach.each + /** Select all the elements which satisfies the predicate. + * This combinator can break the fusion property see Optional.filter for more details. + */ + def filter(predicate: A => Boolean): Traversal[S, A] = + self.andThen(Optional.filter(predicate)) + def withDefault[A1: Eq](defaultValue: A1)(implicit evOpt: A =:= Option[A1]): Traversal[S, A1] = self.adapt[Option[A1], Option[A1]] composeIso (std.option.withDefault(defaultValue)) diff --git a/core/shared/src/main/scala/monocle/syntax/ApplyFold.scala b/core/shared/src/main/scala/monocle/syntax/ApplyFold.scala index d6d95a972..08043e73c 100644 --- a/core/shared/src/main/scala/monocle/syntax/ApplyFold.scala +++ b/core/shared/src/main/scala/monocle/syntax/ApplyFold.scala @@ -2,7 +2,7 @@ package monocle.syntax import cats.{Eq, Monoid} import monocle.function.{At, Each, Index} -import monocle.{std, Fold, Getter, PIso, PLens, POptional, PPrism, PTraversal} +import monocle.{std, Fold, Getter, Optional, PIso, PLens, POptional, PPrism, PTraversal} case class ApplyFold[S, A](s: S, _fold: Fold[S, A]) { def foldMap[M: Monoid](f: A => M): M = _fold.foldMap(f)(s) @@ -20,6 +20,12 @@ case class ApplyFold[S, A](s: S, _fold: Fold[S, A]) { def each[C](implicit evEach: Each[A, C]): ApplyFold[S, C] = composeTraversal(evEach.each) + /** Select all the elements which satisfies the predicate. + * This combinator can break the fusion property see Optional.filter for more details. + */ + def filter(predicate: A => Boolean): ApplyFold[S, A] = + andThen(Optional.filter(predicate)) + def some[A1](implicit ev1: A =:= Option[A1]): ApplyFold[S, A1] = adapt[Option[A1]] composePrism (std.option.pSome) diff --git a/core/shared/src/main/scala/monocle/syntax/ApplyGetter.scala b/core/shared/src/main/scala/monocle/syntax/ApplyGetter.scala index b1d5a56ca..298456d3b 100644 --- a/core/shared/src/main/scala/monocle/syntax/ApplyGetter.scala +++ b/core/shared/src/main/scala/monocle/syntax/ApplyGetter.scala @@ -2,7 +2,7 @@ package monocle.syntax import cats.Eq import monocle.function.{At, Each, Index} -import monocle.{std, Fold, Getter, PIso, PLens, POptional, PPrism, PTraversal} +import monocle.{std, Fold, Getter, Optional, PIso, PLens, POptional, PPrism, PTraversal} final case class ApplyGetter[S, A](s: S, getter: Getter[S, A]) { def get: A = getter.get(s) @@ -12,6 +12,12 @@ final case class ApplyGetter[S, A](s: S, getter: Getter[S, A]) { def each[C](implicit evEach: Each[A, C]): ApplyFold[S, C] = composeTraversal(evEach.each) + /** Select all the elements which satisfies the predicate. + * This combinator can break the fusion property see Optional.filter for more details. + */ + def filter(predicate: A => Boolean): ApplyFold[S, A] = + andThen(Optional.filter(predicate)) + def some[A1](implicit ev1: A =:= Option[A1]): ApplyFold[S, A1] = adapt[Option[A1]] composePrism (std.option.pSome) diff --git a/core/shared/src/main/scala/monocle/syntax/ApplyIso.scala b/core/shared/src/main/scala/monocle/syntax/ApplyIso.scala index 8bc4fcada..9af5273a8 100644 --- a/core/shared/src/main/scala/monocle/syntax/ApplyIso.scala +++ b/core/shared/src/main/scala/monocle/syntax/ApplyIso.scala @@ -2,7 +2,7 @@ package monocle.syntax import cats.{Eq, Functor} import monocle.function.{At, Each, Index} -import monocle.{std, Fold, Getter, PIso, PLens, POptional, PPrism, PSetter, PTraversal} +import monocle.{std, Fold, Getter, Optional, PIso, PLens, POptional, PPrism, PSetter, PTraversal} final case class ApplyIso[S, T, A, B](s: S, iso: PIso[S, T, A, B]) { def get: A = iso.get(s) @@ -78,6 +78,12 @@ final case class ApplyIsoSyntax[S, A](private val self: ApplyIso[S, S, A, A]) ex def each[C](implicit evEach: Each[A, C]): ApplyTraversal[S, S, C, C] = self composeTraversal evEach.each + /** Select all the elements which satisfies the predicate. + * This combinator can break the fusion property see Optional.filter for more details. + */ + def filter(predicate: A => Boolean): ApplyOptional[S, S, A, A] = + self.andThen(Optional.filter(predicate)) + def withDefault[A1: Eq](defaultValue: A1)(implicit evOpt: A =:= Option[A1]): ApplyIso[S, S, A1, A1] = self.adapt[Option[A1], Option[A1]] composeIso (std.option.withDefault(defaultValue)) diff --git a/core/shared/src/main/scala/monocle/syntax/ApplyLens.scala b/core/shared/src/main/scala/monocle/syntax/ApplyLens.scala index 455e35bc0..d575d57dd 100644 --- a/core/shared/src/main/scala/monocle/syntax/ApplyLens.scala +++ b/core/shared/src/main/scala/monocle/syntax/ApplyLens.scala @@ -2,7 +2,7 @@ package monocle.syntax import cats.{Eq, Functor} import monocle.function.{At, Each, Index} -import monocle.{std, Fold, Getter, PIso, PLens, POptional, PPrism, PSetter, PTraversal} +import monocle.{std, Fold, Getter, Optional, PIso, PLens, POptional, PPrism, PSetter, PTraversal} final case class ApplyLens[S, T, A, B](s: S, lens: PLens[S, T, A, B]) { def get: A = lens.get(s) @@ -79,6 +79,12 @@ final case class ApplyLensSyntax[S, A](private val self: ApplyLens[S, S, A, A]) def each[C](implicit evEach: Each[A, C]): ApplyTraversal[S, S, C, C] = self composeTraversal evEach.each + /** Select all the elements which satisfies the predicate. + * This combinator can break the fusion property see Optional.filter for more details. + */ + def filter(predicate: A => Boolean): ApplyOptional[S, S, A, A] = + self.andThen(Optional.filter(predicate)) + def withDefault[A1: Eq](defaultValue: A1)(implicit evOpt: A =:= Option[A1]): ApplyLens[S, S, A1, A1] = self.adapt[Option[A1], Option[A1]] composeIso (std.option.withDefault(defaultValue)) diff --git a/core/shared/src/main/scala/monocle/syntax/ApplyOptional.scala b/core/shared/src/main/scala/monocle/syntax/ApplyOptional.scala index ca5ca6aba..a51b1f184 100644 --- a/core/shared/src/main/scala/monocle/syntax/ApplyOptional.scala +++ b/core/shared/src/main/scala/monocle/syntax/ApplyOptional.scala @@ -2,7 +2,7 @@ package monocle.syntax import cats.{Applicative, Eq} import monocle.function.{At, Each, Index} -import monocle.{std, Fold, PIso, PLens, POptional, PPrism, PSetter, PTraversal} +import monocle.{std, Fold, Optional, PIso, PLens, POptional, PPrism, PSetter, PTraversal} final case class ApplyOptional[S, T, A, B](s: S, optional: POptional[S, T, A, B]) { def getOption: Option[A] = optional.getOption(s) @@ -86,6 +86,12 @@ final case class ApplyOptionalSyntax[S, A](private val self: ApplyOptional[S, S, def each[C](implicit evEach: Each[A, C]): ApplyTraversal[S, S, C, C] = self composeTraversal evEach.each + /** Select all the elements which satisfies the predicate. + * This combinator can break the fusion property see Optional.filter for more details. + */ + def filter(predicate: A => Boolean): ApplyOptional[S, S, A, A] = + self.andThen(Optional.filter(predicate)) + def withDefault[A1: Eq](defaultValue: A1)(implicit evOpt: A =:= Option[A1]): ApplyOptional[S, S, A1, A1] = self.adapt[Option[A1], Option[A1]] composeIso (std.option.withDefault(defaultValue)) diff --git a/core/shared/src/main/scala/monocle/syntax/ApplyPrism.scala b/core/shared/src/main/scala/monocle/syntax/ApplyPrism.scala index fa94001f7..37aa0e951 100644 --- a/core/shared/src/main/scala/monocle/syntax/ApplyPrism.scala +++ b/core/shared/src/main/scala/monocle/syntax/ApplyPrism.scala @@ -2,7 +2,7 @@ package monocle.syntax import cats.{Applicative, Eq} import monocle.function.{At, Each, Index} -import monocle.{std, Fold, PIso, PLens, POptional, PPrism, PSetter, PTraversal} +import monocle.{std, Fold, Optional, PIso, PLens, POptional, PPrism, PSetter, PTraversal} final case class ApplyPrism[S, T, A, B](s: S, prism: PPrism[S, T, A, B]) { def getOption: Option[A] = prism.getOption(s) @@ -84,6 +84,12 @@ final case class ApplyPrismSyntax[S, A](private val self: ApplyPrism[S, S, A, A] def each[C](implicit evEach: Each[A, C]): ApplyTraversal[S, S, C, C] = self composeTraversal evEach.each + /** Select all the elements which satisfies the predicate. + * This combinator can break the fusion property see Optional.filter for more details. + */ + def filter(predicate: A => Boolean): ApplyOptional[S, S, A, A] = + self.andThen(Optional.filter(predicate)) + def withDefault[A1: Eq](defaultValue: A1)(implicit evOpt: A =:= Option[A1]): ApplyPrism[S, S, A1, A1] = self.adapt[Option[A1], Option[A1]] composeIso (std.option.withDefault(defaultValue)) diff --git a/core/shared/src/main/scala/monocle/syntax/ApplySetter.scala b/core/shared/src/main/scala/monocle/syntax/ApplySetter.scala index 3c7c4d3dc..6cc92e60d 100644 --- a/core/shared/src/main/scala/monocle/syntax/ApplySetter.scala +++ b/core/shared/src/main/scala/monocle/syntax/ApplySetter.scala @@ -2,7 +2,7 @@ package monocle.syntax import cats.Eq import monocle.function.{At, Each, Index} -import monocle.{std, PIso, PLens, POptional, PPrism, PSetter, PTraversal} +import monocle.{std, Optional, PIso, PLens, POptional, PPrism, PSetter, PTraversal} final case class ApplySetter[S, T, A, B](s: S, setter: PSetter[S, T, A, B]) { def replace(b: B): T = setter.replace(b)(s) @@ -69,6 +69,12 @@ final case class ApplySetterSyntax[S, A](private val self: ApplySetter[S, S, A, def each[C](implicit evEach: Each[A, C]): ApplySetter[S, S, C, C] = self composeTraversal evEach.each + /** Select all the elements which satisfies the predicate. + * This combinator can break the fusion property see Optional.filter for more details. + */ + def filter(predicate: A => Boolean): ApplySetter[S, S, A, A] = + self.andThen(Optional.filter(predicate)) + def withDefault[A1: Eq](defaultValue: A1)(implicit evOpt: A =:= Option[A1]): ApplySetter[S, S, A1, A1] = self.adapt[Option[A1], Option[A1]] composeIso (std.option.withDefault(defaultValue)) diff --git a/core/shared/src/main/scala/monocle/syntax/ApplyTraversal.scala b/core/shared/src/main/scala/monocle/syntax/ApplyTraversal.scala index 290ec9a31..cb5f39b21 100644 --- a/core/shared/src/main/scala/monocle/syntax/ApplyTraversal.scala +++ b/core/shared/src/main/scala/monocle/syntax/ApplyTraversal.scala @@ -2,7 +2,7 @@ package monocle.syntax import cats.{Applicative, Eq} import monocle.function.{At, Each, Index} -import monocle.{std, Fold, PIso, PLens, POptional, PPrism, PSetter, PTraversal} +import monocle.{std, Fold, Optional, PIso, PLens, POptional, PPrism, PSetter, PTraversal} final case class ApplyTraversal[S, T, A, B](s: S, traversal: PTraversal[S, T, A, B]) { def getAll: List[A] = traversal.getAll(s) @@ -84,6 +84,12 @@ final case class ApplyTraversalSyntax[S, A](private val self: ApplyTraversal[S, def each[C](implicit evEach: Each[A, C]): ApplyTraversal[S, S, C, C] = self composeTraversal evEach.each + /** Select all the elements which satisfies the predicate. + * This combinator can break the fusion property see Optional.filter for more details. + */ + def filter(predicate: A => Boolean): ApplyTraversal[S, S, A, A] = + self.andThen(Optional.filter(predicate)) + def withDefault[A1: Eq](defaultValue: A1)(implicit evOpt: A =:= Option[A1]): ApplyTraversal[S, S, A1, A1] = self.adapt[Option[A1], Option[A1]] composeIso (std.option.withDefault(defaultValue)) diff --git a/test/shared/src/test/scala/monocle/FoldSpec.scala b/test/shared/src/test/scala/monocle/FoldSpec.scala index 157e20448..c477ec976 100644 --- a/test/shared/src/test/scala/monocle/FoldSpec.scala +++ b/test/shared/src/test/scala/monocle/FoldSpec.scala @@ -126,6 +126,14 @@ class FoldSpec extends MonocleSuite { assertEquals(numbers.applyFold(fold).each.getAll, List(1, 2, 3, 4)) } + test("filter") { + val numbers = List(1, 2, 3) + val fold = Fold.fromFoldable[List, Int] + + assertEquals(fold.filter(_ > 1).getAll(numbers), List(2, 3)) + assertEquals(numbers.applyFold(fold).filter(_ > 1).getAll, List(2, 3)) + } + test("at") { val tuple2 = (1, 2) val tuple2Fold = Fold.id[(Int, Int)] diff --git a/test/shared/src/test/scala/monocle/GetterSpec.scala b/test/shared/src/test/scala/monocle/GetterSpec.scala index 1256d194b..c882d9671 100644 --- a/test/shared/src/test/scala/monocle/GetterSpec.scala +++ b/test/shared/src/test/scala/monocle/GetterSpec.scala @@ -109,6 +109,16 @@ class GetterSpec extends MonocleSuite { assertEquals(obj.applyGetter(getter).each.getAll, List(1, 2, 3)) } + test("filter") { + case class SomeTest(x: Int, y: Int) + val obj = SomeTest(1, 2) + + val getter = Getter[SomeTest, Int](_.y) + + assertEquals(getter.filter(_ > 0).getAll(obj), List(2)) + assertEquals(obj.applyGetter(getter).filter(_ > 0).getAll, List(2)) + } + test("at") { val tuple2 = (1, 2) val tuple2Getter = Getter.id[(Int, Int)] diff --git a/test/shared/src/test/scala/monocle/IsoSpec.scala b/test/shared/src/test/scala/monocle/IsoSpec.scala index 74a8d0e18..585927444 100644 --- a/test/shared/src/test/scala/monocle/IsoSpec.scala +++ b/test/shared/src/test/scala/monocle/IsoSpec.scala @@ -201,6 +201,16 @@ assertEquals( (Nullary() match { case _nullary(unit) => unit }) , (())) assertEquals(obj.applyIso(iso).each.getAll, List(1, 2, 3)) } + test("each") { + case class SomeTest(y: Int) + val obj = SomeTest(2) + + val iso = Iso[SomeTest, Int](_.y)(SomeTest) + + assertEquals(iso.filter(_ > 0).getOption(obj), Some(2)) + assertEquals(obj.applyIso(iso).filter(_ > 0).getOption, Some(2)) + } + test("at") { val tuple2 = (1, 2) val tuple2Lens = Iso.id[(Int, Int)] diff --git a/test/shared/src/test/scala/monocle/LensSpec.scala b/test/shared/src/test/scala/monocle/LensSpec.scala index b23ba7d90..27cd525e4 100644 --- a/test/shared/src/test/scala/monocle/LensSpec.scala +++ b/test/shared/src/test/scala/monocle/LensSpec.scala @@ -130,6 +130,16 @@ class LensSpec extends MonocleSuite { assertEquals(obj.applyLens(lens).each.getAll, List(1, 2, 3)) } + test("filter") { + case class SomeTest(x: Int, y: Int) + val obj = SomeTest(1, 2) + + val lens = GenLens[SomeTest](_.y) + + assertEquals(lens.filter(_ > 0).getOption(obj), Some(2)) + assertEquals(obj.applyLens(lens).filter(_ > 0).getOption, Some(2)) + } + test("at") { val tuple2 = (1, 2) val tuple2Lens = Lens.id[(Int, Int)] diff --git a/test/shared/src/test/scala/monocle/OptionalSpec.scala b/test/shared/src/test/scala/monocle/OptionalSpec.scala index 8676c1193..a3609a5e8 100644 --- a/test/shared/src/test/scala/monocle/OptionalSpec.scala +++ b/test/shared/src/test/scala/monocle/OptionalSpec.scala @@ -301,4 +301,23 @@ class OptionalSpec extends MonocleSuite { assertEquals(nel.applyOptional(nelOptional).index(0).getOption, Some(1)) assertEquals(nel.applyOptional(nelOptional).index(1).getOption, None) } + + test("filter") { + val positiveNumbers = Traversal.fromTraverse[List, Int] composeOptional Optional.filter[Int](_ >= 0) + + assertEquals(positiveNumbers.getAll(List(1, 2, -3, 4, -5)), List(1, 2, 4)) + assertEquals(positiveNumbers.modify(_ * 10)(List(1, 2, -3, 4, -5)), List(10, 20, -3, 40, -5)) + } + + test("filter can break the fusion property") { + val positiveNumbers = Traversal.fromTraverse[List, Int] composeOptional Optional.filter[Int](_ >= 0) + val list = List(1, 5, -3) + val firstStep = positiveNumbers.modify(_ - 3)(list) + val secondStep = positiveNumbers.modify(_ * 2)(firstStep) + val bothSteps = positiveNumbers.modify(x => (x - 3) * 2)(list) + assertEquals(firstStep, List(-2, 2, -3)) + assertEquals(secondStep, List(-2, 4, -3)) + assertEquals(bothSteps, List(-4, 4, -3)) + assertNotEquals(secondStep, bothSteps) + } } diff --git a/test/shared/src/test/scala/monocle/PrismSpec.scala b/test/shared/src/test/scala/monocle/PrismSpec.scala index 447f98395..7905a3655 100644 --- a/test/shared/src/test/scala/monocle/PrismSpec.scala +++ b/test/shared/src/test/scala/monocle/PrismSpec.scala @@ -218,6 +218,16 @@ assertEquals( ((Nullary(): Arities) match { case _nullary(unit) => unit }) , assertEquals(obj.applyPrism(prism).each.getAll, List(1, 2, 3)) } + test("filter") { + case class SomeTest(y: Int) + val obj = SomeTest(2) + + val prism = Iso[SomeTest, Int](_.y)(SomeTest).asPrism + + assertEquals(prism.filter(_ > 0).getOption(obj), Some(2)) + assertEquals(obj.applyPrism(prism).filter(_ > 0).getOption, Some(2)) + } + test("at") { val tuple2 = (1, 2) val tuple2Prism = Prism.id[(Int, Int)] diff --git a/test/shared/src/test/scala/monocle/SetterSpec.scala b/test/shared/src/test/scala/monocle/SetterSpec.scala index feac945cb..4d12e5d37 100644 --- a/test/shared/src/test/scala/monocle/SetterSpec.scala +++ b/test/shared/src/test/scala/monocle/SetterSpec.scala @@ -79,6 +79,16 @@ class SetterSpec extends MonocleSuite { assertEquals(obj.applySetter(setter).each.replace(3), SomeTest(1, List(3, 3, 3))) } + test("each") { + case class SomeTest(x: Int, y: Int) + val obj = SomeTest(1, 2) + + val setter = GenLens[SomeTest](_.y).asSetter + + assertEquals(setter.filter(_ > 0).replace(3)(obj), SomeTest(1, 3)) + assertEquals(obj.applySetter(setter).filter(_ > 0).replace(3), SomeTest(1, 3)) + } + test("at") { val tuple2 = (1, 2) val tuple2Setter = Setter.id[(Int, Int)] diff --git a/test/shared/src/test/scala/monocle/TraversalSpec.scala b/test/shared/src/test/scala/monocle/TraversalSpec.scala index 950b6367c..1102cc1fb 100644 --- a/test/shared/src/test/scala/monocle/TraversalSpec.scala +++ b/test/shared/src/test/scala/monocle/TraversalSpec.scala @@ -189,6 +189,14 @@ class TraversalSpec extends MonocleSuite { assertEquals(numbers.applyTraversal(traversal).each.getAll, List(1, 2, 3, 4)) } + test("filter") { + val numbers = List(1, 2, 3) + val traversal = Traversal.fromTraverse[List, Int] + + assertEquals(traversal.filter(_ > 1).getAll(numbers), List(2, 3)) + assertEquals(numbers.applyTraversal(traversal).filter(_ > 1).getAll, List(2, 3)) + } + test("at") { val tuple2 = (1, 2) val tuple2Traversal = Traversal.id[(Int, Int)] diff --git a/test/shared/src/test/scala/monocle/unsafe/UnsafeSelectSpec.scala b/test/shared/src/test/scala/monocle/unsafe/UnsafeSelectSpec.scala index 9447cb8e6..10cc7bb6e 100644 --- a/test/shared/src/test/scala/monocle/unsafe/UnsafeSelectSpec.scala +++ b/test/shared/src/test/scala/monocle/unsafe/UnsafeSelectSpec.scala @@ -4,9 +4,11 @@ import monocle.MonocleSuite import monocle.law.discipline.OptionalTests import monocle.macros.GenLens import org.scalacheck.Arbitrary - import cats.Eq +import scala.annotation.nowarn + +@nowarn class UnsafeSelectSpec extends MonocleSuite { /* This fails the "unsafe.Prism.round trip other way" test with value -1 diff --git a/unsafe/src/main/scala/monocle/unsafe/UnsafeSelect.scala b/unsafe/src/main/scala/monocle/unsafe/UnsafeSelect.scala index 4716a0d5b..755c279e3 100644 --- a/unsafe/src/main/scala/monocle/unsafe/UnsafeSelect.scala +++ b/unsafe/src/main/scala/monocle/unsafe/UnsafeSelect.scala @@ -3,6 +3,7 @@ package monocle.unsafe import monocle.Prism object UnsafeSelect { + @deprecated("use optic.filter(predicate)", since = "3.0.0-M1") def unsafeSelect[A](predicate: A => Boolean): Prism[A, A] = Prism[A, A](a => Some(a).filter(predicate))(a => a) }