From 553dd0342e23aad59d67b8f347e26a4b5906f062 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C5=A0pan=C4=9Bl?= Date: Thu, 28 Apr 2022 16:48:19 +0200 Subject: [PATCH 1/3] Array each / index test --- .../quicklens/ModifyArrayIndexTest.scala | 27 +++++++++++++++++++ .../com/softwaremill/quicklens/TestData.scala | 3 +++ 2 files changed, 30 insertions(+) create mode 100644 quicklens/src/test/scala/com/softwaremill/quicklens/ModifyArrayIndexTest.scala diff --git a/quicklens/src/test/scala/com/softwaremill/quicklens/ModifyArrayIndexTest.scala b/quicklens/src/test/scala/com/softwaremill/quicklens/ModifyArrayIndexTest.scala new file mode 100644 index 0000000..edc59f8 --- /dev/null +++ b/quicklens/src/test/scala/com/softwaremill/quicklens/ModifyArrayIndexTest.scala @@ -0,0 +1,27 @@ +package com.softwaremill.quicklens + +import com.softwaremill.quicklens.TestData._ +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +class ModifyArrayIndexTest extends AnyFlatSpec with Matchers { + + it should "modify a non-nested array with case class item" in { + modify(ar1)(_.index(2).a4.a5.name).using(duplicate) should be(l1at2dup) + modify(ar1)(_.index(2)) + .using(a3 => modify(a3)(_.a4.a5.name).using(duplicate)) should be(l1at2dup) + } + + it should "modify a nested array using index" in { + modify(arar1)(_.index(2).index(1).name).using(duplicate) should be(ll1at2at1dup) + } + + it should "modify a nested array using index and each" in { + modify(arar1)(_.index(2).each.name).using(duplicate) should be(ll1at2eachdup) + modify(arar1)(_.each.index(1).name).using(duplicate) should be(ll1eachat1dup) + } + + it should "not modify if given index does not exist" in { + modify(ar1)(_.index(10).a4.a5.name).using(duplicate) should be(l1) + } +} diff --git a/quicklens/src/test/scala/com/softwaremill/quicklens/TestData.scala b/quicklens/src/test/scala/com/softwaremill/quicklens/TestData.scala index 9ced840..73cfd97 100644 --- a/quicklens/src/test/scala/com/softwaremill/quicklens/TestData.scala +++ b/quicklens/src/test/scala/com/softwaremill/quicklens/TestData.scala @@ -70,6 +70,9 @@ object TestData { val is1 = l1.toIndexedSeq val iss1 = ss1.map(_.toIndexedSeq).toIndexedSeq + val ar1: Array[A3] = s1.toArray + val arar1: Array[Array[A5]] = ss1.map(_.toArray).toArray + case class M2(m3: Map[String, A4]) val m1 = Map("K1" -> A4(A5("d1")), "K2" -> A4(A5("d2")), "K3" -> A4(A5("d3"))) From 6c4c95eb6ad66d56ab0cd93d8244201721efb0e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C5=A0pan=C4=9Bl?= Date: Thu, 28 Apr 2022 17:00:23 +0200 Subject: [PATCH 2/3] Implement QuicklensIndexedFunctor for Array --- .../scala-3/com/softwaremill/quicklens/package.scala | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/quicklens/src/main/scala-3/com/softwaremill/quicklens/package.scala b/quicklens/src/main/scala-3/com/softwaremill/quicklens/package.scala index 5fc30cf..2a95d87 100644 --- a/quicklens/src/main/scala-3/com/softwaremill/quicklens/package.scala +++ b/quicklens/src/main/scala-3/com/softwaremill/quicklens/package.scala @@ -168,6 +168,18 @@ package object quicklens { if fa.isDefinedAt(idx) then fa.updated(idx, f(fa(idx))).asInstanceOf[S[A]] else fa } + given QuicklensIndexedFunctor[Array, Int] with { + def at[A](fa: Array[A], f: A => A, idx: Int): Array[A] = + implicit val aClassTag: ClassTag[A] = fa.elemTag.asInstanceOf[ClassTag[A]] + fa.updated(idx, f(fa(idx))) + def atOrElse[A](fa: Array[A], f: A => A, idx: Int, default: => A): Array[A] = + implicit val aClassTag: ClassTag[A] = fa.elemTag.asInstanceOf[ClassTag[A]] + fa.updated(idx, f(fa.applyOrElse(idx, Function.const(default)))) + def index[A](fa: Array[A], f: A => A, idx: Int): Array[A] = + implicit val aClassTag: ClassTag[A] = fa.elemTag.asInstanceOf[ClassTag[A]] + if fa.isDefinedAt(idx) then fa.updated(idx, f(fa(idx))) else fa + } + given [K, M <: ([V] =>> Map[K, V])]: QuicklensIndexedFunctor[M, K] with { def at[A](fa: M[A], f: A => A, idx: K): M[A] = fa.updated(idx, f(fa(idx))).asInstanceOf[M[A]] From 56cc9390319a6052c8be5b3edc1423d367b631e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C5=A0pan=C4=9Bl?= Date: Thu, 28 Apr 2022 17:18:23 +0200 Subject: [PATCH 3/3] Implement QuicklensFunctor for Array (map[A, B] simplified to map[A]) --- .../com/softwaremill/quicklens/package.scala | 17 ++++++++++++----- .../quicklens/test/ModifyAllOptimizedTest.scala | 3 ++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/quicklens/src/main/scala-3/com/softwaremill/quicklens/package.scala b/quicklens/src/main/scala-3/com/softwaremill/quicklens/package.scala index 2a95d87..f544c3b 100644 --- a/quicklens/src/main/scala-3/com/softwaremill/quicklens/package.scala +++ b/quicklens/src/main/scala-3/com/softwaremill/quicklens/package.scala @@ -3,6 +3,7 @@ package com.softwaremill import scala.collection.{Factory, SortedMap} import scala.annotation.compileTimeOnly import com.softwaremill.quicklens.QuicklensMacros._ +import scala.reflect.ClassTag package object quicklens { @@ -127,27 +128,33 @@ package object quicklens { } trait QuicklensFunctor[F[_]] { - def map[A, B](fa: F[A], f: A => B): F[B] + def map[A](fa: F[A], f: A => A): F[A] def each[A](fa: F[A], f: A => A): F[A] = map(fa, f) def eachWhere[A](fa: F[A], f: A => A, cond: A => Boolean): F[A] = map(fa, x => if cond(x) then f(x) else x) } object QuicklensFunctor { given [S <: ([V] =>> Seq[V])]: QuicklensFunctor[S] with { - def map[A, B](fa: S[A], f: A => B): S[B] = fa.map(f).asInstanceOf[S[B]] + def map[A](fa: S[A], f: A => A): S[A] = fa.map(f).asInstanceOf[S[A]] } given QuicklensFunctor[Option] with { - def map[A, B](fa: Option[A], f: A => B): Option[B] = fa.map(f) + def map[A](fa: Option[A], f: A => A): Option[A] = fa.map(f) + } + + given QuicklensFunctor[Array] with { + def map[A](fa: Array[A], f: A => A): Array[A] = + implicit val aClassTag: ClassTag[A] = fa.elemTag.asInstanceOf[ClassTag[A]] + fa.map(f) } given [K, M <: ([V] =>> Map[K, V])]: QuicklensFunctor[M] with { - def map[A, B](fa: M[A], f: A => B): M[B] = { + def map[A](fa: M[A], f: A => A): M[A] = { val mapped = fa.view.mapValues(f) (fa match { case sfa: SortedMap[K, A] => sfa.sortedMapFactory.from(mapped)(using sfa.ordering) case _ => mapped.to(fa.mapFactory) - }).asInstanceOf[M[B]] + }).asInstanceOf[M[A]] } } } diff --git a/quicklens/src/test/scala-3/com/softwaremill/quicklens/test/ModifyAllOptimizedTest.scala b/quicklens/src/test/scala-3/com/softwaremill/quicklens/test/ModifyAllOptimizedTest.scala index 82e4709..406539f 100644 --- a/quicklens/src/test/scala-3/com/softwaremill/quicklens/test/ModifyAllOptimizedTest.scala +++ b/quicklens/src/test/scala-3/com/softwaremill/quicklens/test/ModifyAllOptimizedTest.scala @@ -3,6 +3,7 @@ package com.softwaremill.quicklens import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.BeforeAndAfterEach import org.scalatest.matchers.should.Matchers +import scala.reflect.ClassTag class ModifyAllOptimizedTest extends AnyFlatSpec with Matchers with BeforeAndAfterEach { import ModifyAllOptimizedTest._ @@ -186,7 +187,7 @@ object ModifyAllOptimizedTest { } given QuicklensFunctor[Opt] with { - def map[A, B](fa: Opt[A], f: A => B): Opt[B] = + def map[A](fa: Opt[A], f: A => A): Opt[A] = Opt.eachCount = Opt.eachCount + 1 fa match { case Nada => Nada