Created
May 3, 2021 16:32
-
-
Save kitlangton/185a59e0555fe966f93c2e371f429f48 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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