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

Optimise boilerplate generators, use instance constructors #3871

Merged
merged 10 commits into from
Jul 15, 2022
10 changes: 2 additions & 8 deletions core/src/main/scala/cats/Show.scala
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,12 @@ object Show extends ScalaVersionSpecificShowInstances with ShowInstances {
/**
* creates an instance of [[Show]] using the provided function
*/
def show[A](f: A => String): Show[A] =
new Show[A] {
def show(a: A): String = f(a)
}
def show[A](f: A => String): Show[A] = f(_)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a practical difference between these? e.g. no class is emitted.

Copy link
Member Author

@joroKr21 joroKr21 Feb 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, indeed 👍 - because Show has only one abstract method

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, thanks. Is this affected by the aforementioned Scala 2 bug?

Also, can Semigroup etc. get the same treatment?

@inline def instance[A](cmb: (A, A) => A): Semigroup[A] =
new Semigroup[A] {
override def combine(x: A, y: A): A = cmb(x, y)
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for Semigroup it doesn't matter because it has other (non-abstract) methods. So then the bug applies and there would be an anonymous classes generated even if we use the SAM syntax. So the only benefit would be aesthetics.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, so it's not just that Show has one abstract method, it's that it has no other methods.

I agree it's only aesthetics on Scala 2, but does the bug apply to Scala 3 as well?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree it's only aesthetics on Scala 2, but does the bug apply to Scala 3 as well?

I don't remember - that's a good question.

Copy link
Member

@armanbilge armanbilge Feb 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I ask is, I don't care so much about the handful of Semigroup.instance etc. that can be re-written.

But I'm wondering if in Scala 3 the boilerplate instances themselves could be written directly like this instead of relying on instance. So we get the win in terms of jar size without introducing any indirection at all.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if that's possible it would be quite hard to split like this 😄

Copy link
Member Author

@joroKr21 joroKr21 Feb 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FTR Scala 3 doesn't have this bug - but again I'm sceptical about version-specific boilerplate generators 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there's interest it can be a followup PR. I think it shouldn't be too hard (famous last words): currently all the instances are in the same file I think, when they could easily be split among a few files. And some of those files can go into the scala-2 and scala-3 srcs.


/**
* creates an instance of [[Show]] using object toString
*/
def fromToString[A]: Show[A] =
new Show[A] {
def show(a: A): String = a.toString
}
def fromToString[A]: Show[A] = _.toString

final case class Shown(override val toString: String) extends AnyVal
object Shown {
Expand Down
5 changes: 1 addition & 4 deletions kernel/src/main/scala/cats/kernel/Hash.scala
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,9 @@ object Hash extends HashFunctions[Hash] {
def hash(x: A) = x.hashCode()
def eqv(x: A, y: A) = x == y
}

}

trait HashToHashingConversion {
implicit def catsKernelHashToHashing[A](implicit ev: Hash[A]): Hashing[A] =
new Hashing[A] {
override def hash(x: A): Int = ev.hash(x)
}
ev.hash(_)
}
44 changes: 21 additions & 23 deletions project/AlgebraBoilerplate.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,7 @@ object AlgebraBoilerplate {
val synVals = (0 until arity).map(n => s"a$n")
val `A..N` = synTypes.mkString(", ")
val `a..n` = synVals.mkString(", ")
val `_.._` = Seq.fill(arity)("_").mkString(", ")
val `(A..N)` = if (arity == 1) "Tuple1[A0]" else synTypes.mkString("(", ", ", ")")
val `(_.._)` = if (arity == 1) "Tuple1[_]" else Seq.fill(arity)("_").mkString("(", ", ", ")")
val `(a..n)` = if (arity == 1) "Tuple1(a)" else synVals.mkString("(", ", ", ")")
}

Expand Down Expand Up @@ -86,32 +84,32 @@ object AlgebraBoilerplate {
import tv._

def constraints(constraint: String) =
synTypes.map(tpe => s"${tpe}: ${constraint}[${tpe}]").mkString(", ")
synTypes.map(tpe => s"$tpe: $constraint[$tpe]").mkString(", ")

def tuple(results: TraversableOnce[String]) = {
val resultsVec = results.toVector
val a = synTypes.size
val r = s"${0.until(a).map(i => resultsVec(i)).mkString(", ")}"
if (a == 1) "Tuple1(" ++ r ++ ")"
else s"(${r})"
else s"($r)"
}

def binMethod(name: String) =
synTypes.zipWithIndex.iterator.map { case (tpe, i) =>
val j = i + 1
s"${tpe}.${name}(x._${j}, y._${j})"
s"$tpe.$name(x._$j, y._$j)"
}

def binTuple(name: String) =
tuple(binMethod(name))

def unaryTuple(name: String) = {
val m = synTypes.zipWithIndex.map { case (tpe, i) => s"${tpe}.${name}(x._${i + 1})" }
val m = synTypes.zipWithIndex.map { case (tpe, i) => s"$tpe.$name(x._${i + 1})" }
tuple(m)
}

def nullaryTuple(name: String) = {
val m = synTypes.map(tpe => s"${tpe}.${name}")
val m = synTypes.map(tpe => s"$tpe.$name")
tuple(m)
}

Expand All @@ -125,34 +123,34 @@ object AlgebraBoilerplate {
-
- implicit def tuple${arity}Rig[${`A..N`}](implicit ${constraints("Rig")}): Rig[${`(A..N)`}] =
- new Rig[${`(A..N)`}] {
- def one: ${`(A..N)`} = ${nullaryTuple("one")}
- def plus(x: ${`(A..N)`}, y: ${`(A..N)`}): ${`(A..N)`} = ${binTuple("plus")}
- def times(x: ${`(A..N)`}, y: ${`(A..N)`}): ${`(A..N)`} = ${binTuple("times")}
- def zero: ${`(A..N)`} = ${nullaryTuple("zero")}
- def zero = ${nullaryTuple("zero")}
- def one = ${nullaryTuple("one")}
- def plus(x: ${`(A..N)`}, y: ${`(A..N)`}) = ${binTuple("plus")}
- def times(x: ${`(A..N)`}, y: ${`(A..N)`}) = ${binTuple("times")}
- }
-
- implicit def tuple${arity}Ring[${`A..N`}](implicit ${constraints("Ring")}): Ring[${`(A..N)`}] =
- new Ring[${`(A..N)`}] {
- def one: ${`(A..N)`} = ${nullaryTuple("one")}
- def plus(x: ${`(A..N)`}, y: ${`(A..N)`}): ${`(A..N)`} = ${binTuple("plus")}
- def times(x: ${`(A..N)`}, y: ${`(A..N)`}): ${`(A..N)`} = ${binTuple("times")}
- def zero: ${`(A..N)`} = ${nullaryTuple("zero")}
- def negate(x: ${`(A..N)`}): ${`(A..N)`} = ${unaryTuple("negate")}
- def zero = ${nullaryTuple("zero")}
- def one = ${nullaryTuple("one")}
- def negate(x: ${`(A..N)`}) = ${unaryTuple("negate")}
- def plus(x: ${`(A..N)`}, y: ${`(A..N)`}) = ${binTuple("plus")}
- def times(x: ${`(A..N)`}, y: ${`(A..N)`}) = ${binTuple("times")}
- }
-
- implicit def tuple${arity}Rng[${`A..N`}](implicit ${constraints("Rng")}): Rng[${`(A..N)`}] =
- new Rng[${`(A..N)`}] {
- def plus(x: ${`(A..N)`}, y: ${`(A..N)`}): ${`(A..N)`} = ${binTuple("plus")}
- def times(x: ${`(A..N)`}, y: ${`(A..N)`}): ${`(A..N)`} = ${binTuple("times")}
- def zero: ${`(A..N)`} = ${nullaryTuple("zero")}
- def negate(x: ${`(A..N)`}): ${`(A..N)`} = ${unaryTuple("negate")}
- def zero = ${nullaryTuple("zero")}
- def negate(x: ${`(A..N)`}) = ${unaryTuple("negate")}
- def plus(x: ${`(A..N)`}, y: ${`(A..N)`}) = ${binTuple("plus")}
- def times(x: ${`(A..N)`}, y: ${`(A..N)`}) = ${binTuple("times")}
- }
-
- implicit def tuple${arity}Semiring[${`A..N`}](implicit ${constraints("Semiring")}): Semiring[${`(A..N)`}] =
- new Semiring[${`(A..N)`}] {
- def plus(x: ${`(A..N)`}, y: ${`(A..N)`}): ${`(A..N)`} = ${binTuple("plus")}
- def times(x: ${`(A..N)`}, y: ${`(A..N)`}): ${`(A..N)`} = ${binTuple("times")}
- def zero: ${`(A..N)`} = ${nullaryTuple("zero")}
- def zero = ${nullaryTuple("zero")}
- def plus(x: ${`(A..N)`}, y: ${`(A..N)`}) = ${binTuple("plus")}
- def times(x: ${`(A..N)`}, y: ${`(A..N)`}) = ${binTuple("times")}
- }
|}
"""
Expand Down
16 changes: 4 additions & 12 deletions project/Boilerplate.scala
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,6 @@ object Boilerplate {
if (arity <= 2) "(*, *)"
else `A..(N - 2)`.mkString("(", ", ", ", *, *)")
val `a..(n - 1)` = (0 until (arity - 1)).map(n => s"a$n")
val `fa._1..fa._(n - 2)` =
if (arity <= 2) "" else (0 until (arity - 2)).map(n => s"fa._${n + 1}").mkString("", ", ", ", ")
val `pure(fa._1..(n - 2))` =
if (arity <= 2) "" else (0 until (arity - 2)).map(n => s"G.pure(fa._${n + 1})").mkString("", ", ", ", ")
val `a0, a(n - 1)` = if (arity <= 1) "" else `a..(n - 1)`.mkString(", ")
val `[A0, A(N - 1)]` = if (arity <= 1) "" else `A..(N - 1)`.mkString("[", ", ", "]")
val `(A0, A(N - 1))` =
Expand All @@ -87,19 +83,15 @@ object Boilerplate {
val `(A..N - 1, *)` =
if (arity == 1) "Tuple1"
else `A..(N - 1)`.mkString("(", ", ", ", *)")
val `(fa._1..(n - 1))` =
if (arity <= 1) "Tuple1.apply" else (0 until (arity - 1)).map(n => s"fa._${n + 1}").mkString("(", ", ", ", _)")

def `A0, A(N - 1)&`(a: String): String =
if (arity <= 1) s"Tuple1[$a]" else `A..(N - 1)`.mkString("(", ", ", s", $a)")

def `fa._1..(n - 1) & `(a: String): String =
if (arity <= 1) s"Tuple1($a)" else (0 until (arity - 1)).map(n => s"fa._${n + 1}").mkString("(", ", ", s", $a)")

def `constraints A..N`(c: String): String = synTypes.map(tpe => s"$tpe: $c[$tpe]").mkString("(implicit ", ", ", ")")
def `constraints A..N`(c: String): String =
synTypes.map(tpe => s"$tpe: $c[$tpe]").mkString("(implicit ", ", ", ")")
def `constraints A..(N-1)`(c: String): String =
if (arity <= 1) "" else `A..(N - 1)`.map(tpe => s"$tpe: $c[$tpe]").mkString("(implicit ", ", ", ")")
def `parameters A..(N-1)`(c: String): String = `A..(N - 1)`.map(tpe => s"$tpe: $c[$tpe]").mkString(", ")
def `parameters A..(N-1)`(c: String): String =
`A..(N - 1)`.map(tpe => s"$tpe: $c[$tpe]").mkString(", ")
}

trait Template {
Expand Down
Loading