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..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]] } } } @@ -168,6 +175,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]] 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 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")))