Skip to content

Commit 436f39f

Browse files
committed
used tree structure instead of linear structure for merging
1 parent 93d87a5 commit 436f39f

File tree

6 files changed

+108
-62
lines changed

6 files changed

+108
-62
lines changed

modules/core/src/main/scala/hxl/Hxl.scala

Lines changed: 58 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ import cats.implicits._
2626
* `andThen` exists as an alternative to `flatMap`, much like `Validated`.
2727
*/
2828
sealed trait Hxl[F[_], A] {
29-
def andThen[B](f: A => Hxl[F, B])(implicit F: Functor[F]): Hxl[F, B]
29+
def andThen[B](f: A => Hxl[F, B]): Hxl[F, B] =
30+
Hxl.AndThen(this, f)
3031

3132
def flatMapF[B](f: A => F[B])(implicit F: Functor[F]): Hxl[F, B] =
3233
andThen(a => Hxl.liftF(f(a)))
@@ -49,9 +50,9 @@ sealed trait Hxl[F[_], A] {
4950
def optimized(implicit F: Monad[F]): Either[Hxl[F, A], F[Hxl[F, A]]]
5051

5152
// Aligns this hxl, this is a hint for future composition that
52-
def align: Hxl[F, A] = Hxl.align(this)
53+
// def align: Hxl[F, A] = Hxl.align(this)
5354

54-
def alignM: HxlM[F, A] = align.monadic
55+
// def alignM: HxlM[F, A] = align.monadic
5556
}
5657

5758
object Hxl {
@@ -61,50 +62,46 @@ object Hxl {
6162

6263
// Almost a free monad
6364
final case class Done[F[_], A](value: A) extends Hxl[F, A] {
64-
def andThen[B](f: A => Hxl[F, B])(implicit F: Functor[F]): Hxl[F, B] = f(value)
6565
def mapK[G[_]: Functor](fk: F ~> G): Hxl[G, A] = Done(value)
6666
def optimized(implicit F: Monad[F]) = Left(this)
6767
}
68-
final case class Bind[F[_], A, B](
69-
requests: Requests[F, A],
70-
f: A => Hxl[F, B]
71-
) extends Hxl[F, B] {
72-
def andThen[C](f2: B => Hxl[F, C])(implicit F: Functor[F]): Hxl[F, C] =
73-
Bind(requests, f.andThen(_.andThen(f2)))
74-
def mapK[G[_]: Functor](fk: F ~> G): Hxl[G, B] =
75-
Bind(requests.mapK(fk), f.andThen(_.mapK(fk)))
76-
def optimized(implicit F: Monad[F]) = Left(Bind(requests.optimized, f))
68+
final case class Run[F[_], A](requests: Requests[F, A]) extends Hxl[F, A] {
69+
def mapK[G[_]: Functor](fk: F ~> G): Hxl[G, A] = Run(requests.mapK(fk))
70+
def optimized(implicit F: Monad[F]) = Left(Run(requests.optimized))
7771
}
7872
final case class LiftF[F[_], A](unFetch: F[Hxl[F, A]]) extends Hxl[F, A] {
79-
def andThen[B](f: A => Hxl[F, B])(implicit F: Functor[F]): Hxl[F, B] =
80-
LiftF(unFetch.map(_.andThen(f)))
8173
def mapK[G[_]: Functor](fk: F ~> G): Hxl[G, A] =
8274
LiftF(fk(unFetch).map(_.mapK(fk)))
8375
def optimized(implicit F: Monad[F]) =
8476
unFetch.flatMap(_.optimized.leftMap(F.pure(_)).merge).asRight
8577
}
86-
final case class Align[F[_], A](fa: Hxl[F, A]) extends Hxl[F, A] {
87-
def andThen[B](f: A => Hxl[F, B])(implicit F: Functor[F]): Hxl[F, B] =
88-
Align(fa.andThen(f))
89-
def mapK[G[_]: Functor](fk: F ~> G): Hxl[G, A] =
90-
Align(fa.mapK(fk))
91-
def optimized(implicit F: Monad[F]) =
92-
Left(this)
93-
78+
// final case class Align[F[_], A](fa: Hxl[F, A]) extends Hxl[F, A] {
79+
// def mapK[G[_]: Functor](fk: F ~> G): Hxl[G, A] =
80+
// Align(fa.mapK(fk))
81+
// def optimized(implicit F: Monad[F]) =
82+
// Left(this)
83+
// }
84+
final case class AndThen[F[_], A, B](fa: Hxl[F, A], fb: A => Hxl[F, B]) extends Hxl[F, B] {
85+
override def mapK[G[_]: Functor](fk: F ~> G): Hxl[G, B] = ???
86+
87+
override def optimized(implicit F: Monad[F]): Either[Hxl[F, B], F[Hxl[F, B]]] = ???
9488
}
9589

9690
def parallelRunner[F[_]](implicit F: Parallel[F]): Compiler[F, F] = new Compiler[F, F] {
9791
implicit val M: Monad[F] = F.monad
9892
override def apply[A](fa: Hxl[F, A]): F[Either[Hxl[F, A], A]] =
99-
fa match {
100-
case Done(a) => M.pure(Right(a))
101-
case Align(fa) => M.pure(Left(fa))
102-
case LiftF(unFetch) => unFetch.map(Left(_))
103-
case bind: Bind[F, a, b] =>
104-
Requests
105-
.run[F, a](bind.requests)
106-
.map(bind.f)
107-
.map(_.asLeft)
93+
M.unit >> {
94+
fa match {
95+
case Done(a) => M.pure(Right(a))
96+
// case Align(fa) => M.pure(Left(fa))
97+
case LiftF(unFetch) => unFetch.map(Left(_))
98+
case at: AndThen[F, a, A] =>
99+
apply(at.fa).map {
100+
case Left(h) => Left(h.andThen(at.fb))
101+
case Right(a) => Left(at.fb(a))
102+
}
103+
case bind: Run[F, A] => Requests.run[F, A](bind.requests).map(Right(_))
104+
}
108105
}
109106
}
110107

@@ -126,18 +123,18 @@ object Hxl {
126123

127124
def apply[F[_], K, V](k: K, source: DataSource[F, K, V]): Hxl[F, Option[V]] =
128125
source.optimization match {
129-
case Some(ev) => Bind[F, Unit, Option[V]](Requests.discard(source, k), x => Done(Some(ev(x))))
130-
case None => Bind[F, Option[V], Option[V]](Requests.lift(source, k), Done(_))
126+
case Some(ev) => Run[F, Option[V]](Requests.empty(source, k, Some(ev(()))))
127+
case None => Run[F, Option[V]](Requests.lift(source, k))
131128
}
132129

133130
def discard[F[_], K, V](k: K, source: DataSource[F, K, V]): Hxl[F, Unit] =
134-
Bind[F, Unit, Unit](Requests.discard(source, k), Done(_))
131+
Run[F, Unit](Requests.discard(source, k))
135132

136133
def force[F[_], K: Show, V](k: K, source: DataSource[F, K, V])(implicit F: ApplicativeThrow[F]): Hxl[F, V] =
137134
apply[F, K, V](k, source)
138135
.flatMapF(F.fromOption(_, new RuntimeException(show"Key $k not found")))
139136

140-
def align[F[_], A](fa: Hxl[F, A]): Hxl[F, A] = Align(fa)
137+
// def align[F[_], A](fa: Hxl[F, A]): Hxl[F, A] = Align(fa)
141138

142139
// Almost the same signature as parallel, except we don't have a monad, but a functor instead
143140
// This is because of the free monad structure of Hxl, we can defer Monad evidence until we need to run
@@ -149,27 +146,37 @@ object Hxl {
149146
type H[A] = Hxl[F, A]
150147
new Applicative[H] {
151148
def pure[A](x: A): H[A] = Done(x)
152-
def ap[A, B](ff: H[A => B])(fa: H[A]): H[B] =
149+
def ap[A, B](ff: H[A => B])(fa: H[A]): H[B] = {
153150
(ff, fa) match {
154151
case (LiftF(fa), LiftF(fb)) => LiftF(gf((fg(fa), fg(fb)).mapN(_ <*> _)))
155152
case (LiftF(fa), h) => LiftF(fa.map(_ <*> h))
156153
case (h, LiftF(fa)) => LiftF(fa.map(h <*> _))
157-
158-
case (Align(fa), Align(fb)) => Align(fa.ap(fb))
159-
// Synthetically align
160-
case (Align(fa), Done(fb)) => Align(fa.map(_(fb)))
161-
case (Done(fa), Align(fb)) => Align(fb.map(fa(_)))
162-
// Missing align on right side, defer fa until later
163-
case (Align(fa), b2: Bind[F, a2, A]) => Bind[F, a2, B](b2.requests, a2 => fa.ap(b2.f(a2)))
164-
case (b1: Bind[F, a1, A => B], Align(fb)) => Bind[F, a1, B](b1.requests, a1 => b1.f(a1).ap(fb))
154+
case (at: AndThen[F, a1, A => B], ab: AndThen[F, a2, A]) =>
155+
AndThen[F, (a1, a2), B](
156+
self.tuple2(at.fa, ab.fa),
157+
{ case (a1, a2) => at.fb(a1).ap(ab.fb(a2)) }
158+
)
159+
160+
// flatMap <*> batch -> move batch into left side of flatMap
161+
// to be optimistic. The choice is arbitrary.
162+
case (at: AndThen[F, a, A => B], fb) =>
163+
AndThen[F, (a, A), B](
164+
(at.fa, fb).tupled,
165+
{ case (a, a2) => self.ap(at.fb(a))(Done(a2)) }
166+
)
167+
case (fa, ab: AndThen[F, a, A]) =>
168+
AndThen[F, (A => B, a), B](
169+
(fa, ab.fa).tupled,
170+
{ case (f, a2) => self.ap(Done(f))(ab.fb(a2)) }
171+
)
165172

166173
case (Done(f), Done(a)) => Done(f(a))
167-
case (b1: Bind[F, a1, A => B], b2: Bind[F, a2, A]) =>
168-
val comb = (b1.requests, b2.requests).tupled
169-
Bind[F, (a1, a2), B](comb, { case (a1, a2) => b1.f(a1) <*> b2.f(a2) })
170-
case (b: Bind[F, a, A => B], Done(a)) => Bind[F, a, B](b.requests, b.f(_).map(_(a)))
171-
case (Done(g), b: Bind[F, a, A]) => Bind[F, a, B](b.requests, b.f(_).map(g))
174+
case (r1: Run[F, A => B], r2: Run[F, A]) =>
175+
self.map(Run((r1.requests, r2.requests).tupled)) { case (f, a) => f(a) }
176+
case (r: Run[F, A => B], Done(a)) => Run(r.requests.map(_(a)))
177+
case (Done(f), r: Run[F, A]) => Run(r.requests.map(f(_)))
172178
}
179+
}
173180
}
174181
}
175182

@@ -214,7 +221,7 @@ object HxlM {
214221

215222
// Monad for HxlM
216223
// HxlM can implement any covariant typeclass (but not contravariant ones since `F ~> HxlM` but not `HxlM ~> F`).
217-
implicit def monadForHxlM[F[_]: Functor]: Monad[HxlM[F, *]] = {
224+
implicit def monadForHxlM[F[_]]: Monad[HxlM[F, *]] = {
218225
type G[A] = HxlM[F, A]
219226
new Monad[G] {
220227
override def pure[A](x: A): G[A] = HxlM(Hxl.Done(x))

modules/core/src/main/scala/hxl/Requests.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,11 @@ object Requests {
9494
Requests(View.empty, FreeApplicative.lift(a), None)
9595
}
9696

97+
def empty[F[_], A, B](source: DataSource[F, A, ?], key: A, as: B): Requests[F, B] =
98+
Requests(View(Discarded(source, key)), FreeApplicative.pure(as), None)
99+
97100
def discard[F[_], A](source: DataSource[F, A, ?], key: A): Requests[F, Unit] =
98-
Requests(View(Discarded(source, key)), FreeApplicative.pure(()), None)
101+
empty(source, key, ())
99102

100103
final case class DSKey0(value: Any) extends AnyRef
101104
final case class ValueKey(value: Any) extends AnyRef

modules/core/src/test/scala/hxl/HxlEvaluationTest.scala

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import munit.FunSuite
2020
import cats.data._
2121
import cats._
2222
import cats.implicits._
23+
import scala.collection.concurrent.TrieMap
2324

2425
final case class FailingKey(key: String) extends DSKey[String, String]
2526

@@ -159,4 +160,45 @@ class HxlEvaluationTest extends FunSuite {
159160
val end = System.currentTimeMillis()
160161
println("Benchmark result: " + (end - start) + "ms")
161162
}
163+
164+
case class VarKey(str: String) extends DSKey[String, String]
165+
test("alignment".only) {
166+
val m = TrieMap.empty[String, Int]
167+
def ds(s: String) = DataSource.from(VarKey(s)) { ks =>
168+
m.updateWith(s) {
169+
case None => Some(1)
170+
case Some(i) => Some(i + 1)
171+
}
172+
ks.toList.map(s => s -> s).toMap.pure[Id]
173+
}
174+
val suf = Hxl("b", ds("b"))
175+
176+
def f(
177+
x: Option[String],
178+
align: Boolean
179+
): Hxl[Id, Unit] = {
180+
def doAlign[A](fa: Hxl[Id, A]): Hxl[Id, A] =
181+
if (align) fa
182+
else fa
183+
184+
for {
185+
_ <- doAlign {
186+
x.traverse(str => Hxl(str, ds("a")))
187+
.map(_.flatten)
188+
.andThen(_.traverse { str2 =>
189+
doAlign(Hxl(str2, ds("a2")))
190+
})
191+
}.monadic
192+
// _ <- doAlign(Hxl("a", ds("a"))).monadic
193+
_ <- doAlign(suf).monadic
194+
} yield ()
195+
}.hxl
196+
197+
val p1 = f(None, true)
198+
val p2 = f("a".some, true)
199+
val p = p1 *> p2
200+
Hxl.runSequential(p)
201+
assertEquals(m("a"), 1)
202+
assertEquals(m("b"), 1)
203+
}
162204
}

modules/natchez/src/main/scala/hxl/natchez/package.scala

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,18 +46,12 @@ package object `natchez` {
4646
new Compiler[F, Effect] {
4747
def apply[A](fa: Hxl[F, A]): Hxl.Target[F, Effect, A] =
4848
fa match {
49-
case Hxl.LiftF(unFetch) =>
50-
StateT.liftF {
51-
Trace[G].span("hxl.fetch") {
52-
compiler(Hxl.LiftF(unFetch))
53-
}
54-
}
55-
case bind: Hxl.Bind[F, a, b] =>
49+
case bind: Hxl.Run[F, A] =>
5650
StateT { (round: Int) =>
5751
Trace[G]
5852
.span("hxl.bind") {
5953
Trace[G].put("round" -> round) *> compiler {
60-
Hxl.Bind(traceRequests(bind.requests), bind.f)
54+
Hxl.Run(traceRequests(bind.requests))
6155
}
6256
}
6357
.map(round + 1 -> _)

modules/natchez/src/test/scala/hxl/natchez/TracingTest.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class TracingTest extends CatsEffectSuite {
4747
n.intValue()
4848
case _ => 0
4949
}
50-
assertEquals(x, 3)
50+
assertEquals(x, 1)
5151
}
5252
}
5353
}

project/metals.sbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33

44
// This file enables sbt-bloop to create bloop config files.
55

6-
addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "2.0.5")
6+
addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "2.0.13")
77

88
// format: on

0 commit comments

Comments
 (0)