def gen()

in project/GenTupleAggregators.scala [6:148]


  def gen(dir: File) = {
    val tupleAggPlace = dir / "com" / "twitter" / "algebird" / "GeneratedTupleAggregators.scala"
    IO.write(
      tupleAggPlace,
      """package com.twitter.algebird

object GeneratedTupleAggregator extends GeneratedTupleAggregator

trait GeneratedTupleAggregator {
""" + genMethods(22, "implicit def", None) + "\n" + "}"
    )

    val multiAggPlace = dir / "com" / "twitter" / "algebird" / "MultiAggregator.scala"
    IO.write(
      multiAggPlace,
      """package com.twitter.algebird

object MultiAggregator {
""" +
        genMethods(22, "def", Some("apply")) + "\n" +
        genMethods(22, "def", Some("apply"), true) + "\n" + "}"
    )

    val mapAggPlace = dir / "com" / "twitter" / "algebird" / "MapAggregator.scala"
    IO.write(
      mapAggPlace,
      s"""
      |package com.twitter.algebird
      |
      |trait MapAggregator[A, B, K, C] extends Aggregator[A, B, Map[K, C]] {
      |  def keys: Set[K]
      |}
      |
      |trait MapMonoidAggregator[A, B, K, C] extends MonoidAggregator[A, B, Map[K, C]] {
      |  def keys: Set[K]
      |}
      |
      |object MapAggregator {
      |  ${genMapMethods(22)}
      |  ${genMapMethods(22, isMonoid = true)}
      |}
      """.stripMargin
    )

    Seq(tupleAggPlace, multiAggPlace, mapAggPlace)
  }

  def genMethods(max: Int, defStr: String, name: Option[String], isMonoid: Boolean = false): String =
    (2 to max)
      .map { i =>
        val methodName = name.getOrElse("from%d".format(i))
        val aggType = if (isMonoid) "Monoid" else ""
        val nums = 1 to i
        val bs = nums.map("B" + _).mkString(", ")
        val cs = nums.map("C" + _).mkString(", ")
        val aggs = nums.map(x => "%sAggregator[A, B%s, C%s]".format(aggType, x, x)).mkString(", ")
        val prepares = nums.map(x => "aggs._%s.prepare(a)".format(x)).mkString(", ")
        val semiType = if (isMonoid) "monoid" else "semigroup"
        val semigroups = nums.map(x => "aggs._%s.%s".format(x, semiType)).mkString(", ")
        val semigroup = "new Tuple%d%s()(%s)".format(i, semiType.capitalize, semigroups)
        val present = nums.map(x => "aggs._%s.present(b._%s)".format(x, x)).mkString(", ")
        val tupleBs = "Tuple%d[%s]".format(i, bs)
        val tupleCs = "Tuple%d[%s]".format(i, cs)

        """
%s %s[A, %s, %s](aggs: Tuple%d[%s]): %sAggregator[A, %s, %s] = {
  new %sAggregator[A, %s, %s] {
    def prepare(a: A) = (%s)
    val %s = %s
    def present(b: %s) = (%s)
  }
}""".format(
          defStr,
          methodName,
          bs,
          cs,
          i,
          aggs,
          aggType,
          tupleBs,
          tupleCs,
          aggType,
          tupleBs,
          tupleCs,
          prepares,
          semiType,
          semigroup,
          tupleBs,
          present
        )
      }
      .mkString("\n")

  def genMapMethods(max: Int, isMonoid: Boolean = false): String = {
    val inputAggregatorType = if (isMonoid) "MonoidAggregator" else "Aggregator"
    val mapAggregatorType = if (isMonoid) "MapMonoidAggregator" else "MapAggregator"

    val semigroupType = if (isMonoid) "monoid" else "semigroup"

    // there's no Semigroup[Tuple1[T]], so just use T as intermediary type instead of Tuple1[T]
    // TODO: keys for 1 item
    val aggregatorForOneItem = s"""
       |def apply[K, A, B, C](agg: (K, $inputAggregatorType[A, B, C])): $mapAggregatorType[A, B, K, C] = {
       |  new $mapAggregatorType[A, B, K, C] {
       |    def prepare(a: A) = agg._2.prepare(a)
       |    val $semigroupType = agg._2.$semigroupType
       |    def present(b: B) = Map(agg._1 -> agg._2.present(b))
       |    def keys = Set(agg._1)
       |  }
       |}
    """.stripMargin

    (2 to max)
      .map { aggrCount =>
        val aggrNums = 1 to aggrCount

        val inputAggs = aggrNums.map(i => s"agg$i: (K, $inputAggregatorType[A, B$i, C])").mkString(", ")

        val bs = aggrNums.map("B" + _).mkString(", ")
        val tupleBs = s"Tuple$aggrCount[$bs]"

        s"""
      |def apply[K, A, $bs, C]($inputAggs): $mapAggregatorType[A, $tupleBs, K, C] = {
      |  new $mapAggregatorType[A, $tupleBs, K, C] {
      |    def prepare(a: A) = (
      |      ${aggrNums.map(i => s"agg$i._2.prepare(a)").mkString(", ")}
      |    )
      |    // a field for semigroup/monoid that combines all input aggregators
      |    val $semigroupType = new Tuple$aggrCount${semigroupType.capitalize}()(
      |      ${aggrNums.map(i => s"agg$i._2.$semigroupType").mkString(", ")}
      |    )
      |    def present(b: $tupleBs) = Map(
      |      ${aggrNums.map(i => s"agg$i._1 -> agg$i._2.present(b._$i)").mkString(", ")}
      |    )
      |    def keys: Set[K] = Set(
      |      ${aggrNums.map(i => s"agg$i._1").mkString(", ")}
      |    )
      |  }
      |}""".stripMargin
      }
      .mkString("\n") + aggregatorForOneItem
  }
}