Skip to content

Commit

Permalink
Add repeat operation and support use case of threading it into zipToS…
Browse files Browse the repository at this point in the history
…hortest (#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
  • Loading branch information
jeffmay authored Feb 10, 2022
1 parent 47407ca commit 7f7b57f
Show file tree
Hide file tree
Showing 15 changed files with 407 additions and 4 deletions.
25 changes: 25 additions & 0 deletions core-v1/src/main/scala/algebra/Expr.scala
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,8 @@ object Expr {
opO: OP[W[B]],
): I ~:> W[B]

def visitRepeat[I, O](expr: Repeat[I, O, OP])(implicit opO: OP[IterableOnce[O]]): I ~:> IterableOnce[O]

def visitSelect[I, A, B, O : OP](expr: Select[I, A, B, O, OP]): I ~:> O

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

override def visitRepeat[I, O](expr: Repeat[I, O, OP])(implicit opO: OP[IterableOnce[O]]): H[I, IterableOnce[O]] =
proxy(underlying.visitRepeat(expr))

override def visitSelect[I, A, B, O : OP](expr: Select[I, A, B, O, OP]): H[I, O] =
proxy(underlying.visitSelect(expr))

Expand Down Expand Up @@ -1039,6 +1044,26 @@ object Expr {
copy(debugging = debugging)
}

/**
* Creates an [[IterableOnce]] that emits the result of the input expression forever, or up to a given limit.
*
* @param inputExpr the input expression to repeat
* @param recompute whether to recompute the expression on every iteration or just use the first result
* @param limit whether to limit the total number of elements produced by the iterable
*/
final case class Repeat[-I, +O, OP[_]](
inputExpr: Expr[I, O, OP],
recompute: Boolean,
limit: Option[Int],
override private[v1] val debugging: Debugging[Nothing, Nothing] = NoDebugging,
)(implicit
opO: OP[IterableOnce[O]],
) extends Expr[I, IterableOnce[O], OP]("repeat") {
override def visit[G[-_, +_]](v: Visitor[G, OP]): G[I, IterableOnce[O]] = v.visitRepeat(this)
override private[v1] def withDebugging(debugging: Debugging[Nothing, Nothing]): Repeat[I, O, OP] =
copy(debugging = debugging)
}

/**
* Grabs all facts of the given [[FactTypeSet]]'s type and returns them as output.
*
Expand Down
7 changes: 7 additions & 0 deletions core-v1/src/main/scala/debug/DebugArgs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import algebra.{Expr, SizeComparison}
import data._
import lens.VariantLens

import cats.Eval
import cats.data.{NonEmptySeq, NonEmptyVector}
import izumi.reflect.Tag
import shapeless.HList
Expand Down Expand Up @@ -256,6 +257,12 @@ object DebugArgs {
override type Out = C[B]
}

implicit def debugRepeat[I, O, OP[_]]: Aux[Expr.Repeat[I, O, OP], OP, (I, Eval[O], Option[Int]), IterableOnce[O]] =
new DebugArgs[Expr.Repeat[I, O, OP], OP] {
override type In = (I, Eval[O], Option[Int])
override type Out = IterableOnce[O]
}

implicit def debugSelect[I, A, B, O, OP[_]]: Aux[Expr.Select[I, A, B, O, OP], OP, (I, A, VariantLens[A, B], B), O] =
new DebugArgs[Expr.Select[I, A, B, O, OP], OP] {
override type In = (I, A, VariantLens[A, B], B)
Expand Down
30 changes: 30 additions & 0 deletions core-v1/src/main/scala/dsl/BuildExprDsl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,36 @@ You should prefer put your declaration of dependency on definitions close to whe
constType: ConstOutputType[W, A],
): ConstExprBuilder[constType.Out, OP]

final def repeatConstForever[I, O](
expr: I ~:> O,
)(implicit
opO: OP[IterableOnce[O]],
): Expr.Repeat[I, O, OP] =
Expr.Repeat(expr, recompute = false, limit = None)

final def repeatConst[I, O](
n: Int,
expr: I ~:> O,
)(implicit
opO: OP[IterableOnce[O]],
): Expr.Repeat[I, O, OP] =
Expr.Repeat(expr, recompute = false, limit = Some(n))

final def repeatForever[I, O](
expr: I ~:> O,
)(implicit
opO: OP[IterableOnce[O]],
): Expr.Repeat[I, O, OP] =
Expr.Repeat(expr, recompute = true, limit = None)

final def repeat[I, O](
n: Int,
expr: I ~:> O,
)(implicit
opO: OP[IterableOnce[O]],
): Expr.Repeat[I, O, OP] =
Expr.Repeat(expr, recompute = true, limit = Some(n))

// TODO: Is this redundant syntax worth keeping around?
implicit def inSet[I, A](inputExpr: I ~:> W[A]): InSetExprBuilder[I, A]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ trait DefaultUnwrappedExprHListImplicits
with DefaultUnwrappedLowPriorityExprHListDslImplicits
with DefaultUnwrappedDslImplicitDefinitions {

override implicit final def hlastAlignIterableOnceMapN[H](
implicit
isCons: IsExprHCons.Aux[IterableOnce[H] :: HNil, IterableOnce[H], HNil],
): ZipToShortest.Aux[Seq, IterableOnce[H] :: HNil, OP, H :: HNil] =
defn.hlastAlignIterableOnceMapN

override implicit final def hlastAlignMapN[C[_] : Functor, H](
implicit
isCons: IsExprHCons.Aux[C[H] :: HNil, C[H], HNil],
Expand All @@ -18,6 +24,13 @@ trait DefaultUnwrappedExprHListImplicits
zts
}

override implicit final def hconsAlignIterableOnceMapN[H, WT <: HList](
implicit
mt: ZipToShortest[Seq, WT, OP],
isCons: IsExprHCons.Aux[IterableOnce[H] :: WT, IterableOnce[H], WT],
): ZipToShortest.Aux[Seq, IterableOnce[H] :: WT, OP, H :: mt.UL] =
defn.hconsAlignIterableOnceMapN(mt)

override implicit final def hconsAlignMapN[C[_] : Align : FunctorFilter, H, WT <: HList](
implicit
isCons: IsExprHCons.Aux[C[H] :: WT, C[H], WT],
Expand Down
61 changes: 60 additions & 1 deletion core-v1/src/main/scala/dsl/DslImplicitDefinitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import com.rallyhealth.vapors.v1.algebra.Expr
import com.rallyhealth.vapors.v1.lens.DataPath
import shapeless.{::, HList, HNil}

import scala.collection.Factory
import scala.collection.{Factory, IterableOnce}

final class DslImplicitDefinitions[W[+_] : Functor : Semigroupal, OP[_]](
implicit
Expand Down Expand Up @@ -107,6 +107,31 @@ final class DslImplicitDefinitions[W[+_] : Functor : Semigroupal, OP[_]](
): W[O] = wrapElementW.wrapSelected(wrapped, path, value)
}

def hlastAlignIterableOnceMapN[C[a] <: IterableOnce[a], H](
implicit
isCons: IsExprHCons.Aux[IterableOnce[W[H]] :: HNil, IterableOnce[W[H]], HNil],
factory: Factory[W[H :: HNil], C[W[H :: HNil]]],
): ZipToShortest.Aux[Lambda[a => C[W[a]]], IterableOnce[W[H]] :: HNil, OP, H :: HNil] =
new ZipToShortest[Lambda[a => C[W[a]]], IterableOnce[W[H]] :: HNil, OP] {
override type UL = H :: HNil
override def zipToShortestWith[G[-_, +_] : Arrow, I](
xhl: ExprHList[I, IterableOnce[W[H]] :: HNil, OP],
v: Expr.Visitor[G, OP],
): G[I, C[W[H :: HNil]]] = {
val G = Arrow[G]
val gcwh = xhl.head.visit(v)
gcwh >>> G.lift { cwh =>
factory.fromSpecific {
cwh.iterator.map { wh =>
wh.map { h =>
h :: HNil
}
}
}
}
}
}

def hlastAlignMapN[C[_] : Functor, H](
implicit
isCons: IsExprHCons.Aux[C[W[H]] :: HNil, C[W[H]], HNil],
Expand All @@ -129,6 +154,40 @@ final class DslImplicitDefinitions[W[+_] : Functor : Semigroupal, OP[_]](
}
}

def hconsAlignIterableOnceMapN[C[a] <: IterableOnce[a], H, WT <: HList](
mt: ZipToShortest[Lambda[a => C[W[a]]], WT, OP],
)(implicit
isCons: IsExprHCons.Aux[IterableOnce[W[H]] :: WT, IterableOnce[W[H]], WT],
factory: Factory[W[H :: mt.UL], C[W[H :: mt.UL]]],
): ZipToShortest.Aux[Lambda[a => C[W[a]]], IterableOnce[W[H]] :: WT, OP, H :: mt.UL] =
new ZipToShortest[Lambda[a => C[W[a]]], IterableOnce[W[H]] :: WT, OP] {
override type UL = H :: mt.UL
override def zipToShortestWith[G[-_, +_] : Arrow, I](
xhl: ExprHList[I, IterableOnce[W[H]] :: WT, OP],
v: Expr.Visitor[G, OP],
): G[I, C[W[H :: mt.UL]]] = {
val G = Arrow[G]
val gcwh = xhl.head.visit(v)
val gcwt: G[I, C[W[mt.UL]]] = mt.zipToShortestWith(xhl.tail, v)
(gcwh &&& gcwt) >>> G.lift {
case (cwh, cwt) =>
val lefts = cwh.iterator.map(Some(_))
val rights = cwt.iterator.map(Some(_))
factory.fromSpecific {
lefts
.zip(rights)
.map {
case (Some(wh), Some(wt)) => Some((wh, wt).mapN { case (h, t) => h :: t })
case _ => None
}
.collect {
case Some(hl) => hl
}
}
}
}
}

def hconsAlignMapN[C[_] : Align : FunctorFilter, H, WT <: HList](
mt: ZipToShortest[Lambda[a => C[W[a]]], WT, OP],
)(implicit
Expand Down
2 changes: 1 addition & 1 deletion core-v1/src/main/scala/dsl/DslTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ trait DslTypes extends Any {
/**
* An [[ExprHList]] with a fixed [[OP]] type.
*/
final type XHL[-I, L <: HList] = ExprHList[I, L, OP]
final type XHL[-I, +L <: HList] = ExprHList[I, L, OP]

/**
* An [[ExprHNil]] with a fixed [[OP]] type.
Expand Down
24 changes: 24 additions & 0 deletions core-v1/src/main/scala/dsl/ExprHListDslImplicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ package dsl
import cats.{Align, Functor, FunctorFilter}
import shapeless.{::, HList, HNil}

import scala.collection.Factory

/**
* A marker trait for determining which set of implicits to inherit.
*
Expand All @@ -27,11 +29,22 @@ sealed trait ExprHListDslImplicits
trait WrappedExprHListDslImplicits extends ExprHListDslImplicits {
self: DslTypes with WrappedLowPriorityExprHListDslImplicits =>

implicit def hlastAlignIterableOnceMapN[H](
implicit
isCons: IsExprHCons.Aux[IterableOnce[W[H]] :: HNil, IterableOnce[W[H]], HNil],
): ZipToShortest.Aux[Lambda[a => Seq[W[a]]], IterableOnce[W[H]] :: HNil, OP, H :: HNil]

implicit def hlastAlignMapN[C[_] : Functor, H](
implicit
isCons: IsExprHCons.Aux[C[W[H]] :: HNil, C[W[H]], HNil],
): ZipToShortest.Aux[Lambda[a => C[W[a]]], C[W[H]] :: HNil, OP, H :: HNil]

implicit def hconsAlignIterableOnceMapN[H, WT <: HList](
implicit
isCons: IsExprHCons.Aux[IterableOnce[W[H]] :: WT, IterableOnce[W[H]], WT],
mt: ZipToShortest[Lambda[a => Seq[W[a]]], WT, OP],
): ZipToShortest.Aux[Lambda[a => Seq[W[a]]], IterableOnce[W[H]] :: WT, OP, H :: mt.UL]

implicit def hconsAlignMapN[C[_] : Align : FunctorFilter, H, WT <: HList](
implicit
isCons: IsExprHCons.Aux[C[W[H]] :: WT, C[W[H]], WT],
Expand All @@ -58,11 +71,22 @@ trait WrappedLowPriorityExprHListDslImplicits {
trait UnwrappedExprHListDslImplicits extends ExprHListDslImplicits {
self: DslTypes with UnwrappedLowPriorityExprHListDslImplicits =>

implicit def hlastAlignIterableOnceMapN[H](
implicit
isCons: IsExprHCons.Aux[IterableOnce[H] :: HNil, IterableOnce[H], HNil],
): ZipToShortest.Aux[Seq, IterableOnce[H] :: HNil, OP, H :: HNil]

implicit def hlastAlignMapN[C[_] : Functor, H](
implicit
isCons: IsExprHCons.Aux[C[H] :: HNil, C[H], HNil],
): ZipToShortest.Aux[C, C[H] :: HNil, OP, H :: HNil]

implicit def hconsAlignIterableOnceMapN[H, WT <: HList](
implicit
mt: ZipToShortest[Seq, WT, OP],
isCons: IsExprHCons.Aux[IterableOnce[H] :: WT, IterableOnce[H], WT],
): ZipToShortest.Aux[Seq, IterableOnce[H] :: WT, OP, H :: mt.UL]

implicit def hconsAlignMapN[C[_] : Align : FunctorFilter, H, WT <: HList](
implicit
isCons: IsExprHCons.Aux[C[H] :: WT, C[H], WT],
Expand Down
13 changes: 13 additions & 0 deletions core-v1/src/main/scala/dsl/JustifiedBuildExprDsl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,25 @@ sealed trait JustifiedExprHListDslImplicits
with JustifiedLowPriorityExprHListDslImplicits
with DefinedJustifiedDslImplicitDefinitions {

override implicit def hlastAlignIterableOnceMapN[H](
implicit
isCons: IsExprHCons.Aux[IterableOnce[Justified[H]] :: HNil, IterableOnce[Justified[H]], HNil],
): ZipToShortest.Aux[Lambda[a => Seq[Justified[a]]], IterableOnce[Justified[H]] :: HNil, OP, H :: HNil] =
defn.hlastAlignIterableOnceMapN

override implicit def hlastAlignMapN[C[_] : Functor, H](
implicit
isCons: IsExprHCons.Aux[C[Justified[H]] :: HNil, C[Justified[H]], HNil],
): ZipToShortest.Aux[Lambda[a => C[Justified[a]]], C[Justified[H]] :: HNil, OP, H :: HNil] =
defn.hlastAlignMapN

override implicit def hconsAlignIterableOnceMapN[H, WT <: HList](
implicit
isCons: IsExprHCons.Aux[IterableOnce[Justified[H]] :: WT, IterableOnce[Justified[H]], WT],
mt: ZipToShortest[Lambda[a => Seq[Justified[a]]], WT, OP],
): ZipToShortest.Aux[Lambda[a => Seq[Justified[a]]], IterableOnce[Justified[H]] :: WT, OP, H :: mt.UL] =
defn.hconsAlignIterableOnceMapN(mt)

override implicit def hconsAlignMapN[C[_] : Align : FunctorFilter, H, WT <: HList](
implicit
isCons: IsExprHCons.Aux[C[Justified[H]] :: WT, C[Justified[H]], WT],
Expand Down
4 changes: 2 additions & 2 deletions core-v1/src/main/scala/dsl/ZipToShortest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import shapeless.HList
*
* (Any ~:> Seq[String]) :: (I ~:> Seq[Int]) => Any ~:> Seq[String :: Int :: HNil]
*/
trait ZipToShortest[W[_], WL <: HList, OP[_]] {
trait ZipToShortest[+W[_], WL <: HList, OP[_]] {
type UL <: HList

def zipToShortestWith[G[-_, +_] : Arrow, I](
Expand All @@ -30,5 +30,5 @@ trait ZipToShortest[W[_], WL <: HList, OP[_]] {
* Implementations live in the subclasses of [[ExprHListDslImplicits]].
*/
object ZipToShortest {
type Aux[W[_], WL <: HList, OP[_], UL0] = ZipToShortest[W, WL, OP] { type UL = UL0 }
type Aux[+W[_], WL <: HList, OP[_], UL0] = ZipToShortest[W, WL, OP] { type UL = UL0 }
}
14 changes: 14 additions & 0 deletions core-v1/src/main/scala/engine/ImmutableCachingEngine.scala
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,20 @@ object ImmutableCachingEngine {
debugging(expr).invokeAndReturn(state((i, inputs), result))
}

override def visitRepeat[I, O](
expr: Expr.Repeat[I, O, OP],
)(implicit
opO: OP[IterableOnce[O]],
): I => CachedResult[IterableOnce[O]] = { i =>
val always = Eval.always {
expr.inputExpr.visit(this)(i).value
}
val eval = if (expr.recompute) always else always.memoize
val unlimited = Iterator.continually(eval.value)
val iterable = expr.limit.fold(unlimited)(unlimited.take)
debugging(expr).invokeAndReturn(state((i, eval, expr.limit), cached(iterable)))
}

override def visitSelect[I, A, B, O : OP](expr: Expr.Select[I, A, B, O, OP]): I => CachedResult[O] =
memoize(expr, _) { i =>
val inputResult = expr.inputExpr.visit(this)(i)
Expand Down
14 changes: 14 additions & 0 deletions core-v1/src/main/scala/engine/SimpleEngine.scala
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,20 @@ object SimpleEngine {
debugging(expr).invokeAndReturn(state((i, results), finalResult))
}

override def visitRepeat[I, O](
expr: Expr.Repeat[I, O, OP],
)(implicit
opO: OP[IterableOnce[O]],
): I => IterableOnce[O] = { i =>
val always = Eval.always {
expr.inputExpr.visit(this)(i)
}
val eval = if (expr.recompute) always else always.memoize
val unlimited = Iterator.continually(eval.value)
val iterable = expr.limit.fold(unlimited)(unlimited.take)
debugging(expr).invokeAndReturn(state((i, eval, expr.limit), iterable))
}

override def visitSelect[I, A, B, O : OP](expr: Expr.Select[I, A, B, O, OP]): I => O = { i =>
val a = expr.inputExpr.visit(this)(i)
val b = expr.lens.get(a)
Expand Down
6 changes: 6 additions & 0 deletions core-v1/src/main/scala/engine/StandardEngine.scala
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,12 @@ object StandardEngine {
ExprResult.Or(expr, finalState, results)
}

override def visitRepeat[I, O](
expr: Expr.Repeat[I, O, OP],
)(implicit
opO: OP[IterableOnce[O]],
): PO <:< I => ExprResult[PO, I, IterableOnce[O], OP] = ???

override def visitSelect[I, A, B, O : OP](
expr: Expr.Select[I, A, B, O, OP],
): PO <:< I => ExprResult[PO, I, O, OP] = { implicit evPOisI =>
Expand Down
Loading

0 comments on commit 7f7b57f

Please sign in to comment.