Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Slow compilation with multiple .modify calls when using Scala3 #114

Closed
adamw opened this issue Sep 10, 2022 · 6 comments · Fixed by #115
Closed

Slow compilation with multiple .modify calls when using Scala3 #114

adamw opened this issue Sep 10, 2022 · 6 comments · Fixed by #115

Comments

@adamw
Copy link
Member

adamw commented Sep 10, 2022

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)
}

cc @odersky @OndrejSpanel

Maybe related to #81 ?

@adamw
Copy link
Member Author

adamw commented Sep 10, 2022

@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?

@KacperFKorban
Copy link
Collaborator

KacperFKorban commented Sep 10, 2022

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.

@KacperFKorban
Copy link
Collaborator

Also, I don't think it has much to do with #81 or #82, since the compilation time seems the same for com.softwaremill.quicklens::quicklens:1.8.2.

@adamw
Copy link
Member Author

adamw commented Sep 10, 2022

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 ;)

@OndrejSpanel
Copy link
Contributor

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.

@adamw
Copy link
Member Author

adamw commented Sep 11, 2022

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)
  )
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants