From c4fc783a75c017d4e3c1060f268b850cffc00f96 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 14 Mar 2014 12:36:17 +0100 Subject: [PATCH 1/2] Support Scala 2.10 ... by adding the specialized variants of `andThen`, `compose`, `curried`, and `tupled`. In Scala 2.11, these variants no longer exists, as the `@unspecialized` facility has been fixed and now suppresses specialization for those methods. But having these in the functional interface cross-compatible with 2.10 and 2.11. --- build.sbt | 2 +- project/CodeGen.scala | 59 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/build.sbt b/build.sbt index d0304c2..45d3ab9 100644 --- a/build.sbt +++ b/build.sbt @@ -1,4 +1,4 @@ -scalaVersion := "2.11.0-RC1" +scalaVersion := "2.10.3" sourceGenerators in Compile <+= sourceManaged in Compile map { dir => def write(name: String, content: String) = { diff --git a/project/CodeGen.scala b/project/CodeGen.scala index f449e6c..0bd65e1 100644 --- a/project/CodeGen.scala +++ b/project/CodeGen.scala @@ -141,6 +141,7 @@ object CodeGen { private val initName = "$init$" private val function1ImplClass = "scala.Function1$class" + private val function2ImplClass = "scala.Function2$class" private val copyright = """ |/* @@ -164,6 +165,9 @@ object CodeGen { methods.map(indent).mkString("\n\n") } + val function1SpecTs = List(Type.Int, Type.Long, Type.Float, Type.Double) + val function1SpecRs = List(Type.Void, Type.Boolean, Type.Int, Type.Float, Type.Long, Type.Double) + private def apply1MethodSpec(t1: Type, r: Type): String = { val name = "apply$mc" + s"${r.code}${t1.code}" + "$sp" val applyCall = s"apply((T1) ((${t1.ref}) v1));" @@ -177,12 +181,31 @@ object CodeGen { } private def apply1SpecMethods = { - val ts = List(Type.Int, Type.Long, Type.Float, Type.Double) - val rs = List(Type.Void, Type.Boolean, Type.Int, Type.Float, Type.Long, Type.Double) - val methods = for (t1 <- ts; r <- rs) yield apply1MethodSpec(t1, r) + val methods = for (t1 <- function1SpecTs; r <- function1SpecRs) yield apply1MethodSpec(t1, r) + methods.map(indent).mkString("\n\n") + } + + private def andThenComposeMethodSpec(t1: Type, r: Type): String = { + val suffix = "$mc" + s"${r.code}${t1.code}" + "$sp" + s""" + |default scala.Function1 compose$suffix(scala.Function1 g) { + | return compose(g); + |} + |default scala.Function1 andThen$suffix(scala.Function1 g) { + | return andThen(g); + |} + |""".stripMargin.trim + } + + // No longer needed under 2.11 (@unspecialized has been fixed), but harmless to keep around to avoid cross-publishing this artifact. + private def andThenComposeSpecMethods = { + val methods = for (t1 <- function1SpecTs; r <- function1SpecRs) yield andThenComposeMethodSpec(t1, r) methods.map(indent).mkString("\n\n") } + val function2SpecTs = List(Type.Int, Type.Long, Type.Double) + val function2SpecRs = function1SpecRs + private def apply2MethodSpec(t1: Type, t2: Type, r: Type): String = { val name = "apply$mc" + s"${r.code}${t1.code}${t2.code}" + "$sp" val applyCall = s"apply((T1) ((${t1.ref}) v1), (T2) ((${t2.ref}) v2));" @@ -196,22 +219,38 @@ object CodeGen { } private def apply2SpecMethods = { - val ts = List(Type.Int, Type.Long, Type.Double) - val rs = List(Type.Void, Type.Boolean, Type.Int, Type.Float, Type.Long, Type.Double) - val methods = for (t1 <- ts; t2 <- ts; r <- rs) yield apply2MethodSpec(t1, t2, r) + val methods = for (t1 <- function2SpecTs; t2 <- function2SpecTs; r <- function2SpecRs) yield apply2MethodSpec(t1, t2, r) + methods.map(indent).mkString("\n\n") + } + + private def curriedTupled2MethodSpec(t1: Type, t2: Type, r: Type): String = { + val suffix = "$mc" + s"${r.code}${t1.code}${t2.code}" + "$sp" + s""" + |default scala.Function1 curried$suffix() { + | return curried(); + |} + |default scala.Function1 tupled$suffix() { + | return tupled(); + |} + |""".stripMargin.trim + } + + // No longer needed under 2.11 (@unspecialized has been fixed), but harmless to keep around to avoid cross-publishing this artifact. + private def curriedTupled2SpecMethods = { + val methods = for (t1 <- function2SpecTs; t2 <- function2SpecTs; r <- function2SpecRs) yield curriedTupled2MethodSpec(t1, t2, r) methods.map(indent).mkString("\n\n") } def fN(n: Int) = { val header = arity(n).fHeader - val applyMethods = n match { + val specializedVariants = n match { case 0 => apply0SpecMethods - case 1 => apply1SpecMethods - case 2 => apply2SpecMethods + case 1 => apply1SpecMethods + "\n\n" + andThenComposeSpecMethods + case 2 => apply2SpecMethods + "\n\n" + curriedTupled2SpecMethods case x => "" } val trailer = "}\n" - List(header, applyMethods, trailer).mkString + List(header, specializedVariants, trailer).mkString } def pN(n: Int) = arity(n).pN From 909f39dc631d5fadb298eb9fd1135360d147126d Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 14 Mar 2014 13:28:44 +0100 Subject: [PATCH 2/2] Refactor code generation of specialized variants --- project/CodeGen.scala | 178 +++++++++++++++++++++++------------------- 1 file changed, 96 insertions(+), 82 deletions(-) diff --git a/project/CodeGen.scala b/project/CodeGen.scala index 0bd65e1..adec5e7 100644 --- a/project/CodeGen.scala +++ b/project/CodeGen.scala @@ -141,115 +141,129 @@ object CodeGen { private val initName = "$init$" private val function1ImplClass = "scala.Function1$class" - private val function2ImplClass = "scala.Function2$class" private val copyright = """ |/* | * Copyright (C) 2012-2014 Typesafe Inc. | */""".stripMargin.trim - private def apply0MethodSpec(r: Type): String = { - val name = "apply$mc" + s"${r.code}" + "$sp" - val applyCall = s"apply();" - def body = if (r == Type.Void) applyCall else s"return (${r.ref}) $applyCall" - s""" - |default ${r.prim} $name() { - | $body - |} - |""".stripMargin.trim + private def function0SpecMethods = { + val apply = specialized("apply", function0Spec) { + case (name, List(r)) => + val applyCall = s"apply();" + def body = if (r == Type.Void) applyCall else s"return (${r.ref}) $applyCall" + s""" + |default ${r.prim} $name() { + | $body + |} + |""".stripMargin.trim + } + indent(apply) } - private def apply0SpecMethods = { + private val function0Spec = { val rs = List(Type.Void, Type.Byte, Type.Short, Type.Int, Type.Long, Type.Char, Type.Float, Type.Double, Type.Boolean) - val methods = for (r <- rs) yield apply0MethodSpec(r) - methods.map(indent).mkString("\n\n") + List("R" -> rs) } - - val function1SpecTs = List(Type.Int, Type.Long, Type.Float, Type.Double) - val function1SpecRs = List(Type.Void, Type.Boolean, Type.Int, Type.Float, Type.Long, Type.Double) - - private def apply1MethodSpec(t1: Type, r: Type): String = { - val name = "apply$mc" + s"${r.code}${t1.code}" + "$sp" - val applyCall = s"apply((T1) ((${t1.ref}) v1));" - def body = if (r == Type.Void) applyCall else s"return (${r.ref}) $applyCall" - - s""" - |default ${r.prim} $name(${t1.prim} v1) { - | $body - |} - |""".stripMargin.trim + private val function1Spec = { + val ts = List(Type.Int, Type.Long, Type.Float, Type.Double) + val rs = List(Type.Void, Type.Boolean, Type.Int, Type.Float, Type.Long, Type.Double) + List("T1" -> ts, "R" -> rs) } - - private def apply1SpecMethods = { - val methods = for (t1 <- function1SpecTs; r <- function1SpecRs) yield apply1MethodSpec(t1, r) - methods.map(indent).mkString("\n\n") + private val function2Spec = { + val ts = List(Type.Int, Type.Long, Type.Double) + val rs = List(Type.Void, Type.Boolean, Type.Int, Type.Float, Type.Long, Type.Double) + List("T1" -> ts, "T2" -> ts, "R" -> rs) } - private def andThenComposeMethodSpec(t1: Type, r: Type): String = { - val suffix = "$mc" + s"${r.code}${t1.code}" + "$sp" - s""" - |default scala.Function1 compose$suffix(scala.Function1 g) { - | return compose(g); - |} - |default scala.Function1 andThen$suffix(scala.Function1 g) { - | return andThen(g); - |} - |""".stripMargin.trim + private def function1SpecMethods = { + val apply = specialized("apply", function1Spec) { + case (name, List(t1, r)) => + val applyCall = s"apply((T1) ((${t1.ref}) v1));" + def body = if (r == Type.Void) applyCall else s"return (${r.ref}) $applyCall" + s""" + |default ${r.prim} $name(${t1.prim} v1) { + | $body + |} + |""".stripMargin.trim + } + // andThen / compose variants are no longer needed under 2.11 (@unspecialized has been fixed), + // but harmless. With them, we can use the same artifact for 2.10 and 2.11 + val compose = specialized("compose", function1Spec) { + case (name, List(t1, r1)) => + s""" + |default scala.Function1 $name(scala.Function1 g) { + | return compose(g); + |}""".stripMargin.trim + } + val andThen = specialized("andThen", function1Spec) { + case (name, List(t1, r1)) => + s""" + |default scala.Function1 $name(scala.Function1 g) { + | return andThen(g); + |}""".stripMargin.trim + } + indent(List(apply, compose, andThen).mkString("\n\n")) } // No longer needed under 2.11 (@unspecialized has been fixed), but harmless to keep around to avoid cross-publishing this artifact. - private def andThenComposeSpecMethods = { - val methods = for (t1 <- function1SpecTs; r <- function1SpecRs) yield andThenComposeMethodSpec(t1, r) - methods.map(indent).mkString("\n\n") - } - - val function2SpecTs = List(Type.Int, Type.Long, Type.Double) - val function2SpecRs = function1SpecRs - - private def apply2MethodSpec(t1: Type, t2: Type, r: Type): String = { - val name = "apply$mc" + s"${r.code}${t1.code}${t2.code}" + "$sp" - val applyCall = s"apply((T1) ((${t1.ref}) v1), (T2) ((${t2.ref}) v2));" - def body = if (r == Type.Void) applyCall else s"return (${r.ref}) $applyCall" - - s""" - |default ${r.prim} $name(${t1.prim} v1, ${t2.prim} v2) { - | $body - |} - |""".stripMargin.trim - } - - private def apply2SpecMethods = { - val methods = for (t1 <- function2SpecTs; t2 <- function2SpecTs; r <- function2SpecRs) yield apply2MethodSpec(t1, t2, r) - methods.map(indent).mkString("\n\n") + private def function2SpecMethods = { + val apply = specialized("apply", function2Spec) { + case (name, List(t1, t2, r)) => + val applyCall = s"apply((T1) ((${t1.ref}) v1), (T2) ((${t2.ref}) v2));" + def body = if (r == Type.Void) applyCall else s"return (${r.ref}) $applyCall" + + s""" + |default ${r.prim} $name(${t1.prim} v1, ${t2.prim} v2) { + | $body + |} + |""".stripMargin.trim + } + val curried = specialized("curried", function2Spec) { + case (name, List(t1, t2, r)) => + s""" + |default scala.Function1 $name() { + | return curried(); + |}""".stripMargin.trim + } + val tupled = specialized("tupled", function2Spec) { + case (name, List(t1, t2, r)) => + s""" + |default scala.Function1 $name() { + | return tupled(); + |}""".stripMargin.trim + } + indent(List(apply, curried, tupled).mkString("\n\n")) } - private def curriedTupled2MethodSpec(t1: Type, t2: Type, r: Type): String = { - val suffix = "$mc" + s"${r.code}${t1.code}${t2.code}" + "$sp" - s""" - |default scala.Function1 curried$suffix() { - | return curried(); - |} - |default scala.Function1 tupled$suffix() { - | return tupled(); - |} - |""".stripMargin.trim + private def specialized(name: String, tps: List[(String, List[Type])])(f: (String, List[Type]) => String): String = { + val tparamNames = tps.map(_._1) + def code(tps: List[Type]) = { + val sorted = (tps zip tparamNames).sortBy(_._2).map(_._1) // as per scalac, sort by tparam name before assembling the code + sorted.map(_.code).mkString + } + val ms = for { + variantTypes <- crossProduct(tps.map(_._2)) + specName = name + "$mc" + code(variantTypes) + "$sp" + } yield f(specName, variantTypes) + ms.mkString("\n") } - // No longer needed under 2.11 (@unspecialized has been fixed), but harmless to keep around to avoid cross-publishing this artifact. - private def curriedTupled2SpecMethods = { - val methods = for (t1 <- function2SpecTs; t2 <- function2SpecTs; r <- function2SpecRs) yield curriedTupled2MethodSpec(t1, t2, r) - methods.map(indent).mkString("\n\n") + def crossProduct[A](input: List[List[A]]): List[List[A]] = input match { + case Nil => Nil + case head :: Nil => head.map(_ :: Nil) + case head :: tail => for (elem <- head; sub <- crossProduct(tail)) yield elem :: sub } def fN(n: Int) = { val header = arity(n).fHeader val specializedVariants = n match { - case 0 => apply0SpecMethods - case 1 => apply1SpecMethods + "\n\n" + andThenComposeSpecMethods - case 2 => apply2SpecMethods + "\n\n" + curriedTupled2SpecMethods + case 0 => function0SpecMethods + case 1 => function1SpecMethods + case 2 => function2SpecMethods case x => "" } - val trailer = "}\n" + val trailer = "\n}\n" List(header, specializedVariants, trailer).mkString }