Skip to content

Commit 7f7b57f

Browse files
authored
Add repeat operation and support use case of threading it into zipToShortest (#94)
* Add repeat operation and support use case of threading it into zipToShortest - Allow IterableOnce for ExprHList.zipToShortest - Use Seq for ZipToShortest of IterableOnce * Add documentation and configuration to Expr.Repeat * Use DSL methods to distinguish between repeat methods * Support better DebugArgs for Repeat
1 parent 47407ca commit 7f7b57f

15 files changed

+407
-4
lines changed

core-v1/src/main/scala/algebra/Expr.scala

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,8 @@ object Expr {
338338
opO: OP[W[B]],
339339
): I ~:> W[B]
340340

341+
def visitRepeat[I, O](expr: Repeat[I, O, OP])(implicit opO: OP[IterableOnce[O]]): I ~:> IterableOnce[O]
342+
341343
def visitSelect[I, A, B, O : OP](expr: Select[I, A, B, O, OP]): I ~:> O
342344

343345
def visitSequence[C[+_] : Applicative : SemigroupK : Traverse, I, O](
@@ -516,6 +518,9 @@ object Expr {
516518
opO: OP[W[B]],
517519
): H[I, W[B]] = proxy(underlying.visitOr(expr))
518520

521+
override def visitRepeat[I, O](expr: Repeat[I, O, OP])(implicit opO: OP[IterableOnce[O]]): H[I, IterableOnce[O]] =
522+
proxy(underlying.visitRepeat(expr))
523+
519524
override def visitSelect[I, A, B, O : OP](expr: Select[I, A, B, O, OP]): H[I, O] =
520525
proxy(underlying.visitSelect(expr))
521526

@@ -1039,6 +1044,26 @@ object Expr {
10391044
copy(debugging = debugging)
10401045
}
10411046

1047+
/**
1048+
* Creates an [[IterableOnce]] that emits the result of the input expression forever, or up to a given limit.
1049+
*
1050+
* @param inputExpr the input expression to repeat
1051+
* @param recompute whether to recompute the expression on every iteration or just use the first result
1052+
* @param limit whether to limit the total number of elements produced by the iterable
1053+
*/
1054+
final case class Repeat[-I, +O, OP[_]](
1055+
inputExpr: Expr[I, O, OP],
1056+
recompute: Boolean,
1057+
limit: Option[Int],
1058+
override private[v1] val debugging: Debugging[Nothing, Nothing] = NoDebugging,
1059+
)(implicit
1060+
opO: OP[IterableOnce[O]],
1061+
) extends Expr[I, IterableOnce[O], OP]("repeat") {
1062+
override def visit[G[-_, +_]](v: Visitor[G, OP]): G[I, IterableOnce[O]] = v.visitRepeat(this)
1063+
override private[v1] def withDebugging(debugging: Debugging[Nothing, Nothing]): Repeat[I, O, OP] =
1064+
copy(debugging = debugging)
1065+
}
1066+
10421067
/**
10431068
* Grabs all facts of the given [[FactTypeSet]]'s type and returns them as output.
10441069
*

core-v1/src/main/scala/debug/DebugArgs.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import algebra.{Expr, SizeComparison}
66
import data._
77
import lens.VariantLens
88

9+
import cats.Eval
910
import cats.data.{NonEmptySeq, NonEmptyVector}
1011
import izumi.reflect.Tag
1112
import shapeless.HList
@@ -256,6 +257,12 @@ object DebugArgs {
256257
override type Out = C[B]
257258
}
258259

260+
implicit def debugRepeat[I, O, OP[_]]: Aux[Expr.Repeat[I, O, OP], OP, (I, Eval[O], Option[Int]), IterableOnce[O]] =
261+
new DebugArgs[Expr.Repeat[I, O, OP], OP] {
262+
override type In = (I, Eval[O], Option[Int])
263+
override type Out = IterableOnce[O]
264+
}
265+
259266
implicit def debugSelect[I, A, B, O, OP[_]]: Aux[Expr.Select[I, A, B, O, OP], OP, (I, A, VariantLens[A, B], B), O] =
260267
new DebugArgs[Expr.Select[I, A, B, O, OP], OP] {
261268
override type In = (I, A, VariantLens[A, B], B)

core-v1/src/main/scala/dsl/BuildExprDsl.scala

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,36 @@ You should prefer put your declaration of dependency on definitions close to whe
235235
constType: ConstOutputType[W, A],
236236
): ConstExprBuilder[constType.Out, OP]
237237

238+
final def repeatConstForever[I, O](
239+
expr: I ~:> O,
240+
)(implicit
241+
opO: OP[IterableOnce[O]],
242+
): Expr.Repeat[I, O, OP] =
243+
Expr.Repeat(expr, recompute = false, limit = None)
244+
245+
final def repeatConst[I, O](
246+
n: Int,
247+
expr: I ~:> O,
248+
)(implicit
249+
opO: OP[IterableOnce[O]],
250+
): Expr.Repeat[I, O, OP] =
251+
Expr.Repeat(expr, recompute = false, limit = Some(n))
252+
253+
final def repeatForever[I, O](
254+
expr: I ~:> O,
255+
)(implicit
256+
opO: OP[IterableOnce[O]],
257+
): Expr.Repeat[I, O, OP] =
258+
Expr.Repeat(expr, recompute = true, limit = None)
259+
260+
final def repeat[I, O](
261+
n: Int,
262+
expr: I ~:> O,
263+
)(implicit
264+
opO: OP[IterableOnce[O]],
265+
): Expr.Repeat[I, O, OP] =
266+
Expr.Repeat(expr, recompute = true, limit = Some(n))
267+
238268
// TODO: Is this redundant syntax worth keeping around?
239269
implicit def inSet[I, A](inputExpr: I ~:> W[A]): InSetExprBuilder[I, A]
240270

core-v1/src/main/scala/dsl/DefaultUnwrappedExprHListImplicits.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ trait DefaultUnwrappedExprHListImplicits
88
with DefaultUnwrappedLowPriorityExprHListDslImplicits
99
with DefaultUnwrappedDslImplicitDefinitions {
1010

11+
override implicit final def hlastAlignIterableOnceMapN[H](
12+
implicit
13+
isCons: IsExprHCons.Aux[IterableOnce[H] :: HNil, IterableOnce[H], HNil],
14+
): ZipToShortest.Aux[Seq, IterableOnce[H] :: HNil, OP, H :: HNil] =
15+
defn.hlastAlignIterableOnceMapN
16+
1117
override implicit final def hlastAlignMapN[C[_] : Functor, H](
1218
implicit
1319
isCons: IsExprHCons.Aux[C[H] :: HNil, C[H], HNil],
@@ -18,6 +24,13 @@ trait DefaultUnwrappedExprHListImplicits
1824
zts
1925
}
2026

27+
override implicit final def hconsAlignIterableOnceMapN[H, WT <: HList](
28+
implicit
29+
mt: ZipToShortest[Seq, WT, OP],
30+
isCons: IsExprHCons.Aux[IterableOnce[H] :: WT, IterableOnce[H], WT],
31+
): ZipToShortest.Aux[Seq, IterableOnce[H] :: WT, OP, H :: mt.UL] =
32+
defn.hconsAlignIterableOnceMapN(mt)
33+
2134
override implicit final def hconsAlignMapN[C[_] : Align : FunctorFilter, H, WT <: HList](
2235
implicit
2336
isCons: IsExprHCons.Aux[C[H] :: WT, C[H], WT],

core-v1/src/main/scala/dsl/DslImplicitDefinitions.scala

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import com.rallyhealth.vapors.v1.algebra.Expr
88
import com.rallyhealth.vapors.v1.lens.DataPath
99
import shapeless.{::, HList, HNil}
1010

11-
import scala.collection.Factory
11+
import scala.collection.{Factory, IterableOnce}
1212

1313
final class DslImplicitDefinitions[W[+_] : Functor : Semigroupal, OP[_]](
1414
implicit
@@ -107,6 +107,31 @@ final class DslImplicitDefinitions[W[+_] : Functor : Semigroupal, OP[_]](
107107
): W[O] = wrapElementW.wrapSelected(wrapped, path, value)
108108
}
109109

110+
def hlastAlignIterableOnceMapN[C[a] <: IterableOnce[a], H](
111+
implicit
112+
isCons: IsExprHCons.Aux[IterableOnce[W[H]] :: HNil, IterableOnce[W[H]], HNil],
113+
factory: Factory[W[H :: HNil], C[W[H :: HNil]]],
114+
): ZipToShortest.Aux[Lambda[a => C[W[a]]], IterableOnce[W[H]] :: HNil, OP, H :: HNil] =
115+
new ZipToShortest[Lambda[a => C[W[a]]], IterableOnce[W[H]] :: HNil, OP] {
116+
override type UL = H :: HNil
117+
override def zipToShortestWith[G[-_, +_] : Arrow, I](
118+
xhl: ExprHList[I, IterableOnce[W[H]] :: HNil, OP],
119+
v: Expr.Visitor[G, OP],
120+
): G[I, C[W[H :: HNil]]] = {
121+
val G = Arrow[G]
122+
val gcwh = xhl.head.visit(v)
123+
gcwh >>> G.lift { cwh =>
124+
factory.fromSpecific {
125+
cwh.iterator.map { wh =>
126+
wh.map { h =>
127+
h :: HNil
128+
}
129+
}
130+
}
131+
}
132+
}
133+
}
134+
110135
def hlastAlignMapN[C[_] : Functor, H](
111136
implicit
112137
isCons: IsExprHCons.Aux[C[W[H]] :: HNil, C[W[H]], HNil],
@@ -129,6 +154,40 @@ final class DslImplicitDefinitions[W[+_] : Functor : Semigroupal, OP[_]](
129154
}
130155
}
131156

157+
def hconsAlignIterableOnceMapN[C[a] <: IterableOnce[a], H, WT <: HList](
158+
mt: ZipToShortest[Lambda[a => C[W[a]]], WT, OP],
159+
)(implicit
160+
isCons: IsExprHCons.Aux[IterableOnce[W[H]] :: WT, IterableOnce[W[H]], WT],
161+
factory: Factory[W[H :: mt.UL], C[W[H :: mt.UL]]],
162+
): ZipToShortest.Aux[Lambda[a => C[W[a]]], IterableOnce[W[H]] :: WT, OP, H :: mt.UL] =
163+
new ZipToShortest[Lambda[a => C[W[a]]], IterableOnce[W[H]] :: WT, OP] {
164+
override type UL = H :: mt.UL
165+
override def zipToShortestWith[G[-_, +_] : Arrow, I](
166+
xhl: ExprHList[I, IterableOnce[W[H]] :: WT, OP],
167+
v: Expr.Visitor[G, OP],
168+
): G[I, C[W[H :: mt.UL]]] = {
169+
val G = Arrow[G]
170+
val gcwh = xhl.head.visit(v)
171+
val gcwt: G[I, C[W[mt.UL]]] = mt.zipToShortestWith(xhl.tail, v)
172+
(gcwh &&& gcwt) >>> G.lift {
173+
case (cwh, cwt) =>
174+
val lefts = cwh.iterator.map(Some(_))
175+
val rights = cwt.iterator.map(Some(_))
176+
factory.fromSpecific {
177+
lefts
178+
.zip(rights)
179+
.map {
180+
case (Some(wh), Some(wt)) => Some((wh, wt).mapN { case (h, t) => h :: t })
181+
case _ => None
182+
}
183+
.collect {
184+
case Some(hl) => hl
185+
}
186+
}
187+
}
188+
}
189+
}
190+
132191
def hconsAlignMapN[C[_] : Align : FunctorFilter, H, WT <: HList](
133192
mt: ZipToShortest[Lambda[a => C[W[a]]], WT, OP],
134193
)(implicit

core-v1/src/main/scala/dsl/DslTypes.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ trait DslTypes extends Any {
105105
/**
106106
* An [[ExprHList]] with a fixed [[OP]] type.
107107
*/
108-
final type XHL[-I, L <: HList] = ExprHList[I, L, OP]
108+
final type XHL[-I, +L <: HList] = ExprHList[I, L, OP]
109109

110110
/**
111111
* An [[ExprHNil]] with a fixed [[OP]] type.

core-v1/src/main/scala/dsl/ExprHListDslImplicits.scala

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ package dsl
55
import cats.{Align, Functor, FunctorFilter}
66
import shapeless.{::, HList, HNil}
77

8+
import scala.collection.Factory
9+
810
/**
911
* A marker trait for determining which set of implicits to inherit.
1012
*
@@ -27,11 +29,22 @@ sealed trait ExprHListDslImplicits
2729
trait WrappedExprHListDslImplicits extends ExprHListDslImplicits {
2830
self: DslTypes with WrappedLowPriorityExprHListDslImplicits =>
2931

32+
implicit def hlastAlignIterableOnceMapN[H](
33+
implicit
34+
isCons: IsExprHCons.Aux[IterableOnce[W[H]] :: HNil, IterableOnce[W[H]], HNil],
35+
): ZipToShortest.Aux[Lambda[a => Seq[W[a]]], IterableOnce[W[H]] :: HNil, OP, H :: HNil]
36+
3037
implicit def hlastAlignMapN[C[_] : Functor, H](
3138
implicit
3239
isCons: IsExprHCons.Aux[C[W[H]] :: HNil, C[W[H]], HNil],
3340
): ZipToShortest.Aux[Lambda[a => C[W[a]]], C[W[H]] :: HNil, OP, H :: HNil]
3441

42+
implicit def hconsAlignIterableOnceMapN[H, WT <: HList](
43+
implicit
44+
isCons: IsExprHCons.Aux[IterableOnce[W[H]] :: WT, IterableOnce[W[H]], WT],
45+
mt: ZipToShortest[Lambda[a => Seq[W[a]]], WT, OP],
46+
): ZipToShortest.Aux[Lambda[a => Seq[W[a]]], IterableOnce[W[H]] :: WT, OP, H :: mt.UL]
47+
3548
implicit def hconsAlignMapN[C[_] : Align : FunctorFilter, H, WT <: HList](
3649
implicit
3750
isCons: IsExprHCons.Aux[C[W[H]] :: WT, C[W[H]], WT],
@@ -58,11 +71,22 @@ trait WrappedLowPriorityExprHListDslImplicits {
5871
trait UnwrappedExprHListDslImplicits extends ExprHListDslImplicits {
5972
self: DslTypes with UnwrappedLowPriorityExprHListDslImplicits =>
6073

74+
implicit def hlastAlignIterableOnceMapN[H](
75+
implicit
76+
isCons: IsExprHCons.Aux[IterableOnce[H] :: HNil, IterableOnce[H], HNil],
77+
): ZipToShortest.Aux[Seq, IterableOnce[H] :: HNil, OP, H :: HNil]
78+
6179
implicit def hlastAlignMapN[C[_] : Functor, H](
6280
implicit
6381
isCons: IsExprHCons.Aux[C[H] :: HNil, C[H], HNil],
6482
): ZipToShortest.Aux[C, C[H] :: HNil, OP, H :: HNil]
6583

84+
implicit def hconsAlignIterableOnceMapN[H, WT <: HList](
85+
implicit
86+
mt: ZipToShortest[Seq, WT, OP],
87+
isCons: IsExprHCons.Aux[IterableOnce[H] :: WT, IterableOnce[H], WT],
88+
): ZipToShortest.Aux[Seq, IterableOnce[H] :: WT, OP, H :: mt.UL]
89+
6690
implicit def hconsAlignMapN[C[_] : Align : FunctorFilter, H, WT <: HList](
6791
implicit
6892
isCons: IsExprHCons.Aux[C[H] :: WT, C[H], WT],

core-v1/src/main/scala/dsl/JustifiedBuildExprDsl.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,25 @@ sealed trait JustifiedExprHListDslImplicits
109109
with JustifiedLowPriorityExprHListDslImplicits
110110
with DefinedJustifiedDslImplicitDefinitions {
111111

112+
override implicit def hlastAlignIterableOnceMapN[H](
113+
implicit
114+
isCons: IsExprHCons.Aux[IterableOnce[Justified[H]] :: HNil, IterableOnce[Justified[H]], HNil],
115+
): ZipToShortest.Aux[Lambda[a => Seq[Justified[a]]], IterableOnce[Justified[H]] :: HNil, OP, H :: HNil] =
116+
defn.hlastAlignIterableOnceMapN
117+
112118
override implicit def hlastAlignMapN[C[_] : Functor, H](
113119
implicit
114120
isCons: IsExprHCons.Aux[C[Justified[H]] :: HNil, C[Justified[H]], HNil],
115121
): ZipToShortest.Aux[Lambda[a => C[Justified[a]]], C[Justified[H]] :: HNil, OP, H :: HNil] =
116122
defn.hlastAlignMapN
117123

124+
override implicit def hconsAlignIterableOnceMapN[H, WT <: HList](
125+
implicit
126+
isCons: IsExprHCons.Aux[IterableOnce[Justified[H]] :: WT, IterableOnce[Justified[H]], WT],
127+
mt: ZipToShortest[Lambda[a => Seq[Justified[a]]], WT, OP],
128+
): ZipToShortest.Aux[Lambda[a => Seq[Justified[a]]], IterableOnce[Justified[H]] :: WT, OP, H :: mt.UL] =
129+
defn.hconsAlignIterableOnceMapN(mt)
130+
118131
override implicit def hconsAlignMapN[C[_] : Align : FunctorFilter, H, WT <: HList](
119132
implicit
120133
isCons: IsExprHCons.Aux[C[Justified[H]] :: WT, C[Justified[H]], WT],

core-v1/src/main/scala/dsl/ZipToShortest.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import shapeless.HList
1717
*
1818
* (Any ~:> Seq[String]) :: (I ~:> Seq[Int]) => Any ~:> Seq[String :: Int :: HNil]
1919
*/
20-
trait ZipToShortest[W[_], WL <: HList, OP[_]] {
20+
trait ZipToShortest[+W[_], WL <: HList, OP[_]] {
2121
type UL <: HList
2222

2323
def zipToShortestWith[G[-_, +_] : Arrow, I](
@@ -30,5 +30,5 @@ trait ZipToShortest[W[_], WL <: HList, OP[_]] {
3030
* Implementations live in the subclasses of [[ExprHListDslImplicits]].
3131
*/
3232
object ZipToShortest {
33-
type Aux[W[_], WL <: HList, OP[_], UL0] = ZipToShortest[W, WL, OP] { type UL = UL0 }
33+
type Aux[+W[_], WL <: HList, OP[_], UL0] = ZipToShortest[W, WL, OP] { type UL = UL0 }
3434
}

core-v1/src/main/scala/engine/ImmutableCachingEngine.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,20 @@ object ImmutableCachingEngine {
405405
debugging(expr).invokeAndReturn(state((i, inputs), result))
406406
}
407407

408+
override def visitRepeat[I, O](
409+
expr: Expr.Repeat[I, O, OP],
410+
)(implicit
411+
opO: OP[IterableOnce[O]],
412+
): I => CachedResult[IterableOnce[O]] = { i =>
413+
val always = Eval.always {
414+
expr.inputExpr.visit(this)(i).value
415+
}
416+
val eval = if (expr.recompute) always else always.memoize
417+
val unlimited = Iterator.continually(eval.value)
418+
val iterable = expr.limit.fold(unlimited)(unlimited.take)
419+
debugging(expr).invokeAndReturn(state((i, eval, expr.limit), cached(iterable)))
420+
}
421+
408422
override def visitSelect[I, A, B, O : OP](expr: Expr.Select[I, A, B, O, OP]): I => CachedResult[O] =
409423
memoize(expr, _) { i =>
410424
val inputResult = expr.inputExpr.visit(this)(i)

0 commit comments

Comments
 (0)