From c84d47110a99e0a4cd3df4b3f01612290f642f13 Mon Sep 17 00:00:00 2001 From: "yu.badalyants" Date: Fri, 3 Dec 2021 11:16:59 +0700 Subject: [PATCH 1/2] Relax type constraint for QuicklensMapAt --- .../com.softwaremill.quicklens/package.scala | 31 ++++++++++++------- .../com.softwaremill.quicklens/package.scala | 31 ++++++++++++------- 2 files changed, 38 insertions(+), 24 deletions(-) diff --git a/quicklens/src/main/scala-2.13+/com.softwaremill.quicklens/package.scala b/quicklens/src/main/scala-2.13+/com.softwaremill.quicklens/package.scala index 9675d9d..5db3d5b 100644 --- a/quicklens/src/main/scala-2.13+/com.softwaremill.quicklens/package.scala +++ b/quicklens/src/main/scala-2.13+/com.softwaremill.quicklens/package.scala @@ -1,14 +1,16 @@ package com.softwaremill +import com.softwaremill.quicklens.{QuicklensMapAtFunctor, canOnlyBeUsedInsideModify} + import scala.annotation.compileTimeOnly import scala.collection.Factory import scala.collection.SeqLike import scala.language.experimental.macros import scala.language.higherKinds -package object quicklens { +package object quicklens extends LowPriorityImplicits { - private def canOnlyBeUsedInsideModify(method: String) = + private[softwaremill] def canOnlyBeUsedInsideModify(method: String) = s"$method can only be used inside modify" /** @@ -177,14 +179,6 @@ package object quicklens { PathLazyModify[T, V]((t, vv) => self.doModify(t, u => f2.doModify(u, vv))) } - implicit class QuicklensEach[F[_], T](t: F[T])(implicit f: QuicklensFunctor[F, T]) { - @compileTimeOnly(canOnlyBeUsedInsideModify("each")) - def each: T = sys.error("") - - @compileTimeOnly(canOnlyBeUsedInsideModify("eachWhere")) - def eachWhere(p: T => Boolean): T = sys.error("") - } - trait QuicklensFunctor[F[_], A] { def map(fa: F[A])(f: A => A): F[A] def each(fa: F[A])(f: A => A): F[A] = map(fa)(f) @@ -239,8 +233,8 @@ package object quicklens { def index(fa: F[T], idx: Int)(f: T => T): F[T] } - implicit class QuicklensMapAt[M[KT, TT] <: Map[KT, TT], K, T](t: M[K, T])( - implicit f: QuicklensMapAtFunctor[M, K, T] + implicit class QuicklensMapAt[M[KT, TT], K, T](t: M[K, T])( + implicit f: QuicklensMapAtFunctor[M, K, T] ) { @compileTimeOnly(canOnlyBeUsedInsideModify("at")) def at(idx: K): T = sys.error("") @@ -311,3 +305,16 @@ package object quicklens { override def eachRight(e: Either[L, R])(f: (R) => R) = e.map(f) } } + +sealed trait LowPriorityImplicits { + + import quicklens._ + + implicit class QuicklensEach[F[_], T](t: F[T])(implicit f: QuicklensFunctor[F, T]) { + @compileTimeOnly(canOnlyBeUsedInsideModify("each")) + def each: T = sys.error("") + + @compileTimeOnly(canOnlyBeUsedInsideModify("eachWhere")) + def eachWhere(p: T => Boolean): T = sys.error("") + } +} diff --git a/quicklens/src/main/scala-2.13-/com.softwaremill.quicklens/package.scala b/quicklens/src/main/scala-2.13-/com.softwaremill.quicklens/package.scala index d6f0173..079e5bd 100644 --- a/quicklens/src/main/scala-2.13-/com.softwaremill.quicklens/package.scala +++ b/quicklens/src/main/scala-2.13-/com.softwaremill.quicklens/package.scala @@ -1,5 +1,7 @@ package com.softwaremill +import com.softwaremill.quicklens.{QuicklensMapAtFunctor, canOnlyBeUsedInsideModify} + import scala.annotation.compileTimeOnly import scala.collection.TraversableLike import scala.collection.SeqLike @@ -7,9 +9,9 @@ import scala.collection.generic.CanBuildFrom import scala.language.experimental.macros import scala.language.higherKinds -package object quicklens { +package object quicklens extends LowPriorityImplicits { - private def canOnlyBeUsedInsideModify(method: String) = + private[softwaremill] def canOnlyBeUsedInsideModify(method: String) = s"$method can only be used inside modify" /** @@ -178,14 +180,6 @@ package object quicklens { PathLazyModify[T, V]((t, vv) => self.doModify(t, u => f2.doModify(u, vv))) } - implicit class QuicklensEach[F[_], T](t: F[T])(implicit f: QuicklensFunctor[F, T]) { - @compileTimeOnly(canOnlyBeUsedInsideModify("each")) - def each: T = sys.error("") - - @compileTimeOnly(canOnlyBeUsedInsideModify("eachWhere")) - def eachWhere(p: T => Boolean): T = sys.error("") - } - trait QuicklensFunctor[F[_], A] { def map(fa: F[A])(f: A => A): F[A] def each(fa: F[A])(f: A => A): F[A] = map(fa)(f) @@ -241,8 +235,8 @@ package object quicklens { def index(fa: F[T], idx: Int)(f: T => T): F[T] } - implicit class QuicklensMapAt[M[KT, TT] <: Map[KT, TT], K, T](t: M[K, T])( - implicit f: QuicklensMapAtFunctor[M, K, T] + implicit class QuicklensMapAt[M[KT, TT], K, T](t: M[K, T])( + implicit f: QuicklensMapAtFunctor[M, K, T] ) { @compileTimeOnly(canOnlyBeUsedInsideModify("at")) def at(idx: K): T = sys.error("") @@ -316,3 +310,16 @@ package object quicklens { override def eachRight(e: Either[L, R])(f: (R) => R) = e.right.map(f) } } + +sealed trait LowPriorityImplicits { + + import quicklens._ + + implicit class QuicklensEach[F[_], T](t: F[T])(implicit f: QuicklensFunctor[F, T]) { + @compileTimeOnly(canOnlyBeUsedInsideModify("each")) + def each: T = sys.error("") + + @compileTimeOnly(canOnlyBeUsedInsideModify("eachWhere")) + def eachWhere(p: T => Boolean): T = sys.error("") + } +} From 63a1fc4bdfe1d4bdcaf2d7e6d18c77f521e3b412 Mon Sep 17 00:00:00 2001 From: "yu.badalyants" Date: Fri, 10 Dec 2021 17:15:01 +0700 Subject: [PATCH 2/2] Tests for QuicklensMapAtFunctor implicits --- .../com.softwaremill.quicklens/package.scala | 3 ++ .../com.softwaremill.quicklens/package.scala | 3 ++ .../QuicklensMapAtFunctorTest.scala | 28 +++++++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 quicklens/src/test/scala-2/com.softwaremill.quicklens/QuicklensMapAtFunctorTest.scala diff --git a/quicklens/src/main/scala-2.13+/com.softwaremill.quicklens/package.scala b/quicklens/src/main/scala-2.13+/com.softwaremill.quicklens/package.scala index 5db3d5b..b9d9f7d 100644 --- a/quicklens/src/main/scala-2.13+/com.softwaremill.quicklens/package.scala +++ b/quicklens/src/main/scala-2.13+/com.softwaremill.quicklens/package.scala @@ -310,6 +310,9 @@ sealed trait LowPriorityImplicits { import quicklens._ + /** + * `QuicklensEach` is in `LowPriorityImplicits` to not conflict with the `QuicklensMapAtFunctor` on `each` calls. + */ implicit class QuicklensEach[F[_], T](t: F[T])(implicit f: QuicklensFunctor[F, T]) { @compileTimeOnly(canOnlyBeUsedInsideModify("each")) def each: T = sys.error("") diff --git a/quicklens/src/main/scala-2.13-/com.softwaremill.quicklens/package.scala b/quicklens/src/main/scala-2.13-/com.softwaremill.quicklens/package.scala index 079e5bd..f641f69 100644 --- a/quicklens/src/main/scala-2.13-/com.softwaremill.quicklens/package.scala +++ b/quicklens/src/main/scala-2.13-/com.softwaremill.quicklens/package.scala @@ -315,6 +315,9 @@ sealed trait LowPriorityImplicits { import quicklens._ + /** + * `QuicklensEach` is in `LowPriorityImplicits` to not conflict with the `QuicklensMapAtFunctor` on `each` calls. + */ implicit class QuicklensEach[F[_], T](t: F[T])(implicit f: QuicklensFunctor[F, T]) { @compileTimeOnly(canOnlyBeUsedInsideModify("each")) def each: T = sys.error("") diff --git a/quicklens/src/test/scala-2/com.softwaremill.quicklens/QuicklensMapAtFunctorTest.scala b/quicklens/src/test/scala-2/com.softwaremill.quicklens/QuicklensMapAtFunctorTest.scala new file mode 100644 index 0000000..5d8a487 --- /dev/null +++ b/quicklens/src/test/scala-2/com.softwaremill.quicklens/QuicklensMapAtFunctorTest.scala @@ -0,0 +1,28 @@ +package com.softwaremill.quicklens + +import com.softwaremill.quicklens.TestData._ +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +class QuicklensMapAtFunctorTest extends AnyFlatSpec with Matchers { + + "QuicklensMapAtFunctor" should "work for types which is not a subtype of Map" in { + case class MapLike[K, V](underlying: Map[K, V]) + + implicit def instance[K, T]: QuicklensMapAtFunctor[MapLike, K, T] = new QuicklensMapAtFunctor[MapLike, K, T] { + private val mapInstance: QuicklensMapAtFunctor[Map, K, T] = + implicitly[QuicklensMapAtFunctor[Map, K, T]] + + def at(fa: MapLike[K, T], idx: K)(f: T => T): MapLike[K, T] = + MapLike(mapInstance.at(fa.underlying, idx)(f)) + def atOrElse(fa: MapLike[K, T], idx: K, default: => T)(f: T => T): MapLike[K, T] = + MapLike(mapInstance.atOrElse(fa.underlying, idx, default)(f)) + def index(fa: MapLike[K, T], idx: K)(f: T => T): MapLike[K, T] = + MapLike(mapInstance.index(fa.underlying, idx)(f)) + def each(fa: MapLike[K, T])(f: T => T): MapLike[K, T] = + MapLike(mapInstance.each(fa.underlying)(f)) + } + + modify(MapLike(m1))(_.at("K1").a5.name).using(duplicate) should be(MapLike(m1dup)) + } +}