Skip to content

Instantly share code, notes, and snippets.

@kitlangton
Created May 3, 2021 16:32
Show Gist options
  • Save kitlangton/185a59e0555fe966f93c2e371f429f48 to your computer and use it in GitHub Desktop.
Save kitlangton/185a59e0555fe966f93c2e371f429f48 to your computer and use it in GitHub Desktop.
package hlist
import hlist.HList.HNil
import meetup.TupleExtractor
sealed trait HList
case class ::[H, T <: HList](head: H, tail: T) extends HList
case object HNil extends HList { self =>
def ::[H](that: H): H :: HNil = new ::(that, self)
}
object HList {
type HNil = HNil.type
def apply(): HNil = HNil
def apply[H](value: H): H :: HNil = value :: HNil
}
trait Concat[A, B] {
type Out <: HList
}
object Concat extends ConcatLowPri1 {
type Aux[A, B, C] = Concat[A, B] { type Out = C }
// A :: B :: HNil ++ C :: D :: HNil
// A :: B :: HNil ++ C :: D :: HNil
implicit def append[B <: HList]: Concat.Aux[HNil, B, B] = new Concat[HNil, B] {
type Out = B
}
implicit def append2[AH, AT <: HList, B <: HList](implicit
concat: Concat[AT, B]
): Concat.Aux[AH :: AT, B, AH :: concat.Out] =
new Concat[AH :: AT, B] {
type Out = AH :: concat.Out
}
}
trait ConcatLowPri0 {
implicit def append4[A, B]: Concat.Aux[A, B, A :: B :: HNil] = new Concat[A, B] {
type Out = A :: B :: HNil
}
}
trait ConcatLowPri1 extends ConcatLowPri0 {
implicit def append3[A, B <: HList]: Concat.Aux[A, B, A :: B] = new Concat[A, B] {
type Out = A :: B
}
}
trait Join[A, B] {
type Out
}
object Join extends JoinLowPri1 {
type Aux[A, B, C] = Join[A, B] { type Out = C }
implicit def joinAB[A, B]: Join.Aux[A, B, AggSplit2[A, B]] = new Join[A, B] {
override type Out = AggSplit2[A, B]
}
}
trait JoinLowPri1 {}
case class Agg[A]() {
def apply[S](seq: Seq[S])(implicit aggr: Aggr[S, A]): aggr.Out =
aggr.aggregate(seq)
def >>>[B](that: Agg[B])(implicit
concat: Concat[A, B]
): Agg[concat.Out] = new Agg[concat.Out]
def ++[B](that: Agg[B])(implicit
join: Join[A, B]
): Agg[join.Out] = new Agg[join.Out]
}
object Agg {
def apply[A]: Agg[A] = new Agg[A]()
def apply[A, B](implicit d1: DummyImplicit): Agg[A :: B :: HNil] = new Agg[A :: B :: HNil]()
def apply[A, B, C](implicit d1: DummyImplicit, d2: DummyImplicit): Agg[A :: B :: C :: HNil] =
new Agg[A :: B :: C :: HNil]()
}
trait Aggr[S, A] {
type Out
def aggregate(s: Seq[S]): Out
}
case class Agg2[A, B]()
case class AggSplit2[A, B]()
trait LowPriAggr {
implicit def agg2[S, A](implicit
ta: TupleExtractor[S, A]
): Aggr.Aux[S, A, Seq[A]] =
new Aggr[S, A] {
override type Out = Seq[A]
override def aggregate(s: Seq[S]): Seq[A] =
s.map(ta(_)).distinct
}
}
object Aggr extends LowPriAggr {
type Aux[S, A, O] = Aggr[S, A] { type Out = O }
implicit final class AggOps[S](self: Seq[S]) {
def agg[A](implicit aggr: Aggr[S, A]): aggr.Out =
aggr.aggregate(self)
}
implicit def agg2AB[S, A, B <: HList](implicit
ta: TupleExtractor[S, A],
aggr: Aggr[S, B]
): Aggr.Aux[S, A :: B, Map[A, aggr.Out]] =
new Aggr[S, A :: B] {
override type Out = Map[A, aggr.Out]
override def aggregate(s: Seq[S]): Map[A, aggr.Out] =
s.groupBy(ta(_)).view.mapValues(s => aggr.aggregate(s)).toMap
}
implicit def agg2ABNil[S, A, B](implicit
ta: TupleExtractor[S, A],
aggr: Aggr[S, B]
): Aggr.Aux[S, A :: B :: HNil, Map[A, aggr.Out]] =
new Aggr[S, A :: B :: HNil] {
override type Out = Map[A, aggr.Out]
override def aggregate(s: Seq[S]): Map[A, aggr.Out] =
s.groupBy(ta(_)).view.mapValues(s => aggr.aggregate(s)).toMap
}
implicit def agg2Split[S, A, B](implicit
aggA: Aggr[S, A],
aggB: Aggr[S, B]
): Aggr.Aux[S, AggSplit2[A, B], (aggA.Out, aggB.Out)] =
new Aggr[S, AggSplit2[A, B]] {
override type Out = (aggA.Out, aggB.Out)
override def aggregate(s: Seq[S]): (aggA.Out, aggB.Out) =
(aggA.aggregate(s), aggB.aggregate(s))
}
def main(args: Array[String]): Unit = {
case class Teacher(name: String)
case class Publication(name: String)
case class Student(name: String)
case class Grade(score: Int)
val importantTextbook = Publication("Important Textbook")
val anthropologyToday = Publication("Anthropology Today")
val firstNovel = Publication("First Novel")
val mrMurray = Teacher("Mr. Murray")
val msGrizelda = Teacher("Ms. Grizelda")
val sally = Student("Sally")
val biff = Student("Biff")
val jim = Student("Jim")
val seq =
List(
((mrMurray, sally, Grade(10)), importantTextbook),
((mrMurray, sally, Grade(30)), anthropologyToday),
((mrMurray, jim, Grade(5)), importantTextbook),
((mrMurray, biff, Grade(12)), importantTextbook),
((mrMurray, biff, Grade(18)), importantTextbook),
((msGrizelda, jim, Grade(8)), firstNovel),
((msGrizelda, jim, Grade(99)), firstNovel),
((msGrizelda, biff, Grade(8)), firstNovel),
((msGrizelda, biff, Grade(18)), firstNovel)
)
val agg = Agg[Teacher] >>> (Agg[Student, Grade] ++ Agg[Publication])
val result: Map[Teacher, (Map[Student, Seq[Grade]], Seq[Publication])] = agg(seq)
println(
result
.map { case (teacher, (sg, p)) =>
teacher.toString + "\n Students\n" +
sg.mkString("\n").linesIterator.map("\t" + _).mkString("\n") + "\n Publications\n" +
p.mkString("\n").linesIterator.map("\t" + _).mkString("\n")
}
.mkString("\n")
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment