Slow compilation with multiple .modify calls when using Scala3
adamw opened this issue · 6 comments
Original issue: scala/scala3#15945
Code to reproduce:
case class A(x: Double)
case class B(a1: A = A(1), a2: A = A(2), a3: A = A(3), a4: A = A(4))
case class C(b: B)
import com.softwaremill.quicklens._
@main
def main(): Unit = {
val c = C(B(A(1)))
c
.modify(_.b.a1.x).setTo(0)
.modify(_.b.a2.x).setTo(0)
.modify(_.b.a3.x).setTo(0)
.modify(_.b.a4.x).setTo(0)
}
Maybe related to #81 ?
@OndrejSpanel I tried compiling the above, but I don't see any performance problems. Though I had to replace 0
with 0.0d
so that the types match. Are you using latest quicklens version?
It might be that to reproduce it, we will require a type error.
The following code:
//> using scala "3.nightly"
//> using lib "com.softwaremill.quicklens::quicklens:1.8.10"
case class A(x: Double)
case class B(a1: A = A(1), a2: A = A(2), a3: A = A(3), a4: A = A(4), a5: A = A(5))
case class C(b: B)
import com.softwaremill.quicklens._
@main
def main(): Unit = {
val c = C(B(A(1)))
c
.modify(_.b.a1.x).setTo(0)
.modify(_.b.a2.x).setTo(0)
.modify(_.b.a3.x).setTo(0)
.modify(_.b.a4.x).setTo(0)
.modify(_.b.a5.x).setTo(0)
}
Takes very long to finish compilation for me.
Ah, of course, you need a compile error 🤦
The original compiles in ~3seconds, so I didn't notice anything, but now I added a5
and after 2 minutes I'm still waiting ;)
The original compiles in ~3seconds,
This is extract from a real-life project, which took 10 minutes - with the compilation error, but the code was compiling fine with Scala 2. The extract with 4 modify calls shows a constraint explosion (641 constraints , as shown by -Ydetailed-stats
option), yet it compiles quickly enough not to be annoyingly slow.
The problem was that we were using the original source when generating the .copy
, which in case of many parameters (for all of which parameters had to be provided) caused an exponential code explosion. Caching the value in an intermediate value seems to solve the problem.
Just for fun, that's the code we generated for 2 (not 5) chained .modify
invocations:
def x(): Unit = {
(
(x: scala.Function1[scala.AnyVal, scala.AnyVal]) =>
((com.softwaremill.quicklens.PathModify.apply[com.softwaremill.quicklens.x.C, scala.AnyVal](
c,
(
(`x₂`: scala.Function1[scala.AnyVal, scala.AnyVal]) =>
(c.copy(b =
c.b.copy(
a1 = `x₃`.apply(c.b.a1),
c.b.copy$default$2,
c.b.copy$default$3,
c.b.copy$default$4,
c.b.copy$default$5
)
): com.softwaremill.quicklens.x.C)
)
): com.softwaremill.quicklens.PathModify[com.softwaremill.quicklens.x.C, scala.AnyVal])
.setTo(0)
.copy(b =
(com.softwaremill.quicklens.PathModify.apply[com.softwaremill.quicklens.x.C, scala.AnyVal](
c,
(
(`x₄`: scala.Function1[scala.AnyVal, scala.AnyVal]) =>
(c.copy(b =
c.b.copy(
a1 = `x₃`.apply(c.b.a1),
c.b.copy$default$2,
c.b.copy$default$3,
c.b.copy$default$4,
c.b.copy$default$5
)
): com.softwaremill.quicklens.x.C)
)
): com.softwaremill.quicklens.PathModify[com.softwaremill.quicklens.x.C, scala.AnyVal])
.setTo(0)
.b
.copy(
(com.softwaremill.quicklens.PathModify.apply[com.softwaremill.quicklens.x.C, scala.AnyVal](
c,
(
(`x₅`: scala.Function1[scala.AnyVal, scala.AnyVal]) =>
(c.copy(b =
c.b.copy(
a1 = `x₃`.apply(c.b.a1),
c.b.copy$default$2,
c.b.copy$default$3,
c.b.copy$default$4,
c.b.copy$default$5
)
): com.softwaremill.quicklens.x.C)
)
): com.softwaremill.quicklens.PathModify[com.softwaremill.quicklens.x.C, scala.AnyVal])
.setTo(0)
.b
.copy$default$1,
a2 = x.apply(
(com.softwaremill.quicklens.PathModify.apply[com.softwaremill.quicklens.x.C, scala.AnyVal](
c,
(
(`x₆`: scala.Function1[scala.AnyVal, scala.AnyVal]) =>
(c.copy(b =
c.b.copy(
a1 = `x₃`.apply(c.b.a1),
c.b.copy$default$2,
c.b.copy$default$3,
c.b.copy$default$4,
c.b.copy$default$5
)
): com.softwaremill.quicklens.x.C)
)
): com.softwaremill.quicklens.PathModify[com.softwaremill.quicklens.x.C, scala.AnyVal]).setTo(0).b.a2
),
(com.softwaremill.quicklens.PathModify.apply[com.softwaremill.quicklens.x.C, scala.AnyVal](
c,
(
(`x₇`: scala.Function1[scala.AnyVal, scala.AnyVal]) =>
(c.copy(b =
c.b.copy(
a1 = `x₃`.apply(c.b.a1),
c.b.copy$default$2,
c.b.copy$default$3,
c.b.copy$default$4,
c.b.copy$default$5
)
): com.softwaremill.quicklens.x.C)
)
): com.softwaremill.quicklens.PathModify[com.softwaremill.quicklens.x.C, scala.AnyVal])
.setTo(0)
.b
.copy$default$3,
(com.softwaremill.quicklens.PathModify.apply[com.softwaremill.quicklens.x.C, scala.AnyVal](
c,
(
(`x₈`: scala.Function1[scala.AnyVal, scala.AnyVal]) =>
(c.copy(b =
c.b.copy(
a1 = `x₃`.apply(c.b.a1),
c.b.copy$default$2,
c.b.copy$default$3,
c.b.copy$default$4,
c.b.copy$default$5
)
): com.softwaremill.quicklens.x.C)
)
): com.softwaremill.quicklens.PathModify[com.softwaremill.quicklens.x.C, scala.AnyVal])
.setTo(0)
.b
.copy$default$4,
(com.softwaremill.quicklens.PathModify.apply[com.softwaremill.quicklens.x.C, scala.AnyVal](
c,
(
(`x₉`: scala.Function1[scala.AnyVal, scala.AnyVal]) =>
(c.copy(b =
c.b.copy(
a1 = `x₃`.apply(c.b.a1),
c.b.copy$default$2,
c.b.copy$default$3,
c.b.copy$default$4,
c.b.copy$default$5
)
): com.softwaremill.quicklens.x.C)
)
): com.softwaremill.quicklens.PathModify[com.softwaremill.quicklens.x.C, scala.AnyVal])
.setTo(0)
.b
.copy$default$5
)
): com.softwaremill.quicklens.x.C)
)
}