From 60219d407e25c24fb232af3ec2e76a34600580cf Mon Sep 17 00:00:00 2001 From: Julien Truffaut Date: Tue, 22 Dec 2020 17:51:28 +0000 Subject: [PATCH] move Map instance for Each and FilterIndex from unsafe module to core --- .../main/scala/monocle/function/Each.scala | 10 ++++ .../scala/monocle/function/FilterIndex.scala | 16 ++++++ docs/src/main/mdoc/modules.md | 4 +- .../scala/monocle/HttpRequestExample.scala | 1 - .../src/test/scala/monocle/JsonExample.scala | 1 - .../scala/monocle/function/EachSpec.scala | 5 +- .../monocle/function/FilterIndexSpec.scala | 7 ++- .../test/scala/monocle/std/ListMapSpec.scala | 13 +++++ .../src/test/scala/monocle/std/MapSpec.scala | 1 + .../monocle/unsafe/MapTraversalSpec.scala | 53 ------------------- .../scala/monocle/unsafe/MapTraversal.scala | 1 + 11 files changed, 47 insertions(+), 65 deletions(-) create mode 100644 test/shared/src/test/scala/monocle/std/ListMapSpec.scala delete mode 100644 test/shared/src/test/scala/monocle/unsafe/MapTraversalSpec.scala diff --git a/core/shared/src/main/scala/monocle/function/Each.scala b/core/shared/src/main/scala/monocle/function/Each.scala index cfd19ad39..a3ca94b01 100644 --- a/core/shared/src/main/scala/monocle/function/Each.scala +++ b/core/shared/src/main/scala/monocle/function/Each.scala @@ -59,6 +59,16 @@ object Each extends EachFunctions { implicit def lazyListEach[A]: Each[LazyList[A], A] = fromTraverse + implicit def defaultMapEach[K, V]: Each[Map[K, V], V] = + Each( + new Traversal[Map[K, V], V] { + def modifyF[F[_]: Applicative](f: V => F[V])(s: Map[K, V]): F[Map[K, V]] = + s.foldLeft(Applicative[F].pure(Map.empty[K, V])) { case (acc, (k, v)) => + Applicative[F].map2(f(v), acc)((head, tail) => tail + (k -> head)) + } + } + ) + implicit def listMapEach[K, V]: Each[ListMap[K, V], V] = Each( new Traversal[ListMap[K, V], V] { diff --git a/core/shared/src/main/scala/monocle/function/FilterIndex.scala b/core/shared/src/main/scala/monocle/function/FilterIndex.scala index f14b12353..8e70edce5 100644 --- a/core/shared/src/main/scala/monocle/function/FilterIndex.scala +++ b/core/shared/src/main/scala/monocle/function/FilterIndex.scala @@ -59,6 +59,22 @@ object FilterIndex extends FilterIndexFunctions { implicit def lazyListFilterIndex[A]: FilterIndex[LazyList[A], Int, A] = fromTraverse(_.zipWithIndex) + implicit def mapFilterIndex[K, V]: FilterIndex[Map[K, V], K, V] = + new FilterIndex[Map[K, V], K, V] { + import cats.syntax.applicative._ + import cats.syntax.functor._ + + def filterIndex(predicate: K => Boolean) = + new Traversal[Map[K, V], V] { + def modifyF[F[_]: Applicative](f: V => F[V])(s: Map[K, V]): F[Map[K, V]] = + s.toList + .traverse { case (k, v) => + (if (predicate(k)) f(v) else v.pure[F]).tupleLeft(k) + } + .map(_.toMap) + } + } + implicit def sortedMapFilterIndex[K, V](implicit ok: Order[K]): FilterIndex[SortedMap[K, V], K, V] = new FilterIndex[SortedMap[K, V], K, V] { import cats.syntax.applicative._ diff --git a/docs/src/main/mdoc/modules.md b/docs/src/main/mdoc/modules.md index 060a548c5..f4e6ff1fd 100644 --- a/docs/src/main/mdoc/modules.md +++ b/docs/src/main/mdoc/modules.md @@ -9,10 +9,10 @@ In an attempt to be modular, Monocle is broken up into several modules: type class instances for standard library types and cats data types * *macro* - macros to simplify the generation of optics * *laws* - laws for the optics and type classes +* *refined* - optics and type class instances using refinement types from [refined](https://github.com/fthomas/refined) * *generic* (deprecated) - optics and type class instances for `HList` and `Coproduct` from [shapeless](https://github.com/milessabin/shapeless) * *state* (deprecated) - conversion between optics and `State` or `Reader` -* *refined* - optics and type class instances using refinement types from [refined](https://github.com/fthomas/refined) -* *unsafe* - optics that do not fully satisfy laws but that are very convenient. More details [here](unsafe_module.html) +* *unsafe* (deprecated) - optics that do not fully satisfy laws but that are very convenient. More details [here](unsafe_module.html) * *tests* - tests that check optics and type class instances satisfy laws * *bench* - benchmarks using jmh to measure optics performances * *docs* - source for this website diff --git a/example/src/test/scala/monocle/HttpRequestExample.scala b/example/src/test/scala/monocle/HttpRequestExample.scala index 795aaa410..88c9843f0 100644 --- a/example/src/test/scala/monocle/HttpRequestExample.scala +++ b/example/src/test/scala/monocle/HttpRequestExample.scala @@ -1,7 +1,6 @@ package monocle import monocle.macros.{GenIso, GenLens, GenPrism} -import monocle.unsafe.MapTraversal._ /** Show how could we use Monocle to handle custom case classes, objects */ diff --git a/example/src/test/scala/monocle/JsonExample.scala b/example/src/test/scala/monocle/JsonExample.scala index 835642208..9c6e32167 100644 --- a/example/src/test/scala/monocle/JsonExample.scala +++ b/example/src/test/scala/monocle/JsonExample.scala @@ -2,7 +2,6 @@ package monocle import alleycats.std.all._ import monocle.function.Plated -import monocle.unsafe.MapTraversal._ /** Show how could we use Optics to manipulate some Json AST */ diff --git a/test/shared/src/test/scala/monocle/function/EachSpec.scala b/test/shared/src/test/scala/monocle/function/EachSpec.scala index 1d89db73d..32f60f1ff 100644 --- a/test/shared/src/test/scala/monocle/function/EachSpec.scala +++ b/test/shared/src/test/scala/monocle/function/EachSpec.scala @@ -3,14 +3,11 @@ package monocle.function import cats.kernel.Eq import monocle.MonocleSuite import monocle.law.discipline.function.EachTests -import scala.collection.immutable.ListMap class EachSpec extends MonocleSuite { - implicit val eqListMap: Eq[ListMap[String, String]] = Eq.fromUniversalEquals + implicit val eqListMap: Eq[Map[String, String]] = Eq.fromUniversalEquals implicit val slistEach: Each[CList, Char] = Each.fromIso(CList.toList) checkAll("fromIso", EachTests[CList, Char]) - - checkAll("ListMap", EachTests[ListMap[String, String], String]) } diff --git a/test/shared/src/test/scala/monocle/function/FilterIndexSpec.scala b/test/shared/src/test/scala/monocle/function/FilterIndexSpec.scala index a621c3c76..c7c3cdfb9 100644 --- a/test/shared/src/test/scala/monocle/function/FilterIndexSpec.scala +++ b/test/shared/src/test/scala/monocle/function/FilterIndexSpec.scala @@ -1,12 +1,11 @@ package monocle.function -import cats.Order import monocle.MonocleSuite import monocle.law.discipline.function.FilterIndexTests class FilterIndexSpec extends MonocleSuite { - implicit def mmapFilterIndex[K: Order, V]: FilterIndex[MSorteMap[K, V], K, V] = - FilterIndex.fromIso(MSorteMap.toSortedMap) + implicit def mmapFilterIndex[K, V]: FilterIndex[MMap[K, V], K, V] = + FilterIndex.fromIso(MMap.toMap) - checkAll("fromIso", FilterIndexTests[MSorteMap[Int, String], Int, String]) + checkAll("fromIso", FilterIndexTests[MMap[Int, String], Int, String]) } diff --git a/test/shared/src/test/scala/monocle/std/ListMapSpec.scala b/test/shared/src/test/scala/monocle/std/ListMapSpec.scala new file mode 100644 index 000000000..5c728e364 --- /dev/null +++ b/test/shared/src/test/scala/monocle/std/ListMapSpec.scala @@ -0,0 +1,13 @@ +package monocle.std + +import cats.kernel.Eq +import monocle.MonocleSuite +import monocle.law.discipline.function._ + +import scala.collection.immutable.ListMap + +class ListMapSpec extends MonocleSuite { + implicit val eqListMap: Eq[ListMap[Int, String]] = Eq.fromUniversalEquals + + checkAll("each Map", EachTests[ListMap[Int, String], String]) +} diff --git a/test/shared/src/test/scala/monocle/std/MapSpec.scala b/test/shared/src/test/scala/monocle/std/MapSpec.scala index 7e457a6e0..099d4dfa9 100644 --- a/test/shared/src/test/scala/monocle/std/MapSpec.scala +++ b/test/shared/src/test/scala/monocle/std/MapSpec.scala @@ -4,6 +4,7 @@ import monocle.MonocleSuite import monocle.law.discipline.function._ import scala.annotation.nowarn +import scala.collection.immutable.Map class MapSpec extends MonocleSuite { checkAll("at Map", AtTests[Map[Int, String], Int, Option[String]]) diff --git a/test/shared/src/test/scala/monocle/unsafe/MapTraversalSpec.scala b/test/shared/src/test/scala/monocle/unsafe/MapTraversalSpec.scala deleted file mode 100644 index 1ff97b397..000000000 --- a/test/shared/src/test/scala/monocle/unsafe/MapTraversalSpec.scala +++ /dev/null @@ -1,53 +0,0 @@ -package monocle.unsafe - -import monocle.{Iso, MonocleSuite, Traversal} -import monocle.law.{IsoLaws, TraversalLaws} -import org.scalacheck.Arbitrary -import org.scalacheck.Prop._ -import org.typelevel.discipline.Laws -import cats.Eq -import monocle.unsafe.MapTraversal.{allKeyValues, mapKVTraversal} -import monocle.law.discipline._ -import cats.instances.option._ - -object MapListIsoTests extends Laws { - def apply[S: Arbitrary: Eq, A: Arbitrary: Eq](iso: Iso[S, A])(implicit arbAA: Arbitrary[A => A]): RuleSet = { - val laws = new IsoLaws(iso) - new SimpleRuleSet( - "MapListIso", - "round trip one way" -> forAll((s: S) => laws.roundTripOneWay(s)), - // "round trip other way does not work because of key collision and unorderness" - "modify id = id" -> forAll((s: S) => laws.modifyIdentity(s)), - // "compose modify does not work because of key collision and unorderness" - "consistent replace with modify" -> forAll((s: S, a: A) => laws.consistentReplaceModify(s, a)), - "consistent modify with modifyId" -> forAll((s: S, f: A => A) => laws.consistentModifyModifyId(s, f)), - "consistent get with modifyId" -> forAll((s: S) => laws.consistentGetModifyId(s)) - ) - } -} - -object MapKVTraversalTests extends Laws { - def apply[S: Arbitrary: Eq, A: Arbitrary: Eq]( - traversal: Traversal[S, A] - )(implicit arbAA: Arbitrary[A => A]): RuleSet = - apply[S, A, Unit](_ => traversal) - - def apply[S: Arbitrary: Eq, A: Arbitrary: Eq, I: Arbitrary]( - f: I => Traversal[S, A] - )(implicit arbAA: Arbitrary[A => A]): RuleSet = { - def laws(i: I): TraversalLaws[S, A] = new TraversalLaws(f(i)) - new SimpleRuleSet( - "MapKVTraversal", - "headOption" -> forAll((s: S, i: I) => laws(i).headOption(s)), - // "get what you replace does not work because of key collision unorderness" - "replace idempotent" -> forAll((s: S, a: A, i: I) => laws(i).replaceIdempotent(s, a)), - "modify id = id" -> forAll((s: S, i: I) => laws(i).modifyIdentity(s)) - // "compose modify does not work because of key collision and unorderness" - ) - } -} - -class MapTraversalSpec extends MonocleSuite { - checkAll("map list Iso", MapListIsoTests(allKeyValues[String, Int])) - checkAll("map KV traversal", MapKVTraversalTests(mapKVTraversal[String, Int])) -} diff --git a/unsafe/src/main/scala/monocle/unsafe/MapTraversal.scala b/unsafe/src/main/scala/monocle/unsafe/MapTraversal.scala index 11878e19f..a1bbef198 100644 --- a/unsafe/src/main/scala/monocle/unsafe/MapTraversal.scala +++ b/unsafe/src/main/scala/monocle/unsafe/MapTraversal.scala @@ -12,6 +12,7 @@ import monocle.function.{Each, FilterIndex} import scala.collection.immutable.Map +@deprecated("use optic.filter(predicate)", since = "3.0.0-M1") object MapTraversal { implicit def mapEach[K, V]: Each[Map[K, V], V] = fromTraverse[Map[K, *], V]