diff --git a/build.sbt b/build.sbt index cd9bca7dc..518a53cde 100644 --- a/build.sbt +++ b/build.sbt @@ -55,12 +55,6 @@ lazy val bench = commonProject("bench", "vapors") Test / parallelExecution := false, ) -lazy val core = commonProject("core") - .dependsOn(`core-v1`) - .settings( - libraryDependencies ++= CoreProject.all(scalaVersion.value), - ) - lazy val `core-v1` = commonProject("core-v1", "vapors.v1") .settings( libraryDependencies ++= CoreV1Project.all, diff --git a/core/src/main/scala/vapors/algebra/CaptureP.scala b/core/src/main/scala/vapors/algebra/CaptureP.scala deleted file mode 100644 index d5dfee25c..000000000 --- a/core/src/main/scala/vapors/algebra/CaptureP.scala +++ /dev/null @@ -1,141 +0,0 @@ -package com.rallyhealth - -package vapors.algebra - -import vapors.data.TypedFact -import vapors.interpreter.{ExprInput, ExprOutput} - -import cats.{Eval, Monoid} - -import scala.annotation.implicitNotFound - -@implicitNotFound( - """ -Missing implicit CaptureP[${V}, ${R}, P] (where P is a custom parameter type that you choose via implicit). -Currently, P is inferred as ${P}. - -Expression builders rely on the implicit CaptureP in scope to determine the concrete type of P. -CaptureP.unit will be used by default unless another implicit is in scope and P will be inferred as Unit. -However, if the compiler has trouble inferring other arguments, such as V or R, then it may also fail to infer P. - -Is the compiler is unable to infer either V (${V}) or R (${R})? -If so, you may need to check the types of your expression and make sure they match. - -Failed to find a CaptureP for the following expression: -""", -) -trait CaptureP[V, R, P] { - - /** - * Folds the captured parameters of the sub-expressions with the captured parameter of this expression. - * - * @param expr the expression that is being evaluated - * @param input the input to the expression - * @param output the output of the expression given the input - * @param subExprParams the captured parameters of the sub-expressions. - * NOTE: This is not a good data structure for parameters because you cannot tell where they - * come from. - */ - def foldToParam( - expr: Expr[V, R, P], - input: ExprInput[V], - output: ExprOutput[R], - subExprParams: List[Eval[P]], - ): Eval[P] -} - -object CaptureP extends CaptureUnitLowPriorityImplicit { - - /** - * Captures the type parameter from facts with a value of type [[T]]. - */ - trait FromFactsOfType[T, R, P] extends CaptureP[Seq[TypedFact[T]], R, P] - - /** - * Captures the type parameter from facts with a value of type [[T]] (assuming [[P]] is a [[Monoid]]) - * - * @see [[AsMonoid]] - */ - abstract class AsMonoidFromFactsOfType[T, R, P : Monoid] - extends AsMonoid[Seq[TypedFact[T]], R, P] // TODO: Use Iterable instead of Seq? - with FromFactsOfType[T, R, P] - - // TODO: This is not very safe. Nodes will often combine the params of their input expressions and sub expressions. - // It might not be safe to assume that Monoid is enough to capture the expected behavior for combining the - // parameters in this situation. Maybe there is a way to group sub-expressions better than List? - /** - * Capture a [[Monoid]]-like parameter and combine all captured params of the current node's children. - * - * You must still define how to extract the parameter from the current node and combine it with its children. - * - * @note some [[Expr]] nodes lump params from different sub-expressions into the same list. If this doesn't - * work for you, then you must define a [[CaptureP]] and handle combining these sub-expression results - * in whatever way works for you. - * - * @note this can be used in conjunction with extending [[AsMonoidCompanion]] in the surrounding object - * to define a standard fallback for unmatched types (assuming your parameter is a [[Monoid]]). - */ - abstract class AsMonoid[V, R, P : Monoid] extends CaptureP[V, R, P] { - - protected val empty: P = Monoid[P].empty - - override def foldToParam( - expr: Expr[V, R, P], - input: ExprInput[V], - output: ExprOutput[R], - subExprParams: List[Eval[P]], - ): Eval[P] = { - val combinedChildren = Monoid[Eval[P]].combineAll(subExprParams) - combinedChildren.flatMap(foldWithParentParam(expr, input, output, _)) - } - - protected def foldWithParentParam( - expr: Expr[V, R, P], - input: ExprInput[V], - output: ExprOutput[R], - processedChildren: P, - ): Eval[P] - } - - /** - * Captures all child node params, combines them, and passes them up as this node's param. - * - * @note this ignores the context of the node it matches. This should only be used as a fallback - * for having no definition of how to capture a parameter with the given types, but needing - * to pass the captured params from this node's children up to the next parent. - */ - final class AsMonoidAndPass[V, R, P : Monoid] extends AsMonoid[V, R, P] { - - override protected def foldWithParentParam( - expr: Expr[V, R, P], - input: ExprInput[V], - output: ExprOutput[R], - processedChildren: P, - ): Eval[P] = Eval.now(processedChildren) - - override def toString: String = "captureParamAndPass" - } - - /** - * Extend this in your companion object to get a low-priority implicit for capturing your [[Monoid]] parameter - * for types that you don't specifically select. - */ - abstract class AsMonoidCompanion[P](protected implicit final val P: Monoid[P]) { - implicit def captureParamAndPass[V, R]: CaptureP[V, R, P] = new AsMonoidAndPass[V, R, P] - } -} - -sealed trait CaptureUnitLowPriorityImplicit { - - implicit def captureUnit[V, R]: CaptureP[V, R, Unit] = new CaptureP[V, R, Unit] { - - override final def foldToParam( - expr: Expr[V, R, Unit], - input: ExprInput[V], - output: ExprOutput[R], - subExprParams: List[Eval[Unit]], - ): Eval[Unit] = Eval.Unit - - override final def toString: String = "captureUnit" - } -} diff --git a/core/src/main/scala/vapors/algebra/Expr.scala b/core/src/main/scala/vapors/algebra/Expr.scala deleted file mode 100644 index 4c56d380d..000000000 --- a/core/src/main/scala/vapors/algebra/Expr.scala +++ /dev/null @@ -1,594 +0,0 @@ -package com.rallyhealth - -package vapors.algebra - -import vapors.data._ -import vapors.interpreter.{ExprOutput, InterpretExprAsResultFn} -import vapors.lens.NamedLens -import vapors.logic.{Conjunction, Disjunction, Negation} -import vapors.math._ - -import cats._ -import cats.data.NonEmptyList -import shapeless.{HList, Typeable} - -import scala.collection.MapView - -/** - * The core expression algebra. - * - * In essence, you can view this as a function from F[V] => R with a custom parameter that you can capture at each - * point in the expression using a [[CaptureP]]. - * - * This expression is folded over to produce serialized versions of the expression as well as the evaluator function. - * - * @see [[InterpretExprAsResultFn]] - * - * V = Input Value type - * R = Return type - * P = Captured param - */ -sealed abstract class Expr[V, R, P] { - - def visit[G[_]](v: Expr.Visitor[V, P, G]): G[R] - - def capture: CaptureP[V, R, P] -} - -object Expr { - - import cats.{~>, Id} - - trait Visitor[V, P, G[_]] extends (Expr[V, *, P] ~> G) { - // Please keep the following methods in alphabetical order - override final def apply[R](fa: Expr[V, R, P]): G[R] = fa.visit(this) - def visitAddOutputs[R : Addition](expr: AddOutputs[V, R, P]): G[R] - def visitAnd[R : Conjunction : ExtractBoolean](expr: And[V, R, P]): G[R] - def visitCollectSomeOutput[M[_] : Foldable, U, R : Monoid](expr: CollectFromOutput[V, M, U, R, P]): G[R] - def visitConcatOutput[M[_] : MonoidK, R](expr: ConcatOutput[V, M, R, P]): G[M[R]] - def visitConstOutput[R](expr: ConstOutput[V, R, P]): G[R] - def visitCustomFunction[A, R](expr: CustomFunction[V, A, R, P]): G[R] - def visitDefine[M[_] : Foldable, T](expr: Define[M, T, P]): G[FactSet] - def visitDivideOutputs[R : Division](expr: DivideOutputs[V, R, P]): G[R] - def visitEmbed[R](expr: Embed[V, R, P]): G[R] - def visitExistsInOutput[M[_] : Foldable, U](expr: ExistsInOutput[V, M, U, P]): G[Boolean] - def visitExponentiateOutputs(expr: ExponentiateOutputs[V, P]): G[Double] - def visitFilterOutput[M[_] : Foldable : FunctorFilter, R](expr: FilterOutput[V, M, R, P]): G[M[R]] - def visitFlatMapOutput[M[_] : Foldable : FlatMap, U, X](expr: FlatMapOutput[V, M, U, X, P]): G[M[X]] - def visitGroupOutput[M[_] : Foldable, U : Order, K](expr: GroupOutput[V, M, U, K, P]): G[MapView[K, Seq[U]]] - def visitMapOutput[M[_] : Foldable : Functor, U, R](expr: MapOutput[V, M, U, R, P]): G[M[R]] - def visitMultiplyOutputs[R : Multiplication](expr: MultiplyOutputs[V, R, P]): G[R] - def visitNegativeOutput[R : Negative](expr: NegativeOutput[V, R, P]): G[R] - def visitNot[R : Negation](expr: Not[V, R, P]): G[R] - def visitOr[R : Disjunction : ExtractBoolean](expr: Or[V, R, P]): G[R] - def visitOutputIsEmpty[M[_] : Foldable, R](expr: OutputIsEmpty[V, M, R, P]): G[Boolean] - def visitOutputWithinSet[R](expr: OutputWithinSet[V, R, P]): G[Boolean] - def visitOutputWithinWindow[R](expr: OutputWithinWindow[V, R, P]): G[Boolean] - def visitFoldOutput[M[_] : Foldable, R : Monoid](expr: FoldOutput[V, M, R, P]): G[R] - def visitReturnInput(expr: ReturnInput[V, P]): G[V] - def visitSelectFromOutput[S, R](expr: SelectFromOutput[V, S, R, P]): G[R] - def visitSortOutput[M[_], R](expr: SortOutput[V, M, R, P]): G[M[R]] - def visitSubtractOutputs[R : Subtraction](expr: SubtractOutputs[V, R, P]): G[R] - def visitTakeFromOutput[M[_] : Traverse : TraverseFilter, R](expr: TakeFromOutput[V, M, R, P]): G[M[R]] - def visitUsingDefinitions[R](expr: UsingDefinitions[V, R, P]): G[R] - def visitWhen[R](expr: When[V, R, P]): G[R] - def visitWrapOutput[L, R](expr: WrapOutput[V, L, R, P]): G[R] - def visitWrapOutputHList[T <: HList, R](expr: WrapOutputHList[V, T, R, P]): G[R] - def visitWrapOutputSeq[R](expr: WrapOutputSeq[V, R, P]): G[Seq[R]] - def visitWithFactsOfType[T, R](expr: WithFactsOfType[T, R, P]): G[R] - def visitZipOutput[M[_] : Align : FunctorFilter, L <: HList, R](expr: ZipOutput[V, M, L, R, P]): G[M[R]] - } - - /* - * Please keep the following expressions in the same order in Expr.scala and ExprResult.scala. - * It makes it easier to find the mirror node. - * - * While alphabetical seems like the best way to organize these classes, in practice - * certain nodes that are related to each other (like addition and subtraction) will have - * very similar implementations and will likely need to be refactored in identical ways. - * - * The Visitor and its subclasses use alphabetical sorting because their definitions - * are derivative and it is practically impossible to forget to update their implementation - * without the compiler complaining. - */ - - // TODO: Should constants always act as their own evidence? - /** - * Uses the given value as the output of this expression and ignores the input. - */ - final case class ConstOutput[V, R, P]( - value: R, - evidence: Evidence, - capture: CaptureP[V, R, P], - ) extends Expr[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitConstOutput(this) - } - - /** - * Returns the input [[V]] values as the output of this expression node. - * - * Essentially, this is the [[identity]] function represented as an [[Expr]]. - * - * @note many of the other [[Expr]] nodes will take an `inputExpr`. This is useful for passing a selected value - * from the input into an expression operation, so that you can operate on it as the output parameter. - * This is useful to avoid needing to define separate nodes for the input / output sides of an expression. - * Instead, you just take an `inputExpr` and you can pass this node to move the input into the output. - */ - final case class ReturnInput[V, P](capture: CaptureP[V, V, P]) extends Expr[V, V, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[V] = v.visitReturnInput(this) - } - - /** - * The input of the expression is replaced with the [[FactTable]] and the output of the [[embeddedExpr]] - * is returned. - * - * This makes it possible to embed separate / reusable sub-expressions that don't depend on any contextual input - * into any sub-expression to substitute or defer the result to a separate computation. - * - * Typically, these nodes only exist to satisfy the type system and are skipped by visitors, however, the - * input to the [[Embed]] node can be captured via the input parameters and folded into the final result. - */ - final case class Embed[V, R, P]( - embeddedExpr: Expr[FactTable, R, P], - capture: CaptureP[V, R, P], - ) extends Expr[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitEmbed(this) - } - - /** - * A common top-level expression that defines an output without any input beyond the initial [[FactTable]]. - * - * The given [[FactTypeSet]] is used to select the matching facts from the [[FactTable]] and provide them - * as both the input value and [[Evidence]] to the [[subExpr]]. - * - * @note these expressions can be easily dropped into any place that accepts the [[subExpr]] output type `R` - * by wrapping the expression in an [[Embed]]. - */ - final case class WithFactsOfType[T, R, P]( - factTypeSet: FactTypeSet[T], - subExpr: Expr[Seq[TypedFact[T]], R, P], - capture: CaptureP[FactTable, R, P], - ) extends Expr[FactTable, R, P] { - override def visit[G[_]](v: Visitor[FactTable, P, G]): G[R] = v.visitWithFactsOfType(this) - } - - /** - * Same as [[Define]], but captures and hides common type parameters to avoid issues with wild-cards on - * higher-kinded type parameters. - * - * @note I'm using a sealed trait instead of a type alias to avoid expressions from matching the type - * that do not supply the requisite metadata (provided by the only subclass [[Define]]). - * - * @see https://github.com/scala/bug/issues/8039 for more info - */ - sealed trait Definition[P] extends Expr[FactTable, FactSet, P] - - /** - * Define or add another source for a [[FactType]]. - * - * When used in conjunction with [[UsingDefinitions]], this node allows you to update the [[FactTable]] - * used by sub-expressions so that they can now have access to the fact values returned by the [[definitionExpr]]. - */ - final case class Define[M[_] : Foldable, T, P]( - factType: FactType[T], - definitionExpr: Expr[FactTable, M[T], P], - capture: CaptureP[FactTable, FactSet, P], - ) extends Definition[P] { - override def visit[G[_]](v: Visitor[FactTable, P, G]): G[FactSet] = v.visitDefine(this) - } - - /** - * Combines all [[Definition]]s and adds the resulting facts to the [[FactTable]], which is then provided - * to the [[subExpr]] during evaluation. - * - * @note this is a way to sequence the evaluation of an expression to occur after the fact definitions it - * depends on have been evaluated and added to the [[FactTable]] as input. If your fact definitions - * require other facts to be defined for their computation, then you must wrap them with a - * [[UsingDefinitions]] expression node as well. Repeating this process until the directed acyclic - * graph of dependencies has been sequenced properly. Including the same fact definition twice is - * likely idempotent, however, there is no currently machinery to avoid unnecessary re-computation. - */ - final case class UsingDefinitions[V, R, P]( - definitions: Vector[Definition[P]], - subExpr: Expr[V, R, P], - capture: CaptureP[V, R, P], - ) extends Expr[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitUsingDefinitions(this) - } - - /** - * A custom (ideally) pure total function. - * - * In practice, it is possible for custom functions to throw exceptions. This is something - * I hope to address in the future, but it will be up to convention to handle exceptions - * by lifting them into the expression language. It is always possible to define them wrong. - * - * @param inputExpr an expression that produces the argument value - * @param name the name of the expression for debugging purposes - * @param evaluate an (ideally) pure total function to apply to the argument type - */ - final case class CustomFunction[V, A : Typeable, R, P]( - inputExpr: Expr[V, A, P], - name: String, - evaluate: A => R, - capture: CaptureP[V, R, P], - ) extends Expr[V, R, P] { // TODO: Should this return a Try[R]? Either[Failure, R]? - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitCustomFunction(this) - } - - /** - * Returns the [[Conjunction]] of all results of the given input expressions. - * - * All evidence is tracked using the logic defined in [[ExprOutput.conjunction]] - * - * @note this does not short-circuit. - * [[Evidence]] for all input values are used in deciding the evidence for the result. - * - * If you want to apply short-circuiting, you must implement it within the algebra using sorting, - * filtering, and limiting operators. - */ - final case class And[V, R : Conjunction : ExtractBoolean, P]( - inputExprList: NonEmptyList[Expr[V, R, P]], - capture: CaptureP[V, R, P], - ) extends Expr[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitAnd(this) - } - - /** - * Returns the [[Disjunction]] of all results of the given input expressions. - * - * All evidence is tracked using the logic defined in [[ExprOutput.disjunction]] - * - * @note this does not short-circuit. - * [[Evidence]] for all input values are used in deciding the evidence for the result. - * - * If you want to apply short-circuiting, you must implement it within the algebra using sorting, - * filtering, and limiting operators. - */ - final case class Or[V, R : Disjunction : ExtractBoolean, P]( - inputExprList: NonEmptyList[Expr[V, R, P]], - capture: CaptureP[V, R, P], - ) extends Expr[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitOr(this) - } - - /** - * Negates the output from the given [[inputExpr]], but keeps the same evidence. - * - * Negation has no impact on the [[Evidence]]. Negation of a false value with evidence - * becomes a true value with the same evidence, and negation of a true value with no - * evidence becomes a false value with no evidence. How the [[Evidence.none]] is interpreted - * is up to the caller, but any boolean-like outcome with no Evidence should be treated - * identically. - */ - final case class Not[V, R : Negation, P]( - inputExpr: Expr[V, R, P], - capture: CaptureP[V, R, P], - ) extends Expr[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitNot(this) - } - - /** - * Conditional expression that depends on the output of a given boolean expression to determine whether to - * evaluate the [[conditionBranches]] or the [[defaultExpr]]. - * - * @note this expression does short-circuit on the first branch whose condition is met - */ - final case class When[V, R, P]( - conditionBranches: NonEmptyList[ConditionBranch[V, R, P]], - defaultExpr: Expr[V, R, P], - capture: CaptureP[V, R, P], - ) extends Expr[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitWhen(this) - } - - /** - * Uses a [[NamedLens]] to select a value from the output of the given [[inputExpr]] and returns it as output. - * - * @note if you want to select from the input [[V]], you can pass [[ReturnInput]] as the [[inputExpr]]. - */ - final case class SelectFromOutput[V, S, R, P]( - inputExpr: Expr[V, S, P], - lens: NamedLens[S, R], - capture: CaptureP[V, R, P], - ) extends Expr[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitSelectFromOutput(this) - } - - final case class FilterOutput[V, M[_] : Foldable : FunctorFilter, R, P]( - inputExpr: Expr[V, M[R], P], - condExpr: Expr[R, Boolean, P], - capture: CaptureP[V, M[R], P], - ) extends Expr[V, M[R], P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[M[R]] = v.visitFilterOutput(this) - } - - /** - * [[Foldable.collectFoldSome]] every element in the output of the given [[inputExpr]]. - * - * @note if you want to apply this to the input (i.e. `F[V]`), you can pass [[ReturnInput]] as the [[inputExpr]]. - */ - // TODO: Rename to CollectFoldSomeOutput? - final case class CollectFromOutput[V, M[_] : Foldable, U, R : Monoid, P]( - inputExpr: Expr[V, M[U], P], - collectExpr: Expr[U, Option[R], P], - capture: CaptureP[V, R, P], - ) extends Expr[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitCollectSomeOutput(this) - } - - /** - * [[FlatMap.flatMap]] every element in the output of the given [[inputExpr]]. - * - * @note if you want to apply this to the input (i.e. `F[V]`), you can pass [[ReturnInput]] as the [[inputExpr]]. - */ - final case class FlatMapOutput[V, M[_] : Foldable : FlatMap, U, R, P]( - inputExpr: Expr[V, M[U], P], - flatMapExpr: Expr[U, M[R], P], - capture: CaptureP[V, M[R], P], - ) extends Expr[V, M[R], P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[M[R]] = v.visitFlatMapOutput(this) - } - - /** - * [[Functor.map]] every element in the output of the given [[inputExpr]]. - * - * @note if you want to apply this to the input (i.e. `F[V]`), you can pass [[ReturnInput]] as the [[inputExpr]]. - */ - final case class MapOutput[V, M[_] : Foldable : Functor, U, R, P]( - inputExpr: Expr[V, M[U], P], - mapExpr: Expr[U, R, P], - capture: CaptureP[V, M[R], P], - ) extends Expr[V, M[R], P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[M[R]] = v.visitMapOutput(this) - } - - /** - * Group the output of a given expression into a Map of some hashable key type. - * - * @note this uses the default (_: K).## method for hashing because it uses the standard Scala Map collection. - */ - final case class GroupOutput[V, M[_] : Foldable, U : Order, K, P]( - inputExpr: Expr[V, M[U], P], - groupByLens: NamedLens[U, K], - capture: CaptureP[V, MapView[K, Seq[U]], P], - ) extends Expr[V, MapView[K, Seq[U]], P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[MapView[K, Seq[U]]] = v.visitGroupOutput(this) - } - - /** - * Sort the elements in the output of the given [[inputExpr]] using the provided [[ExprSorter]]. - * - * @note if you want to apply this to the input (i.e. `F[V]`), you can pass [[ReturnInput]] as the [[inputExpr]]. - */ - final case class SortOutput[V, M[_], R, P]( - inputExpr: Expr[V, M[R], P], - sorter: ExprSorter[M, R], - capture: CaptureP[V, M[R], P], - ) extends Expr[V, M[R], P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[M[R]] = v.visitSortOutput(this) - } - - /** - * Concatenate the outputs of all the given expressions into a single expression of the concatenated monoid. - * - * @note if you want the output to be evaluated only as needed, then you should use a LazyList for the - * [[inputExprList]] param sequence type. - */ - final case class ConcatOutput[V, M[_] : MonoidK, R, P]( - inputExprList: Seq[Expr[V, M[R], P]], - capture: CaptureP[V, M[R], P], - ) extends Expr[V, M[R], P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[M[R]] = v.visitConcatOutput(this) - } - - /** - * Fold the output of the input expression by combining all the elements as pairs or with - * the empty result value. - */ - final case class FoldOutput[V, M[_] : Foldable, R : Monoid, P]( - inputExpr: Expr[V, M[R], P], - capture: CaptureP[V, R, P], - ) extends Expr[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitFoldOutput(this) - } - - /** - * Apply an [[ExprConverter]] to the output of a single expression. - * - * This is a bit of an escape hatch for automatically converting one type to another in a manner that - * is restricted to what [[ExprConverter]] supports. In the future, I hope to come up with a more - * general purpose mechanism for doing this that works across all different types, including custom - * user-defined types. - */ - final case class WrapOutput[V, L, R, P]( - inputExpr: Expr[V, L, P], - converter: ExprConverter[L, R], - capture: CaptureP[V, R, P], - ) extends Expr[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitWrapOutput(this) - } - - /** - * Wrap a sequence of expressions into a single expression of a lazy sequence that evaluates only the - * expressions needed to produce the values used in subsequent expression nodes. - */ - final case class WrapOutputSeq[V, R, P]( - inputExprList: Seq[Expr[V, R, P]], - capture: CaptureP[V, Seq[R], P], - ) extends Expr[V, Seq[R], P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[Seq[R]] = v.visitWrapOutputSeq(this) - } - - /** - * Convert a non-empty HList of expressions into an expression of HList, then map a [[converter]] function. - * - * This is very similar to [[ZipOutput]], except that for concrete types, there is no possibility of having - * anything other than a single instance of the expected type, so you don't need any type-classes for the - * return type. - */ - final case class WrapOutputHList[V, L <: HList, R, P]( - inputExprHList: NonEmptyExprHList[V, Id, L, P], - converter: ExprConverter[L, R], - capture: CaptureP[V, R, P], - ) extends Expr[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitWrapOutputHList(this) - } - - /** - * Apply a given [[converter]] to every HList produced by zipping the outputs of expressions that return the - * same higher-kinded sequence, stopping at the shortest sequence. - */ - final case class ZipOutput[V, M[_] : Align : FunctorFilter, L <: HList, R, P]( - inputExprHList: NonEmptyExprHList[V, M, L, P], - converter: ExprConverter[L, R], - capture: CaptureP[V, M[R], P], - ) extends Expr[V, M[R], P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[M[R]] = v.visitZipOutput(this) - } - - /** - * Return true if the output of the given [[inputExpr]] is an empty collection. - */ - final case class OutputIsEmpty[V, M[_] : Foldable, R, P]( - inputExpr: Expr[V, M[R], P], - capture: CaptureP[V, Boolean, P], - ) extends Expr[V, Boolean, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[Boolean] = v.visitOutputIsEmpty(this) - } - - /** - * Take a given number of elements from the input sequence. - * - * If the number to take is positive, it will pull that number of elements from the start of the sequence. - * If negative, it will pull that number of elements from the tail of the sequence. If zero, it will return - * an empty sequence. - */ - final case class TakeFromOutput[V, M[_] : Traverse : TraverseFilter, R, P]( - inputExpr: Expr[V, M[R], P], - take: Int, - capture: CaptureP[V, M[R], P], - ) extends Expr[V, M[R], P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[M[R]] = v.visitTakeFromOutput(this) - } - - /** - * Returns `true` if at least one element in the output of the given [[inputExpr]] meets the given [[conditionExpr]]. - * - * @note this does not short-circuit. [[Evidence]] for all elements that match (or do not match) the - * [[conditionExpr]] will be retained as evidence of the whole result. - * @note if you want to apply this to the input (i.e. `F[V]`), you can pass [[ReturnInput]] as the [[inputExpr]]. - * @see [[Foldable.exists]] for more details. - */ - final case class ExistsInOutput[V, M[_] : Foldable, U, P]( - inputExpr: Expr[V, M[U], P], - conditionExpr: Expr[U, Boolean, P], - capture: CaptureP[V, Boolean, P], - ) extends Expr[V, Boolean, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[Boolean] = v.visitExistsInOutput(this) - } - - /** - * Adds the results of all expressions in [[inputExprList]] using the provided definition for [[Addition]]. - */ - final case class AddOutputs[V, R : Addition, P]( - inputExprList: NonEmptyList[Expr[V, R, P]], - capture: CaptureP[V, R, P], - ) extends Expr[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitAddOutputs(this) - } - - /** - * Subtracts the results of all expressions in [[inputExprList]] using the provided definition for [[Subtraction]]. - * - * @note the order of expressions matters for subtraction, so all subtraction is applied left-to-right. - */ - final case class SubtractOutputs[V, R : Subtraction, P]( - inputExprList: NonEmptyList[Expr[V, R, P]], - capture: CaptureP[V, R, P], - ) extends Expr[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitSubtractOutputs(this) - } - - /** - * Multiply the results of all expressions in [[inputExprList]] using the provided definition for [[Addition]]. - */ - final case class MultiplyOutputs[V, R : Multiplication, P]( - inputExprList: NonEmptyList[Expr[V, R, P]], - capture: CaptureP[V, R, P], - ) extends Expr[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitMultiplyOutputs(this) - } - - /** - * Divides the results of all expressions in [[inputExprList]] using the provided definition for [[Division]]. - * - * @note the order of expressions matters for division, so all division is applied left-to-right. - */ - final case class DivideOutputs[V, R : Division, P]( - inputExprList: NonEmptyList[Expr[V, R, P]], - capture: CaptureP[V, R, P], - ) extends Expr[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitDivideOutputs(this) - } - - /** - * Raise the given base expression to the result of the given power exponent expression. - */ - final case class ExponentiateOutputs[V, P]( - baseExpr: Expr[V, Double, P], - exponentExpr: Expr[V, Double, P], - capture: CaptureP[V, Double, P], - ) extends Expr[V, Double, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[Double] = v.visitExponentiateOutputs(this) - } - - /** - * Returns the negative result value of [[inputExpr]] using the provided definition for [[Negative]]. - */ - final case class NegativeOutput[V, R : Negative, P]( - inputExpr: Expr[V, R, P], - capture: CaptureP[V, R, P], - ) extends Expr[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitNegativeOutput(this) - } - - /** - * Checks if the result of the [[inputExpr]] is contained within the given set of [[accepted]] values. - */ - final case class OutputWithinSet[V, R, P]( - inputExpr: Expr[V, R, P], - accepted: Set[R], - capture: CaptureP[V, Boolean, P], - ) extends Expr[V, Boolean, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[Boolean] = v.visitOutputWithinSet(this) - } - - /** - * Checks if the result of the [[inputExpr]] is contained within the given [[windowExpr]]. - * - * This is effectively how comparison checks are made. - * - * @see [[Window]] for more details. - */ - final case class OutputWithinWindow[V, R, P]( - inputExpr: Expr[V, R, P], - windowExpr: Expr[V, Window[R], P], - capture: CaptureP[V, Boolean, P], - ) extends Expr[V, Boolean, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[Boolean] = v.visitOutputWithinWindow(this) - } -} - -/** - * A container for a condition and an expression to compute if the condition is met. - * - * @see [[Expr.When]] for usage. - * - * @param whenExpr a conditional expression that guards the resulting [[thenExpr]] - * @param thenExpr an expression to compute the result if the [[whenExpr]] returns true - */ -final case class ConditionBranch[V, R, P]( - whenExpr: Expr[V, Boolean, P], - thenExpr: Expr[V, R, P], -) diff --git a/core/src/main/scala/vapors/algebra/ExprConverter.scala b/core/src/main/scala/vapors/algebra/ExprConverter.scala deleted file mode 100644 index 9fdddbf99..000000000 --- a/core/src/main/scala/vapors/algebra/ExprConverter.scala +++ /dev/null @@ -1,50 +0,0 @@ -package com.rallyhealth - -package vapors.algebra - -import vapors.data.Window - -import cats.Order -import shapeless.ops.hlist.Tupler -import shapeless.{Generic, HList} - -/** - * A serializable description of a total conversion function from one type to another. - */ -sealed trait ExprConverter[L, R] extends (L => R) { - - /** - * The type of conversion applied. - */ - def conversionType: String - - /** - * Apply the conversion. - * - * @note this should never throw an exception. - */ - override def apply(inputValue: L): R -} - -object ExprConverter { - - // TODO: Capture type information about R for debugging? - private final class Impl[L, R]( - override val conversionType: String, - convert: L => R, - ) extends ExprConverter[L, R] { - override def apply(in: L): R = convert(in) - override val toString: String = s"ExprConverter.$conversionType" - } - - def asHListIdentity[R <: HList]: ExprConverter[R, R] = new Impl("asHList", identity) - - def asProductType[L <: HList, R](implicit gen: Generic.Aux[R, L]): ExprConverter[L, R] = - new Impl("asProduct", gen.from) - - def asTuple[L <: HList, R](implicit tupler: Tupler.Aux[L, R]): ExprConverter[L, R] = - new Impl("asTuple", tupler.apply) - - def asWindow[R : Order](buildWindow: R => Window[R]): ExprConverter[R, Window[R]] = - new Impl("asWindow", buildWindow) -} diff --git a/core/src/main/scala/vapors/algebra/ExprResult.scala b/core/src/main/scala/vapors/algebra/ExprResult.scala deleted file mode 100644 index 4ecbd5196..000000000 --- a/core/src/main/scala/vapors/algebra/ExprResult.scala +++ /dev/null @@ -1,384 +0,0 @@ -package com.rallyhealth - -package vapors.algebra - -import vapors.data.{FactSet, FactTable, TypedFact} -import vapors.interpreter.{ExprInput, ExprOutput} - -import cats._ -import cats.kernel.Monoid -import shapeless.HList - -import scala.collection.{BitSet, MapView} - -/** - * The result of evaluating an [[Expr]] with some given [[ExprInput]]. - * - * You can fold over this result to see the input, output, value, and captured custom parameter at each node in the - * original expression. Every node of this algebra matches to the [[Expr]] subclass with the same name. - */ -sealed trait ExprResult[V, R, P] { - - def context: ExprResult.Context[V, R, P] - - @inline final def input: ExprInput[V] = context.input - - @inline final def output: ExprOutput[R] = context.output - - @inline final def param: Eval[P] = context.param - - def convertOutputToInput: ExprInput[R] = input.withValue(output.value, output.evidence) - - def visit[G[_]](v: ExprResult.Visitor[V, P, G]): G[R] -} - -object ExprResult { - - final case class Context[V, R, P]( - input: ExprInput[V], - output: ExprOutput[R], - param: Eval[P], - ) - - trait Visitor[V, P, G[_]] extends (ExprResult[V, *, P] ~> G) { - // Please keep the following methods in alphabetical order - override final def apply[R](fa: ExprResult[V, R, P]): G[R] = fa.visit(this) - def visitAddOutputs[R](result: AddOutputs[V, R, P]): G[R] - def visitAnd[R](result: And[V, R, P]): G[R] - def visitCollectFromOutput[M[_] : Foldable, U, R : Monoid](result: CollectFromOutput[V, M, U, R, P]): G[R] - def visitConcatOutput[M[_] : MonoidK, R](result: ConcatOutput[V, M, R, P]): G[M[R]] - def visitConstOutput[R](result: ConstOutput[V, R, P]): G[R] - def visitCustomFunction[A, R](result: CustomFunction[V, A, R, P]): G[R] - def visitDeclare[M[_], T](result: Define[V, M, T, P]): G[FactSet] - def visitDivideOutputs[R](result: DivideOutputs[V, R, P]): G[R] - def visitEmbed[R](result: Embed[V, R, P]): G[R] - def visitExistsInOutput[M[_] : Foldable, U](result: ExistsInOutput[V, M, U, P]): G[Boolean] - def visitExponentiateOutputs(result: ExponentiateOutputs[V, P]): G[Double] - def visitFilterOutput[M[_] : Foldable : FunctorFilter, R](result: FilterOutput[V, M, R, P]): G[M[R]] - def visitFlatMapOutput[M[_], U, R](result: FlatMapOutput[V, M, U, R, P]): G[M[R]] - def visitGroupOutput[M[_] : Foldable, U : Order, K](result: GroupOutput[V, M, U, K, P]): G[MapView[K, Seq[U]]] - def visitMapOutput[M[_], U, R](result: MapOutput[V, M, U, R, P]): G[M[R]] - def visitMultiplyOutputs[R](result: MultiplyOutputs[V, R, P]): G[R] - def visitNegativeOutput[R](result: NegativeOutput[V, R, P]): G[R] - def visitNot[R](result: Not[V, R, P]): G[R] - def visitOr[R](result: Or[V, R, P]): G[R] - def visitOutputIsEmpty[M[_] : Foldable, R](result: OutputIsEmpty[V, M, R, P]): G[Boolean] - def visitOutputWithinSet[R](result: OutputWithinSet[V, R, P]): G[Boolean] - def visitOutputWithinWindow[R](result: OutputWithinWindow[V, R, P]): G[Boolean] - def visitFoldOutput[M[_] : Foldable, R : Monoid](result: FoldOutput[V, M, R, P]): G[R] - def visitReturnInput(result: ReturnInput[V, P]): G[V] - def visitSelectFromOutput[S, R](result: SelectFromOutput[V, S, R, P]): G[R] - def visitSortOutput[M[_], R](result: SortOutput[V, M, R, P]): G[M[R]] - def visitSubtractOutputs[R](result: SubtractOutputs[V, R, P]): G[R] - def visitTakeFromOutput[M[_] : Traverse : TraverseFilter, R](result: TakeFromOutput[V, M, R, P]): G[M[R]] - def visitUsingDefinitions[R](result: UsingDefinitions[V, R, P]): G[R] - def visitWhen[R](result: When[V, R, P]): G[R] - def visitWrapOutput[L, R](result: WrapOutput[V, L, R, P]): G[R] - def visitWrapOutputHList[T <: HList, R](result: WrapOutputHList[V, T, R, P]): G[R] - def visitWrapOutputSeq[R](result: WrapOutputSeq[V, R, P]): G[Seq[R]] - def visitWithFactsOfType[T, R](result: WithFactsOfType[V, T, R, P]): G[R] - def visitZipOutput[M[_] : Align : FunctorFilter, L <: HList, R](result: ZipOutput[V, M, L, R, P]): G[M[R]] - } - - /* - * Please keep the following expressions in the same order in Expr.scala and ExprResult.scala. - * It makes it easier to find the mirror node. - * - * While alphabetical seems like the best way to organize these classes, in practice - * certain nodes that are related to each other (like addition and subtraction) will have - * very similar implementations and will likely need to be refactored in identical ways. - * - * The Visitor and its subclasses use alphabetical sorting because their definitions - * are derivative and it is practically impossible to forget to update their implementation - * without the compiler complaining. - */ - - final case class ConstOutput[V, R, P]( - expr: Expr.ConstOutput[V, R, P], - context: Context[V, R, P], - ) extends ExprResult[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitConstOutput(this) - } - - final case class ReturnInput[V, P]( - expr: Expr.ReturnInput[V, P], - context: Context[V, V, P], - ) extends ExprResult[V, V, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[V] = v.visitReturnInput(this) - } - - final case class Embed[V, R, P]( - expr: Expr.Embed[V, R, P], - context: Context[V, R, P], - embeddedResult: ExprResult[FactTable, R, P], - ) extends ExprResult[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitEmbed(this) - } - - final case class CustomFunction[V, A, R, P]( - expr: Expr.CustomFunction[V, A, R, P], - context: Context[V, R, P], - argResult: ExprResult[V, A, P], - ) extends ExprResult[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitCustomFunction(this) - } - - final case class WithFactsOfType[V, T, R, P]( - expr: Expr.WithFactsOfType[T, R, P], - context: Context[V, R, P], - subResult: ExprResult[Seq[TypedFact[T]], R, P], - ) extends ExprResult[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitWithFactsOfType(this) - } - - final case class Define[V, M[_], T, P]( - expr: Expr.Define[M, T, P], - context: Context[V, FactSet, P], - definitionResult: ExprResult[FactTable, M[T], P], - ) extends ExprResult[V, FactSet, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[FactSet] = v.visitDeclare(this) - } - - final case class UsingDefinitions[V, R, P]( - expr: Expr.UsingDefinitions[V, R, P], - context: Context[V, R, P], - subResult: ExprResult[V, R, P], - ) extends ExprResult[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitUsingDefinitions(this) - } - - final case class And[V, R, P]( - expr: Expr.And[V, R, P], - context: Context[V, R, P], - subResultList: List[ExprResult[V, R, P]], - ) extends ExprResult[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitAnd(this) - } - - final case class Or[V, R, P]( - expr: Expr.Or[V, R, P], - context: Context[V, R, P], - subResultList: List[ExprResult[V, R, P]], - ) extends ExprResult[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitOr(this) - } - - final case class Not[V, R, P]( - expr: Expr.Not[V, R, P], - context: Context[V, R, P], - subResult: ExprResult[V, R, P], - ) extends ExprResult[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitNot(this) - } - - final case class When[V, R, P]( - expr: Expr.When[V, R, P], - context: Context[V, R, P], - matchedBranch: Option[ConditionBranch[V, R, P]], - subResult: ExprResult[V, R, P], - ) extends ExprResult[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitWhen(this) - val thenExpr: Expr[V, R, P] = matchedBranch.map(_.thenExpr).getOrElse(expr.defaultExpr) - } - - final case class SelectFromOutput[V, S, R, P]( - expr: Expr.SelectFromOutput[V, S, R, P], - context: Context[V, R, P], - inputResult: ExprResult[V, S, P], - ) extends ExprResult[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitSelectFromOutput(this) - } - - final case class FilterOutput[V, M[_] : Foldable : FunctorFilter, R, P]( - expr: Expr.FilterOutput[V, M, R, P], - context: Context[V, M[R], P], - inputResult: ExprResult[V, M[R], P], - condResultList: List[ExprResult[R, Boolean, P]], - ) extends ExprResult[V, M[R], P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[M[R]] = v.visitFilterOutput(this) - } - - final case class CollectFromOutput[V, M[_] : Foldable, U, R : Monoid, P]( - expr: Expr.CollectFromOutput[V, M, U, R, P], - context: Context[V, R, P], - inputResult: ExprResult[V, M[U], P], - ) extends ExprResult[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitCollectFromOutput(this) - } - - final case class FlatMapOutput[V, M[_], U, R, P]( - expr: Expr.FlatMapOutput[V, M, U, R, P], - context: Context[V, M[R], P], - inputResult: ExprResult[V, M[U], P], - subResultList: List[ExprResult[U, M[R], P]], - ) extends ExprResult[V, M[R], P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[M[R]] = v.visitFlatMapOutput(this) - } - - final case class MapOutput[V, M[_], U, R, P]( - expr: Expr.MapOutput[V, M, U, R, P], - context: Context[V, M[R], P], - inputResult: ExprResult[V, M[U], P], - subResultList: List[ExprResult[U, R, P]], - ) extends ExprResult[V, M[R], P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[M[R]] = v.visitMapOutput(this) - } - - final case class GroupOutput[V, M[_] : Foldable, U : Order, K, P]( - expr: Expr.GroupOutput[V, M, U, K, P], - context: Context[V, MapView[K, Seq[U]], P], - inputResult: ExprResult[V, M[U], P], - ) extends ExprResult[V, MapView[K, Seq[U]], P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[MapView[K, Seq[U]]] = v.visitGroupOutput(this) - } - - final case class SortOutput[V, M[_], R, P]( - expr: Expr.SortOutput[V, M, R, P], - context: Context[V, M[R], P], - inputResult: ExprResult[V, M[R], P], - ) extends ExprResult[V, M[R], P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[M[R]] = v.visitSortOutput(this) - } - - final case class ConcatOutput[V, M[_] : MonoidK, R, P]( - expr: Expr.ConcatOutput[V, M, R, P], - context: Context[V, M[R], P], - inputResultList: Seq[ExprResult[V, M[R], P]], - ) extends ExprResult[V, M[R], P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[M[R]] = v.visitConcatOutput(this) - } - - final case class FoldOutput[V, M[_] : Foldable, R : Monoid, P]( - expr: Expr.FoldOutput[V, M, R, P], - context: Context[V, R, P], - inputResult: ExprResult[V, M[R], P], - ) extends ExprResult[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitFoldOutput(this) - } - - final case class OutputIsEmpty[V, M[_] : Foldable, R, P]( - expr: Expr.OutputIsEmpty[V, M, R, P], - context: Context[V, Boolean, P], - inputResult: ExprResult[V, M[R], P], - ) extends ExprResult[V, Boolean, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[Boolean] = v.visitOutputIsEmpty(this) - } - - final case class TakeFromOutput[V, M[_] : Traverse : TraverseFilter, R, P]( - expr: Expr.TakeFromOutput[V, M, R, P], - context: Context[V, M[R], P], - inputResult: ExprResult[V, M[R], P], - ) extends ExprResult[V, M[R], P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[M[R]] = v.visitTakeFromOutput(this) - } - - final case class ExistsInOutput[V, M[_] : Foldable, U, P]( - expr: Expr.ExistsInOutput[V, M, U, P], - context: Context[V, Boolean, P], - inputResult: ExprResult[V, M[U], P], - conditionResultList: List[ExprResult[U, Boolean, P]], - matchedIndexes: BitSet, - ) extends ExprResult[V, Boolean, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[Boolean] = v.visitExistsInOutput(this) - } - - final case class AddOutputs[V, R, P]( - expr: Expr.AddOutputs[V, R, P], - context: Context[V, R, P], - subResultList: List[ExprResult[V, R, P]], - ) extends ExprResult[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitAddOutputs(this) - } - - final case class SubtractOutputs[V, R, P]( - expr: Expr.SubtractOutputs[V, R, P], - context: Context[V, R, P], - subResultList: List[ExprResult[V, R, P]], - ) extends ExprResult[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitSubtractOutputs(this) - } - - final case class MultiplyOutputs[V, R, P]( - expr: Expr.MultiplyOutputs[V, R, P], - context: Context[V, R, P], - subResultList: List[ExprResult[V, R, P]], - ) extends ExprResult[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitMultiplyOutputs(this) - } - - final case class DivideOutputs[V, R, P]( - expr: Expr.DivideOutputs[V, R, P], - context: Context[V, R, P], - subResultList: List[ExprResult[V, R, P]], - ) extends ExprResult[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitDivideOutputs(this) - } - - final case class ExponentiateOutputs[V, P]( - expr: Expr.ExponentiateOutputs[V, P], - context: Context[V, Double, P], - baseResult: ExprResult[V, Double, P], - exponentResult: ExprResult[V, Double, P], - ) extends ExprResult[V, Double, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[Double] = v.visitExponentiateOutputs(this) - } - - final case class WrapOutput[V, L, R, P]( - expr: Expr.WrapOutput[V, L, R, P], - context: Context[V, R, P], - inputResult: ExprResult[V, L, P], - ) extends ExprResult[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitWrapOutput(this) - } - - // TODO: This seems redundant with WrapOutput... maybe we can boil these "wrap" operations into - // a single operation... or a smaller subset. In essence these are all just a single function. - // The only major difference is their typeclass dependencies and how we serialize them, which - // could be baked into an operation metadata object to sit alongside a function. - final case class WrapOutputSeq[V, R, P]( - expr: Expr.WrapOutputSeq[V, R, P], - context: Context[V, Seq[R], P], - inputResultList: Seq[ExprResult[V, R, P]], - ) extends ExprResult[V, Seq[R], P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[Seq[R]] = v.visitWrapOutputSeq(this) - } - - final case class WrapOutputHList[V, T <: HList, R, P]( - expr: Expr.WrapOutputHList[V, T, R, P], - context: Context[V, R, P], - ) extends ExprResult[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitWrapOutputHList(this) - } - - final case class ZipOutput[V, M[_] : Align : FunctorFilter, L <: HList, R, P]( - expr: Expr.ZipOutput[V, M, L, R, P], - context: Context[V, M[R], P], - ) extends ExprResult[V, M[R], P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[M[R]] = v.visitZipOutput(this) - } - - final case class NegativeOutput[V, R, P]( - expr: Expr.NegativeOutput[V, R, P], - context: Context[V, R, P], - inputResult: ExprResult[V, R, P], - ) extends ExprResult[V, R, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[R] = v.visitNegativeOutput(this) - } - - final case class OutputWithinSet[V, R, P]( - expr: Expr.OutputWithinSet[V, R, P], - context: Context[V, Boolean, P], - inputResult: ExprResult[V, R, P], - ) extends ExprResult[V, Boolean, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[Boolean] = v.visitOutputWithinSet(this) - } - - final case class OutputWithinWindow[V, R, P]( - expr: Expr.OutputWithinWindow[V, R, P], - context: Context[V, Boolean, P], - inputResult: ExprResult[V, R, P], - ) extends ExprResult[V, Boolean, P] { - override def visit[G[_]](v: Visitor[V, P, G]): G[Boolean] = v.visitOutputWithinWindow(this) - } - -} diff --git a/core/src/main/scala/vapors/algebra/ExprSorter.scala b/core/src/main/scala/vapors/algebra/ExprSorter.scala deleted file mode 100644 index 80f6d8db3..000000000 --- a/core/src/main/scala/vapors/algebra/ExprSorter.scala +++ /dev/null @@ -1,59 +0,0 @@ -package com.rallyhealth - -package vapors.algebra - -import vapors.lens.NamedLens - -import cats.Order -import cats.syntax.contravariant._ -import cats.syntax.show._ - -import scala.collection.Factory -import scala.reflect.runtime.universe.{typeOf, TypeTag} - -/** - * Defines a closed set of operations for sorting elements of the given arity-1 type constructor. - * - * This is basically like an [[Order]], except it exists only for serialization and debugging purposes. - */ -sealed trait ExprSorter[M[_], R] extends (M[R] => M[R]) { - - /** - * A description of how the elements are sorted for serialization and debugging purposes. - */ - def sortDescription: String - - /** - * Sort the given collection. - */ - override def apply(collection: M[R]): M[R] -} - -object ExprSorter { - - private final class Impl[M[_], R]( - override val sortDescription: String, - op: M[R] => M[R], - ) extends ExprSorter[M, R] { - override def apply(collection: M[R]): M[R] = op(collection) - } - - def byNaturalOrder[M[_], R : Order : TypeTag]( - implicit - ev: M[R] <:< Seq[R], - factory: Factory[R, M[R]], - ): ExprSorter[M, R] = - new Impl(s"natural order of ${typeOf[R]}", _.sorted(Order[R].toOrdering).to(factory)) - - def byField[M[_], S : TypeTag, R : Order]( - lens: NamedLens[S, R], - )(implicit - ev: M[S] <:< Seq[S], - factory: Factory[S, M[S]], - ): ExprSorter[M, S] = - new Impl( - s"sort using ${typeOf[S]}${lens.path.show}", - _.sorted(Order[R].contramap(lens.get).toOrdering).to(factory), - ) - -} diff --git a/core/src/main/scala/vapors/algebra/HExprList.scala b/core/src/main/scala/vapors/algebra/HExprList.scala deleted file mode 100644 index 1fa409958..000000000 --- a/core/src/main/scala/vapors/algebra/HExprList.scala +++ /dev/null @@ -1,134 +0,0 @@ -package com.rallyhealth - -package vapors.algebra - -import cats.data.Ior -import cats.syntax.all._ -import cats.{Align, Functor, FunctorFilter, Id, Semigroupal} -import shapeless.{::, HList, HNil} - -/** - * A helpful short-hand for an HList of expressions that all share the same input, effect, and parameter types. - * - * This not only provides syntactic sugar by fixing some of the type arguments to avoid repetition, it also - * provides implementation for some recursive operations that produce HList outputs. - */ -sealed trait NonEmptyExprHList[V, M[_], L <: HList, P] { - - /** - * Prepends an expression that accepts the same input and outputs a value wrapped in the same effect as - * all the other expressions. - */ - def ::[H](other: Expr[V, M[H], P]): NonEmptyExprHList[V, M, H :: L, P] = - new ExprHCons[V, M, H, L, P](other, this) - - /** - * Visits each expression in the HList, applies the given visitor, and combines the results by applying a - * product operation on the effectful container of results to produce every possible combination of HList - * from the given input. - * - * @note If given the [[Id]] effect, this is equivalent to mapping over each given expression to produce - * an expression that takes the same input and produces a single HList combining every result type. - * - * @example NonEmptyExprHList.of(const("A"), const(1)).visitProduct(g) == const("A" :: 1 :: HNil).visit(g) - * - * @note If given a [[List]], this will combine the resulting lists of each expression into a list of - * every permutation of HList that can be produced from the output lists. - * - * @example NonEmptyExprHList.of(const(List("A", "B")), const(List(1, 2))).visitProduct(g) == const( - * List("A" :: 1 :: HNil, "A" :: 2 :: HNil, "B" :: 1 :: HNil, "C" :: 2 :: HNil) - * ).visit(g) - * - * @example NonEmptyExprHList.of(const(List("A")), const(Nil)).visitProduct(g) == const(Nil).visit(g) - */ - def visitProduct[G[_] : Functor : Semigroupal]( - v: Expr.Visitor[V, P, G], - )(implicit - functorM: Functor[M], - semigroupalM: Semigroupal[M], - ): G[M[L]] - - /** - * Visits each expression in the HList, applies the given visitor, zips over every element in the resulting - * functor-wrapped values (stopping at the length of the first sequence to run out of elements), and then - * combines the elements into an HList. - * - * @example NonEmptyExprHList.of(const(List("A", "B", "C"), const(List(1, 2, 3, 4, 5))).visitZippedToShortest(g) == - * const(List("A" :: 1 :: HNil, "B" :: 2 :: HNil, "C" :: 3 :: HNil)).visit(g) - * - * @example NonEmptyExprHList.of(const(List("A", "B", "C"), const(Nil)).visitZippedToShortest(g) == - * const(Nil).visit(g) - */ - def visitZippedToShortest[G[_] : Functor : Semigroupal]( - v: Expr.Visitor[V, P, G], - )(implicit - alignM: Align[M], - filterM: FunctorFilter[M], - ): G[M[L]] -} - -object NonEmptyExprHList { - - def tail[V, R, P](expr: Expr[V, R, P]): ExprLast[V, Id, R, P] = - ExprLast[V, Id, R, P](expr) - - def tailK[V, M[_], R, P](expr: Expr[V, M[R], P]): ExprLast[V, M, R, P] = - ExprLast(expr) -} - -final case class ExprHCons[V, M[_], H, T <: HList, P]( - head: Expr[V, M[H], P], - tail: NonEmptyExprHList[V, M, T, P], -) extends NonEmptyExprHList[V, M, H :: T, P] { - - override def visitProduct[G[_] : Functor : Semigroupal]( - v: Expr.Visitor[V, P, G], - )(implicit - functorM: Functor[M], - semigroupalM: Semigroupal[M], - ): G[M[H :: T]] = { - Semigroupal[G].product(head.visit(v), tail.visitProduct(v)).map { - _.mapN(_ :: _) - } - } - - override def visitZippedToShortest[G[_] : Functor : Semigroupal]( - v: Expr.Visitor[V, P, G], - )(implicit - alignM: Align[M], - filterM: FunctorFilter[M], - ): G[M[H :: T]] = { - Semigroupal[G].product(head.visit(v), tail.visitZippedToShortest(v)).map { - case (hs, ts) => - Align[M] - .alignWith(hs, ts) { - case Ior.Both(h, t) => Some(h :: t) - case _ => None - } - .collect { - case Some(hlist) => hlist - } - } - } -} - -final case class ExprLast[V, M[_], H, P](last: Expr[V, M[H], P]) extends NonEmptyExprHList[V, M, H :: HNil, P] { - - override def visitProduct[G[_] : Functor : Semigroupal]( - v: Expr.Visitor[V, P, G], - )(implicit - functorM: Functor[M], - semigroupalM: Semigroupal[M], - ): G[M[H :: HNil]] = { - last.visit(v).map(_.map(_ :: HNil)) - } - - override def visitZippedToShortest[G[_] : Functor : Semigroupal]( - v: Expr.Visitor[V, P, G], - )(implicit - alignM: Align[M], - filterM: FunctorFilter[M], - ): G[M[H :: HNil]] = { - last.visit(v).map(filterM.functor.map(_)(_ :: HNil)) - } -} diff --git a/core/src/main/scala/vapors/data/Bounded.scala b/core/src/main/scala/vapors/data/Bounded.scala deleted file mode 100644 index 7315c9f08..000000000 --- a/core/src/main/scala/vapors/data/Bounded.scala +++ /dev/null @@ -1,36 +0,0 @@ -package com.rallyhealth - -package vapors.data - -import cats.Functor - -/** - * Represents a boundary on a 1-dimensional line at a specific position. - * - * It has two directions: [[Bounded.Below]] and [[Bounded.Above]]. - */ -sealed trait Bounded[A] - -object Bounded { - - final case class Above[A]( - lowerBound: A, - inclusiveLowerBound: Boolean, - ) extends Bounded[A] - - final case class Below[A]( - upperBound: A, - inclusiveUpperBound: Boolean, - ) extends Bounded[A] - - implicit final object AboveFunctorLike extends Functor[Above] { - override def map[A, B](fa: Above[A])(f: A => B): Above[B] = - fa.copy[B](lowerBound = f(fa.lowerBound)) - } - - implicit final object BelowFunctorLike extends Functor[Below] { - override def map[A, B](fa: Below[A])(f: A => B): Below[B] = - fa.copy[B](upperBound = f(fa.upperBound)) - } - -} diff --git a/core/src/main/scala/vapors/data/Evidence.scala b/core/src/main/scala/vapors/data/Evidence.scala deleted file mode 100644 index 521057e46..000000000 --- a/core/src/main/scala/vapors/data/Evidence.scala +++ /dev/null @@ -1,125 +0,0 @@ -package com.rallyhealth - -package vapors.data - -import cats.Monoid -import cats.data.NonEmptySet -import cats.instances.order._ - -import scala.annotation.tailrec -import scala.collection.immutable.SortedSet - -/** - * A set of [[Fact]]s used to derive an evaluated expression result. - * - * If the resulting evidence is empty, then you should treat the value as identical to its negation. - * - * TODO: there are some bugs with evidence tracking of collection-level operations and how constants - * are handled, so you probably shouldn't rely on this right now. - */ -final class Evidence private (val factSet: Set[Fact]) extends AnyVal { - - def isEmpty: Boolean = factSet.isEmpty - def nonEmpty: Boolean = factSet.nonEmpty - - @inline def ++(that: Evidence): Evidence = union(that) - @inline def |(that: Evidence): Evidence = union(that) - - def ofType[T](factTypeSet: FactTypeSet[T]): Option[NonEmptySet[TypedFact[T]]] = { - val matchingFacts = SortedSet.from(this.factSet.iterator.collect(factTypeSet.collector)) - NonEmptySet.fromSet(matchingFacts) - } - - /** - * Union of the two sets of results. - */ - def union(that: Evidence): Evidence = this.factSet match { - case Evidence.none.factSet | that.factSet => that - case _ => Evidence(this.factSet | that.factSet) - } - - @inline def &(that: Evidence): Evidence = combineNonEmpty(that) - - /** - * If either side contains empty evidence then the result contains no evidence. - */ - def combineNonEmpty(that: Evidence): Evidence = { - if (this.isEmpty || that.isEmpty) Evidence.none - else this.union(that) - } - - def derivedFromSources: Evidence = { - @tailrec def loop( - mixed: Iterable[Fact], - source: FactSet, - ): FactSet = { - if (mixed.isEmpty) source - else { - val (remainingEvidence, sourceFacts) = mixed.partitionMap { - case DerivedFact(_, _, evidence) => Left(evidence.factSet) - case source => Right(source) - } - loop(remainingEvidence.flatten, source ++ sourceFacts) - } - } - val allSourceFacts = loop(this.factSet, FactSet.empty) - new Evidence(allSourceFacts) - } - - override def toString: String = - if (this.factSet.isEmpty) "Evidence()" - else s"Evidence${factSet.mkString("(", ", ", ")")}" -} - -object Evidence { - - def unapply(evidence: Evidence): Some[Set[Fact]] = Some(evidence.factSet) - - @inline final def apply(facts: FactOrFactSet*): Evidence = { - if (facts.isEmpty) none - else new Evidence(FactOrFactSet.flatten(facts)) - } - - final val none = new Evidence(Set.empty) - - /** - * Convert any given value into [[Evidence]] by inspecting whether it is a fact or valid collection of facts. - * - * This is used by the library when iterating over a collection of facts, where the facts can be used as their own - * evidence in the subexpression. - */ - def fromAny(any: Any): Option[Evidence] = any match { - case ev: Evidence => Some(ev) - case fact: Fact => Some(Evidence(fact)) - case map: collection.Map[_, _] => fromAnyIterable(map.valuesIterator) - case iter: IterableOnce[_] => fromAnyIterable(iter) - case _ => None - } - - @inline def fromAnyOrNone(any: Any): Evidence = fromAny(any).getOrElse(none) - - private[this] def fromAnyIterable(anyIter: IterableOnce[_]): Option[Evidence] = { - val iter = anyIter.iterator - if (iter.isEmpty) None - else Some(Evidence(FactSet.from(iter.collect { case fact: Fact => fact }))) - } - - /** - * Unions evidence as a standard definition for monoid. - * - * This is the right behavior to satify [[Monoid]], however if you need to do evidence tracking, - * you wlil probably want to be more specific if you need to represent the behavior of merging - * evidence from the output of various expressions (or their input). - * - * @see [[InterpretExprAsResultFn.Output.monoid]] - */ - implicit val monoid: Monoid[Evidence] = { - new Monoid[Evidence] { - override def empty: Evidence = Evidence.none - override def combine( - x: Evidence, - y: Evidence, - ): Evidence = x.union(y) - } - } -} diff --git a/core/src/main/scala/vapors/data/ExtractInstant.scala b/core/src/main/scala/vapors/data/ExtractInstant.scala deleted file mode 100644 index 6bd2d4994..000000000 --- a/core/src/main/scala/vapors/data/ExtractInstant.scala +++ /dev/null @@ -1,29 +0,0 @@ -package com.rallyhealth - -package vapors.data - -import java.time._ - -/** - * Special case of [[ExtractValue]] that extracts an [[Instant]]. - * - * TODO: This should be replaced by a type alias to [[ExtractValue]] in the same way [[ExtractBoolean]] was. - */ -trait ExtractInstant[-T] extends ExtractValue[T, Instant] - -object ExtractInstant { - - def apply[T](implicit instance: ExtractInstant[T]): ExtractInstant[T] = instance - - def from[T, V : ExtractInstant](extractField: T => V): ExtractInstant[T] = { obj => - ExtractInstant[V].extractValue(extractField(obj)) - } - - implicit def instantFromLocalDate(implicit zone: ZoneId): ExtractInstant[LocalDate] = _.atStartOfDay(zone).toInstant - - implicit def instantFromLocalDateTime(implicit zone: ZoneId): ExtractInstant[LocalDateTime] = _.atZone(zone).toInstant - - implicit val instantFromZonedDate: ExtractInstant[ZonedDateTime] = _.toInstant - - implicit val instantFromZonedDateTime: ExtractInstant[ZonedDateTime] = _.toInstant -} diff --git a/core/src/main/scala/vapors/data/ExtractValue.scala b/core/src/main/scala/vapors/data/ExtractValue.scala deleted file mode 100644 index 71b111fbe..000000000 --- a/core/src/main/scala/vapors/data/ExtractValue.scala +++ /dev/null @@ -1,32 +0,0 @@ -package com.rallyhealth - -package vapors.data - -/** - * Extracts a value from some starting value. - * - * This is useful for defining [[CaptureP]] based on some input - * type, without having to share a common supertype. Although, because of variance rules, this makes it - * easy to define an [[ExtractValue]] for a supertype, if desired. - */ -trait ExtractValue[-T, +V] { - def extractValue(obj: T): V -} - -object ExtractValue { - @inline final def apply[V]: Of[V] = new Of[V] - final class Of[V] private[ExtractValue] (private val dummy: Boolean = true) extends AnyVal { - - def apply[T](input: T)(implicit extractor: ExtractValue[T, V]): V = extractor.extractValue(input) - - def from[T](implicit extractor: ExtractValue[T, V]): ExtractValue[T, V] = extractor - } - - implicit def id[T]: ExtractValue[T, T] = identity[T] - - implicit class ExtractBooleanOps[T](private val extractBoolean: ExtractBoolean[T]) extends AnyVal { - - @deprecated("Use ExtractValue[Boolean](value) or .extractValue(value) instead.", "0.14.1") - def isTrue(value: T): Boolean = extractBoolean.extractValue(value) - } -} diff --git a/core/src/main/scala/vapors/data/Fact.scala b/core/src/main/scala/vapors/data/Fact.scala deleted file mode 100644 index c41d13ba2..000000000 --- a/core/src/main/scala/vapors/data/Fact.scala +++ /dev/null @@ -1,162 +0,0 @@ -package com.rallyhealth - -package vapors.data - -import vapors.lens.{DataPath, NamedLens} - -import cats.Order -import cats.syntax.all._ - -/** - * An untyped fact for putting many different facts into collections without causing issues - * with variance. - * - * Typically, you will work with [[TypedFact]]s. - * - * The [[FactType]] and `value` inside share the same type member [[Value]]. - */ -sealed abstract class Fact { - type Value - - val typeInfo: FactType[Value] - val value: Value -} - -object Fact { - - /** - * Builds a fact given a [[FactType]] and a `value` of the same type. - */ - def apply[T]( - factType: FactType[T], - value: T, - ): Fact = SourceFactOfType(factType, value) - - def unapply(fact: Fact): Some[(FactType[fact.Value], fact.Value)] = Some((fact.typeInfo, fact.value)) - - val orderByFactValue: Order[Fact] = { (x, y) => - def maybeXCompared = - x.typeInfo - .cast(y) - .map(yAsX => x.typeInfo.order.compare(x.value, yAsX.value)) - - def maybeYCompared = - y.typeInfo - .cast(x) - .map(xAsY => y.typeInfo.order.compare(xAsY.value, y.value)) - - def fallbackToCompareAsStrings = - if (x.value == y.value) 0 - else Order[String].compare(x.value.toString, y.value.toString) - - maybeXCompared.orElse(maybeYCompared).getOrElse(fallbackToCompareAsStrings) - } - - def orderByFactName(nameOrder: Order[String]): Order[Fact] = { - nameOrder.contramap(_.typeInfo.name) - } - - // This will be used a lot, so cache it - private final val defaultOrder: Order[Fact] = orderByFactName(Order[String]) - - implicit def order(implicit orderFactNames: Order[String]): Order[Fact] = { - if (orderFactNames == cats.instances.string.catsKernelStdOrderForString) defaultOrder - else Order.whenEqual(orderByFactName(orderFactNames), orderByFactValue) - } -} - -/** - * A typed [[Fact]], for when you know the type of fact or need to know it. - * - * @see [[Fact]] - */ -sealed trait TypedFact[A] extends Fact { - type Value = A -} - -object TypedFact { - - def apply[A]( - typeInfo: FactType[A], - value: A, - ): TypedFact[A] = SourceFactOfType(typeInfo, value) - - def apply[A]( - typeInfo: FactType[A], - value: A, - evidence: Evidence, - ): TypedFact[A] with DerivedFact = DerivedFactOfType(typeInfo, value, evidence) - - def unapply[A](fact: TypedFact[A]): Some[(FactType[A], A)] = fact match { - case SourceFactOfType(typeInfo, value) => Some((typeInfo, value)) - case DerivedFactOfType(typeInfo, value, _) => Some((typeInfo, value)) - } - - def orderTypedFactByValue[T]: Order[TypedFact[T]] = { (x, y) => - x.typeInfo.order.compare(x.value, y.value) - } - - implicit def orderFactByNameThenValue[T](implicit orderFactNames: Order[String]): Order[TypedFact[T]] = { (x, y) => - orderFactNames.compare(x.typeInfo.name, y.typeInfo.name) match { - case 0 => orderTypedFactByValue[T].compare(x, y) - case orderByName => orderByName - } - } - - def lens[A]: NamedLens.Id[TypedFact[A]] = NamedLens.id[TypedFact[A]] - - def value[A]: NamedLens[TypedFact[A], A] = { - NamedLens[TypedFact[A], A](DataPath.empty.atField("value"), _.value) - } -} - -/** - * A [[Fact]] this is derived from other [[Fact]]s via an expression. - * - * The [[Evidence]] is tracked from the original set of [[Fact]]s based on the expression. - */ -sealed trait DerivedFact extends Fact { - def evidence: Evidence -} - -object DerivedFact { - - def apply[A]( - typeInfo: FactType[A], - value: A, - evidence: Evidence, - ): DerivedFact = - DerivedFactOfType(typeInfo, value, evidence) - - def unapply(fact: Fact): Option[(FactType[fact.Value], fact.Value, Evidence)] = fact match { - case DerivedFactOfType(_, _, evidence) => - Some((fact.typeInfo, fact.value, evidence)) - case _ => None - } -} - -/** - * A [[TypedFact]] that is provided in the initial [[FactTable]] by the caller. - */ -final case class SourceFactOfType[A]( - typeInfo: FactType[A], - value: A, -) extends TypedFact[A] { - - override def toString: String = s"SourceFact(${typeInfo.fullName} = $value)" -} - -/** - * A [[TypedFact]] that is derived from the initial [[FactTable]] by some interpreted expression. - * - * @see [[DerivedFact]] - */ -final case class DerivedFactOfType[A]( - typeInfo: FactType[A], - value: A, - evidence: Evidence, -) extends TypedFact[A] - with DerivedFact { - - override def toString: String = s"DerivedFact(${typeInfo.fullName} = $value, evidence = $evidence)" -} diff --git a/core/src/main/scala/vapors/data/FactOrFactSet.scala b/core/src/main/scala/vapors/data/FactOrFactSet.scala deleted file mode 100644 index c3202fa21..000000000 --- a/core/src/main/scala/vapors/data/FactOrFactSet.scala +++ /dev/null @@ -1,20 +0,0 @@ -package com.rallyhealth - -package vapors.data - -/** - * A magnet type for passing individual [[Fact]]s and collections of [[Fact]]s into the same variable argument list. - */ -final class FactOrFactSet private[FactOrFactSet] (val toSet: Set[Fact]) extends AnyVal - -object FactOrFactSet { - - implicit def setOfOneFact(fact: Fact): FactOrFactSet = new FactOrFactSet(Set(fact)) - - implicit def iterableSetOfFacts(facts: Iterable[Fact]): FactOrFactSet = - new FactOrFactSet(Set.from(facts)) - - def flatten(factOrFactSets: Iterable[FactOrFactSet]): FactSet = { - factOrFactSets.foldLeft(FactSet.empty)(_ | _.toSet) - } -} diff --git a/core/src/main/scala/vapors/data/FactSet.scala b/core/src/main/scala/vapors/data/FactSet.scala deleted file mode 100644 index 2086bf5d8..000000000 --- a/core/src/main/scala/vapors/data/FactSet.scala +++ /dev/null @@ -1,33 +0,0 @@ -package com.rallyhealth - -package vapors.data - -import cats.Foldable - -/** - * @see [[FactSet]] - */ -object FactSet { - - final val empty: FactSet = Set.empty[Fact] - - @inline final def apply(facts: Fact*): FactSet = Set.from(facts) - - @inline final def from(facts: IterableOnce[Fact]): FactSet = Set.from(facts) - - @inline final def fromFoldable[F[_] : Foldable](facts: F[Fact]): FactSet = { - Set.from(Foldable[F].toIterable(facts)) - } -} - -/** - * @see [[TypedFactSet]] - */ -object TypedFactSet { - - @inline final def empty[T]: TypedFactSet[T] = Set.empty[TypedFact[T]] - - @inline final def apply[T](facts: TypedFact[T]*): TypedFactSet[T] = Set.from(facts) - - @inline final def from[T](facts: Iterable[TypedFact[T]]): TypedFactSet[T] = Set.from(facts) -} diff --git a/core/src/main/scala/vapors/data/FactTable.scala b/core/src/main/scala/vapors/data/FactTable.scala deleted file mode 100644 index fd70ed5ad..000000000 --- a/core/src/main/scala/vapors/data/FactTable.scala +++ /dev/null @@ -1,79 +0,0 @@ -package com.rallyhealth - -package vapors.data - -import vapors.lens.Indexed - -import cats.instances.order._ -import cats.{Eq, Monoid} - -import scala.collection.immutable.SortedMap - -/** - * The current state of all the facts in an expression. - * - * @note some expressions can update the fact table for sub-expressions. - */ -final case class FactTable(factsByName: SortedMap[String, FactSet]) extends AnyVal { - - def add(fact: Fact): FactTable = addAll(FactSet(fact)) - - def addAll(facts: Iterable[Fact]): FactTable = { - import cats.syntax.semigroup._ - val newFactTable = FactTable(facts) - new FactTable(this.factsByName.combine(newFactTable.factsByName)) - } - - def getSortedSeq[T](factTypeSet: FactTypeSet[T]): IndexedSeq[TypedFact[T]] = { - val sortedArray = getSet(factTypeSet).toArray.sortInPlace() - sortedArray.toIndexedSeq - } - - def getSet[T](factTypeSet: FactTypeSet[T]): TypedFactSet[T] = { - val matchingFacts = for { - factName <- factTypeSet.typeMap.toSortedMap.keys - matchingFactsByName <- this.factsByName.get(factName) - } yield matchingFactsByName.collect { - case factTypeSet(matchingByType) => matchingByType - } - matchingFacts.reduceOption(_ | _).map(TypedFactSet.from).getOrElse(Set.empty) - } -} - -object FactTable { - - final val empty = new FactTable(SortedMap.empty) - - def apply(facts: FactOrFactSet*): FactTable = { - val factSet = FactOrFactSet.flatten(facts) - if (factSet.isEmpty) empty - else new FactTable(SortedMap.from(factSet.groupBy(_.typeInfo.fullName))) - } - - implicit object MonoidInstance extends Monoid[FactTable] { - override def empty: FactTable = FactTable.empty - override def combine( - x: FactTable, - y: FactTable, - ): FactTable = { - import cats.syntax.semigroup._ - new FactTable(x.factsByName |+| y.factsByName) - } - } - - implicit val eq: Eq[FactTable] = Eq.fromUniversalEquals - - implicit def indexedByFactType[T]: Indexed[FactTable, FactType[T], TypedFactSet[T]] = { - new Indexed[FactTable, FactType[T], TypedFactSet[T]] { - override def get(container: FactTable)(key: FactType[T]): TypedFactSet[T] = { - container.getSet(key) - } - } - } - - implicit def indexedByFactTypeSet[T]: Indexed[FactTable, FactTypeSet[T], TypedFactSet[T]] = { - new Indexed[FactTable, FactTypeSet[T], TypedFactSet[T]] { - override def get(container: FactTable)(key: FactTypeSet[T]): TypedFactSet[T] = container.getSet(key) - } - } -} diff --git a/core/src/main/scala/vapors/data/FactType.scala b/core/src/main/scala/vapors/data/FactType.scala deleted file mode 100644 index c315fc98a..000000000 --- a/core/src/main/scala/vapors/data/FactType.scala +++ /dev/null @@ -1,104 +0,0 @@ -package com.rallyhealth - -package vapors.data - -import vapors.lens.{NamedLens, ValidDataPathKey} - -import cats.Order - -import scala.reflect.ClassTag -import scala.reflect.runtime.universe.TypeTag - -/** - * Type information required to construct a [[Fact]]. - */ -trait FactType[T] extends (T => TypedFact[T]) { - - /** - * The unique name for this fact type. - */ - def name: String - - /** - * Definition for how to order fact values. - */ - def order: Order[T] - - /** - * The unique type name plus type information. - */ - final lazy val fullName: String = s"'$name' as ${tt.tpe}" - - protected[vapors] implicit val ct: ClassTag[T] - protected[vapors] implicit val tt: TypeTag[T] - - /** - * Validates the value is the correct type, but packages it as an [[Fact]] to avoid - * issues with the compiler attempting to find the LUB of the [[TypedFact]] type parameter using - * a wildcard. - * - * If you need the specific type of fact for some reason, you can use the standard [[TypedFact]] - * factory method and supply this fact type as the first parameter. - */ - override def apply(value: T): TypedFact[T] = TypedFact(this, value) - - def unapply(value: Fact): Option[TypedFact[T]] = cast(value) - - /** - * Safely cast the given fact to this type. - */ - def cast(fact: Fact): Option[TypedFact[T]] = { - fact match { - // Justification: This checks validity of the FactType and safely casts the fact value using a class tag - case f @ TypedFact(ft, _: T) if ft.tt.tpe <:< this.tt.tpe => - Some(f.asInstanceOf[TypedFact[T]]) - case f: Fact if f.typeInfo.tt.tpe <:< this.tt.tpe => - f.value match { - case v: T => Some(TypedFact[T](f.typeInfo.asInstanceOf[FactType[T]], v)) - case _ => None - } - case _ => None - } - } - - /** - * Defines FactType equality as "both types have the same name and compiled type." - */ - override final def equals(o: Any): Boolean = o match { - case that: FactType[_] => - this.fullName == that.fullName && this.tt.tpe =:= that.tt.tpe - case _ => false - } - - override final def hashCode: Int = this.fullName.hashCode() - - def productPrefix: String = "CustomFactType" - - override def toString: String = s"$productPrefix($fullName)" -} - -object FactType { - - def apply[T : ClassTag : TypeTag : Order](name: String): FactType[T] = Simple(name) - - private final case class Simple[T : ClassTag : TypeTag : Order](name: String) extends FactType[T] { - override val order: Order[T] = Order[T] - override protected[vapors] val ct: ClassTag[T] = implicitly - override protected[vapors] val tt: TypeTag[T] = implicitly - override def productPrefix: String = "FactType" - override def productElementName(n: Int): String = n match { - case 0 => "name" - case _ => super.productElementName(n) - } - } - - implicit class ToLens[T](private val ft: FactType[T]) extends AnyVal { - def lens: NamedLens[TypedFact[T], T] = TypedFact.value[T] - } - - implicit def orderingByName[T]: Ordering[FactType[T]] = Ordering.by(_.fullName) - - implicit def orderByName[T]: Order[FactType[T]] = Order.by(_.fullName) - - implicit def validDataPathKey[T]: ValidDataPathKey[FactType[T]] = _.fullName -} diff --git a/core/src/main/scala/vapors/data/FactTypeSet.scala b/core/src/main/scala/vapors/data/FactTypeSet.scala deleted file mode 100644 index 9d7f6801e..000000000 --- a/core/src/main/scala/vapors/data/FactTypeSet.scala +++ /dev/null @@ -1,87 +0,0 @@ -package com.rallyhealth - -package vapors.data - -import cats.data.{NonEmptyList, NonEmptyMap, NonEmptySet} - -import scala.collection.immutable.{SortedMap, SortedSet} - -// TODO: Is there a better way to do this? -// Maybe we can use something like the Has[_] data type to combine multiple fact types? - -/** - * A set of [[FactType]]s that all share a common Scala type. - * - * This can be used to safely cast facts into a required type (lower-bounded by the type parameter of this object). - * - * @param typeMap a map of type names to [[FactType]]s, used for constant-time look-up - */ -final case class FactTypeSet[A] private (typeMap: NonEmptyMap[String, FactType[A]]) extends AnyVal { - - def typeList: NonEmptyList[FactType[A]] = NonEmptyList.fromListUnsafe(typeMap.toSortedMap.values.toList) - - def typeSet: NonEmptySet[FactType[A]] = NonEmptySet.fromSetUnsafe(SortedSet.from(typeMap.toSortedMap.values)) - - def unapply(fact: Fact): Option[TypedFact[A]] = collector.lift(fact) - - /** - * Checks the [[FactType]] for equality with one of the types in this set. - * - * @return the [[TypedFact]] as the expected type. - */ - def cast(fact: Fact): Option[TypedFact[A]] = collector.lift(fact) - - /** - * - * @return a partial function for collecting facts of a specific type - */ - def collector: PartialFunction[Fact, TypedFact[A]] = { - // Justification: This checks equality of the FactType at runtime, so it should be safe to cast - case fact @ TypedFact(factType, _) if typeMap(factType.fullName).contains(factType) => - fact.asInstanceOf[TypedFact[A]] - } - -} - -object FactTypeSet { - - /** Automatically wrap a single [[FactType]] into a [[FactTypeSet]] */ - implicit def one[A](factType: FactType[A]): FactTypeSet[A] = of(factType) - - /** Build a [[FactTypeSet]] from multiple [[FactType]]s that all share the same Scala type */ - def of[A]( - one: FactType[A], - others: FactType[A]*, - ): FactTypeSet[A] = - new FactTypeSet(NonEmptyMap.of(one.fullName -> one, others.map(a => (a.fullName, a)): _*)) - - def fromFactsNel[A](facts: NonEmptyList[TypedFact[A]]): FactTypeSet[A] = fromNel(facts.map(_.typeInfo)) - - def fromNel[A](types: NonEmptyList[FactType[A]]): FactTypeSet[A] = of(types.head, types.tail: _*) - - def fromNes[A](types: NonEmptySet[FactType[A]]): FactTypeSet[A] = of(types.head, types.tail.toSeq: _*) - - def validateSet[A](types: Set[FactType[A]]): Either[String, FactTypeSet[A]] = validateList(types.toList) - - def validateList[A](types: List[FactType[A]]): Either[String, FactTypeSet[A]] = { - val m = types.groupBy(_.fullName) - val (duplicates, uniqueTypes) = m.partitionMap { - case (name, one :: Nil) => Right((name, one)) - case (name, tooMany) => Left((name, tooMany)) - } - if (duplicates.isEmpty) { - NonEmptyMap - .fromMap(SortedMap.from(uniqueTypes)) - .map(new FactTypeSet(_)) - .toRight("types list cannot be empty.") - } else { - Left( - s"types list contains duplicate fact types: ${duplicates.map(_._1).mkString("'", "', '", "'")}", - ) - } - } - - def fromListOrThrow[A](types: List[FactType[A]]): FactTypeSet[A] = { - validateList(types).fold(message => throw new IllegalArgumentException(message), identity) - } -} diff --git a/core/src/main/scala/vapors/data/TimeOrder.scala b/core/src/main/scala/vapors/data/TimeOrder.scala deleted file mode 100644 index 70eafa161..000000000 --- a/core/src/main/scala/vapors/data/TimeOrder.scala +++ /dev/null @@ -1,15 +0,0 @@ -package com.rallyhealth - -package vapors.data - -object TimeOrder { - - @deprecated("This is confusingly named, please use com.rallyhealth.vapors.v1.data.YoungestFirst instead", "1.0.0") - final val LatestFirst = vapors.v1.data.TimeOrder.YoungestFirst - - @deprecated( - "This was implemented incorrectly, please use com.rallyhealth.vapors.v1.data.OldestFirst for the correct behavior", - "1.0.0", - ) - final val EarliestFirst = vapors.v1.data.TimeOrder.YoungestFirst -} diff --git a/core/src/main/scala/vapors/data/Window.scala b/core/src/main/scala/vapors/data/Window.scala deleted file mode 100644 index 759f41021..000000000 --- a/core/src/main/scala/vapors/data/Window.scala +++ /dev/null @@ -1,148 +0,0 @@ -package com.rallyhealth - -package vapors.data - -import alleycats.Empty -import cats.data.Ior -import cats.{Invariant, Order, Show} - -import scala.collection.immutable.NumericRange - -/** - * Represents an open or closed range with either 1 or 2 boundaries. - * - * A window can be bounded from below or above or both, and it can tell if a value is contained within - * those boundaries. - */ -trait Window[A] { - def order: Order[A] - def contains(value: A): Boolean - def bounds: Ior[Bounded.Above[A], Bounded.Below[A]] -} - -object Window { - import Bounded._ - import cats.syntax.functor._ - import cats.syntax.order._ - import cats.syntax.show._ - - def empty[A : Order : Empty]: Window[A] = new EmptyWindow[A] - - private final class EmptyWindow[A : Order : Empty] extends Window[A] { - private val zero = Empty[A].empty - override val order: Order[A] = Order[A] - override def contains(value: A): Boolean = false - override def bounds: Ior[Above[A], Below[A]] = - Ior.Both(Above(zero, inclusiveLowerBound = false), Below(zero, inclusiveUpperBound = false)) - override def toString: String = s"Window.empty($zero)" - } - - implicit object InvariantWindow extends Invariant[Window] { - override def imap[A, B](fa: Window[A])(f: A => B)(g: B => A): Window[B] = { - implicit val o: Order[B] = Order.by(g)(fa.order) - val newBounds = fa.bounds.bimap(a => a.map(f), b => b.map(f)) - KnownWindow(newBounds) - } - } - - def showWindowWithTerm[A : Show](term: String): Show[Window[A]] = Show.show { window => - val (op1, op2) = window.bounds - .bimap( - b => (if (b.inclusiveLowerBound) ">=" else ">", ""), - b => ("", if (b.inclusiveUpperBound) "<=" else "<"), - ) - .merge - window.bounds match { - case Ior.Left(lb) => s"$term $op1 ${lb.lowerBound.show}" - case Ior.Right(ub) => s"$term $op2 ${ub.upperBound.show}" - case Ior.Both(lb, ub) => - s"$term $op1 ${lb.lowerBound.show} and $term $op2 ${ub.upperBound.show}" - } - } - - implicit def showWindow[A : Show]: Show[Window[A]] = showWindowWithTerm("x") - - def fromBounds[A : Order](bounds: Ior[Bounded.Above[A], Bounded.Below[A]]): Window[A] = KnownWindow(bounds) - - def fromRange(range: Range): Window[Int] = - Window.between(range.start, includeMin = true, range.end, includeMax = range.isInclusive) - - def fromRange[A : Order](range: NumericRange[A]): Window[A] = - Window.between(range.start, includeMin = true, range.end, includeMax = range.isInclusive) - - def equalTo[A : Order](value: A): Window[A] = - Window.betweenInclusive(value, value) - - def lessThan[A : Order]( - max: A, - inclusive: Boolean, - ): Window[A] = KnownWindow(Ior.Right(Below(max, inclusive))) - - /** (-infinity, max) */ - def lessThan[A : Order](max: A): Window[A] = lessThan(max, inclusive = false) - - /** (-infinity, max] */ - def lessThanOrEqual[A : Order](max: A): Window[A] = lessThan(max, inclusive = true) - - /** Generally defined range with configurable open or lower bound. */ - def greaterThan[A : Order]( - min: A, - inclusive: Boolean, - ): Window[A] = KnownWindow(Ior.Left(Above(min, inclusiveLowerBound = inclusive))) - - /** (min, +infinity) */ - def greaterThan[A : Order](min: A): Window[A] = greaterThan(min, inclusive = false) - - /** [min, +infinity) */ - def greaterThanOrEqual[A : Order](min: A): Window[A] = greaterThan(min, inclusive = true) - - /** Generally defined bounded range with the ability to set open or closed at each end of the range.*/ - def between[A : Order]( - min: A, - includeMin: Boolean, - max: A, - includeMax: Boolean, - ): Window[A] = - KnownWindow(Ior.Both(Above(min, includeMin), Below(max, includeMax))) - - /** [min,max) */ - def between[A : Order]( - min: A, - max: A, - ): Window[A] = between(min, includeMin = true, max, includeMax = false) - - /** [min,max] */ - def betweenInclusive[A : Order]( - min: A, - max: A, - ): Window[A] = between(min, includeMin = true, max, includeMax = true) - - private[Window] final case class KnownWindow[A]( - bounds: Ior[Above[A], Below[A]], - )(implicit - override val order: Order[A], - ) extends Window[A] { - - private val checkBetween: A => Boolean = bounds match { - case Ior.Left(lb) if lb.inclusiveLowerBound => _ >= lb.lowerBound - case Ior.Left(lb) => _ > lb.lowerBound - - case Ior.Right(ub) if ub.inclusiveUpperBound => _ <= ub.upperBound - case Ior.Right(ub) => _ < ub.upperBound - - case Ior.Both(lb, ub) if lb.lowerBound == ub.upperBound && (lb.inclusiveLowerBound || ub.inclusiveUpperBound) => - a => a == lb.lowerBound - case Ior.Both(lb, ub) if lb.inclusiveLowerBound && ub.inclusiveUpperBound => - a => a >= lb.lowerBound && a <= ub.upperBound - case Ior.Both(lb, ub) if ub.inclusiveUpperBound => - a => a > lb.lowerBound && a <= ub.upperBound - case Ior.Both(lb, ub) if lb.inclusiveLowerBound => - a => a >= lb.lowerBound && a < ub.upperBound - case Ior.Both(lb, ub) => - a => a > lb.lowerBound && a < ub.upperBound - } - - override def contains(value: A): Boolean = checkBetween(value) - } - -} diff --git a/core/src/main/scala/vapors/data/package.scala b/core/src/main/scala/vapors/data/package.scala deleted file mode 100644 index 176e6c115..000000000 --- a/core/src/main/scala/vapors/data/package.scala +++ /dev/null @@ -1,32 +0,0 @@ -package com.rallyhealth - -package vapors - -package object data { - - /** - * Special case of [[ExtractValue]] that extracts a [[Boolean]] value. - */ - final type ExtractBoolean[-T] = ExtractValue[T, Boolean] - - object ExtractBoolean { - - @deprecated("Use ExtractValue[Boolean].from[T]", "0.14.1") - @inline final def apply[T](implicit instance: ExtractBoolean[T]): ExtractBoolean[T] = instance - } - - /** - * An ordered set of untyped [[Fact]]s. - */ - final type FactSet = Set[Fact] - - /** - * An ordered set of [[TypedFact]]s. - * - * @note not to be confused with a [[FactTypeSet]] (which is a set of [[FactType]]s, with not values) - */ - final type TypedFactSet[T] = Set[TypedFact[T]] - - @deprecated("Use com.rallyhealth.vapors.v1.data.TimeOrder instead.", "1.0.0") - type TimeOrder = vapors.v1.data.TimeOrder -} diff --git a/core/src/main/scala/vapors/dsl/ConcatOutputExprBuilder.scala b/core/src/main/scala/vapors/dsl/ConcatOutputExprBuilder.scala deleted file mode 100644 index 6ad134df4..000000000 --- a/core/src/main/scala/vapors/dsl/ConcatOutputExprBuilder.scala +++ /dev/null @@ -1,70 +0,0 @@ -package com.rallyhealth - -package vapors.dsl - -import vapors.algebra.{CaptureP, Expr} -import vapors.lens.NamedLens - -import cats.MonoidK - -import scala.collection.Factory - -final class ConcatOutputExprBuilder[V, M[_], R, P](private val expressions: LazyList[Expr[V, M[R], P]]) extends AnyVal { - - /** - * Concatenates the results of all the expressions into a single [[LazyList]]. - * - * This requires that the return type is an [[IterableOnce]] scala collection. - * - * This is generally preferable as a data structure because you probably don't want to compute things - * that are not used. - * - * @example - * {{{ - * assert(eval(concat(Some(1), None, Some(2)).toLazyList).output.value == LazyList(1, 2)) - * }}} - */ - def toLazyList( - implicit - ev: M[R] <:< IterableOnce[R], - captureResult: CaptureP[V, LazyList[R], P], - ): Expr.ConcatOutput[V, LazyList, R, P] = - Expr.ConcatOutput( - expressions.map { expr => - Expr.SelectFromOutput( - expr, - NamedLens.id[M[R]].asIterable[R].to(LazyList: Factory[R, LazyList[R]]), - captureResult, - ) - }, - captureResult, - ) - - /** - * Combines all the results of the expressions using the definition of monoid for the return type. - * - * This requires that the return type constructor has a standard definition for [[MonoidK]]. - * - * @example - * {{{ - * assert(eval(concat(List(1, 2), List(3, 4)).toOutputMonoid).output.value == List(1, 2, 3, 4)) - * }}} - */ - def toOutputMonoid( - implicit - monoidKM: MonoidK[M], - captureResult: CaptureP[V, M[R], P], - ): Expr.ConcatOutput[V, M, R, P] = - Expr.ConcatOutput(expressions, captureResult) - -} - -object ConcatOutputExprBuilder { - - implicit def defaultAsMonoid[V, M[_] : MonoidK, R, P]( - builder: ConcatOutputExprBuilder[V, M, R, P], - )(implicit - captureResult: CaptureP[V, M[R], P], - ): Expr.ConcatOutput[V, M, R, P] = - builder.toOutputMonoid -} diff --git a/core/src/main/scala/vapors/dsl/DefinitionExprBuilder.scala b/core/src/main/scala/vapors/dsl/DefinitionExprBuilder.scala deleted file mode 100644 index 8ff595fe9..000000000 --- a/core/src/main/scala/vapors/dsl/DefinitionExprBuilder.scala +++ /dev/null @@ -1,31 +0,0 @@ -package com.rallyhealth - -package vapors.dsl - -import vapors.algebra.Expr -import vapors.data.{FactSet, FactType} - -import cats.{Foldable, Id} - -final class DefinitionExprBuilder[T](private val factType: FactType[T]) extends AnyVal { - - /** - * Creates a definition that always adds exactly one fact. - */ - def from[P]( - defExpr: RootExpr[T, P], - )(implicit - captureResult: CaptureRootExpr[FactSet, P], - ): Expr.Define[Id, T, P] = - Expr.Define[Id, T, P](factType, defExpr, captureResult) - - /** - * Creates a definition that adds a fact for every value returned by the [[Foldable]] [[Expr]]. - */ - def fromEvery[M[_] : Foldable, P]( - defExpr: RootExpr[M[T], P], - )(implicit - captureResult: CaptureRootExpr[FactSet, P], - ): Expr.Define[M, T, P] = - Expr.Define(factType, defExpr, captureResult) -} diff --git a/core/src/main/scala/vapors/dsl/ExprBuilder.scala b/core/src/main/scala/vapors/dsl/ExprBuilder.scala deleted file mode 100644 index 011ed7d78..000000000 --- a/core/src/main/scala/vapors/dsl/ExprBuilder.scala +++ /dev/null @@ -1,746 +0,0 @@ -package com.rallyhealth - -package vapors.dsl - -import vapors.algebra.{CaptureP, Expr, ExprConverter, ExprSorter} -import vapors.data.{Evidence, FactTable, TypedFact, Window} -import vapors.lens.NamedLens -import vapors.math._ - -import cats._ - -import scala.collection.{Factory, MapView, View} -import scala.reflect.runtime.universe.TypeTag - -/** - * Chains algebraic operations into a single [[Expr]] node. - * - * @param returnOutput the head of the tree of expressions as built so far - * - * TODO: Make this more reusable for [[MapViewExprBuilder]] - */ -sealed class ExprBuilder[V, M[_], U, P](val returnOutput: Expr[V, M[U], P]) { - - type CaptureResult[R] = CaptureP[V, R, P] - - type CaptureCondResult = CaptureResult[Boolean] - - type CaptureInputResult[R] = CaptureP[U, R, P] - type CaptureInputAsResult = CaptureInputResult[U] - type CaptureInputCondResult = CaptureInputResult[Boolean] - - def returnInput(implicit captureResult: CaptureResult[V]): Expr[V, V, P] = Expr.ReturnInput(captureResult) - - /** - * Embed the result of an expression with the input of the current builder. - */ - def embedResult[A]( - expr: RootExpr[A, P], - )(implicit - captureEmbed: CaptureP[V, A, P], - ): ValExprBuilder[V, A, P] = - new ValExprBuilder[V, A, P](Expr.Embed(expr, captureEmbed)) - - /** - * Embed a constant value with the input of the current builder. - */ - def embedConst[A]( - value: A, - )(implicit - captureConst: CaptureRootExpr[A, P], - captureEmbed: CaptureP[V, A, P], - ): ValExprBuilder[V, A, P] = - new ValExprBuilder[V, A, P](Expr.Embed(Expr.ConstOutput(value, Evidence.none, captureConst), captureEmbed)) -} - -object ExprBuilder { - - type ValId[V, P] = ValExprBuilder[V, V, P] - type ValFn[V, R, P] = ValExprBuilder[V, V, P] => ExprBuilder[V, Id, R, P] - - type FoldableId[F[_], V, P] = FoldableExprBuilder[F[V], F, V, P] - type FoldableFn[F[_], V, M[_], U, P] = FoldableId[F, V, P] => ExprBuilder[F[V], M, U, P] -} - -trait ExprBuilderSyntax { - - /** - * Implicitly allows embedding any expression that only requires the FactTable into expression. - * - * @see [[Expr.Embed]] - */ - implicit def embedExpr[V, R, P]( - expr: RootExpr[R, P], - )(implicit - captureResult: CaptureP[V, R, P], - ): Expr.Embed[V, R, P] = Expr.Embed(expr, captureResult) - - implicit def liftValExpr[V, R, P](expr: Expr[V, R, P]): ValExprBuilder[V, R, P] = - new ValExprBuilder(expr) - - implicit def returnFoldableExprOutput[V, M[_], U, P](builder: FoldableExprBuilder[V, M, U, P]): Expr[V, M[U], P] = - builder.returnOutput - - implicit def returnValExprOutput[V, R, P](builder: ValExprBuilder[V, R, P]): Expr[V, R, P] = - builder.returnOutput - - implicit def addTo[R : Addition](lhs: R): AdditionBuilderOps[R] = new AdditionBuilderOps(lhs) - - implicit def subtractFrom[R : Subtraction](lhs: R): SubtractBuilderOps[R] = new SubtractBuilderOps(lhs) - - implicit def divideFrom[R : Division](lhs: R): DivisionBuilderOps[R] = new DivisionBuilderOps(lhs) - -} - -final class AdditionBuilderOps[R : Addition](number: R) { - - def +[V, P]( - builder: ValExprBuilder[V, R, P], - )(implicit - captureResult: builder.CaptureResult[R], - ): ValExprBuilder[V, R, P] = { - builder.addTo(number) - } -} - -final class SubtractBuilderOps[R : Subtraction](number: R) { - - def -[V, P]( - builder: ValExprBuilder[V, R, P], - )(implicit - captureResult: builder.CaptureResult[R], - ): ValExprBuilder[V, R, P] = { - builder.subtractFrom(number) - } -} - -final class MultiplicationBuilderOps[R : Multiplication](number: R) { - - def *[V, P]( - builder: ValExprBuilder[V, R, P], - )(implicit - captureResult: builder.CaptureResult[R], - ): ValExprBuilder[V, R, P] = { - builder.multiplyTo(number) - } -} - -final class DivisionBuilderOps[R : Division](number: R) { - - def /[V, P]( - builder: ValExprBuilder[V, R, P], - )(implicit - captureResult: builder.CaptureResult[R], - ): ValExprBuilder[V, R, P] = { - builder.divideFrom(number) - } -} - -/** - * Same as [[ExprBuilder]], but for arity-1 higher-kinded types - * - * @see [[ExprBuilder]] - * - * TODO: Rename? Is this always foldable? - */ -final class FoldableExprBuilder[V, M[_], U, P](returnOutput: Expr[V, M[U], P]) - extends ExprBuilder[V, M, U, P](returnOutput) { - - /** - * Same as [[to]](List). - */ - def toList( - implicit - ev: M[U] <:< IterableOnce[U], - captureResult: CaptureResult[List[U]], - ): FoldableExprBuilder[V, List, U, P] = - to(List) - - /** - * Same as [[to]](Set). - */ - def toSet( - implicit - ev: M[U] <:< IterableOnce[U], - captureResult: CaptureResult[Set[U]], - ): FoldableExprBuilder[V, Set, U, P] = - to(Set) - - /** - * Converts the iterable of 2-tuples return value into a Map. - * - * @example - * {{{ - * assert(eval(const(Set("A" -> 1, "B" -> 2)).withOutputFoldable.toMap).output.value == Map("A" -> 1, "B" -> 2)) - * }}} - */ - def toMap[K, X]( - implicit - ev: M[U] <:< IterableOnce[(K, X)], - captureResult: CaptureResult[MapView[K, X]], - ): MapViewExprBuilder[V, K, X, P] = - new MapViewExprBuilder( - Expr.SelectFromOutput[V, M[U], MapView[K, X], P]( - returnOutput, - NamedLens.id[M[U]].toMapView, - captureResult, - ), - ) - - /** - * Converts the iterable return value into the specific Scala collection companion object. - * - * @example - * {{{ - * assert(eval(const(List(1, 2, 2, 3)).withOutputFoldable.to(Set)).output.value == Set(1, 2, 3)) - * }}} - */ - def to[N[_] : Foldable]( - factory: Factory[U, N[U]], - )(implicit - ev: M[U] <:< IterableOnce[U], - captureResult: CaptureResult[N[U]], - ): FoldableExprBuilder[V, N, U, P] = - new FoldableExprBuilder( - Expr.SelectFromOutput[V, M[U], N[U], P]( - returnOutput, - NamedLens.id[M[U]].asIterable.to(factory), - captureResult, - ), - ) - - /** - * Sorts the sequence of values returned based on the given total ordering for the value type. - */ - def sorted( - implicit - orderU: Order[U], - ev: M[U] <:< Seq[U], - tt: TypeTag[U], - factory: Factory[U, M[U]], - captureResult: CaptureResult[M[U]], - ): FoldableExprBuilder[V, M, U, P] = - new FoldableExprBuilder(Expr.SortOutput(returnOutput, ExprSorter.byNaturalOrder[M, U], captureResult)) - - /** - * Sorts the sequence of values returned based on the given total ordering for the type of the - * selected field of the values. - */ - def sortBy[R : Order]( - buildLens: NamedLens.Fn[U, R], - )(implicit - ev: M[U] <:< Seq[U], - tt: TypeTag[U], - factory: Factory[U, M[U]], - captureResult: CaptureResult[M[U]], - ): FoldableExprBuilder[V, M, U, P] = { - val lens = buildLens(NamedLens.id[U]) - new FoldableExprBuilder(Expr.SortOutput(returnOutput, ExprSorter.byField[M, U, R](lens), captureResult)) - } - - /** - * Groups the [[Foldable]] return value by the selected field. - * - * The values of the map produced must have a total ordering so that they are retrieved from the map - * in a consistent and predictable way. - */ - def groupBy[K]( - buildKeyLens: NamedLens.Fn[U, K], - )(implicit - foldableM: Foldable[M], - orderU: Order[U], - captureSelect: CaptureResult[View[(K, Seq[U])]], - captureResult: CaptureResult[MapView[K, Seq[U]]], - ): MapViewExprBuilder[V, K, Seq[U], P] = { - val keyLens = buildKeyLens(NamedLens.id[U]) - new MapViewExprBuilder(Expr.GroupOutput(returnOutput, keyLens, captureResult)) - } - - /** - * Takes a given number of elements from the start of the traversable return value. - */ - def take( - n: Int, - )(implicit - traverseM: Traverse[M], - traverseFilterM: TraverseFilter[M], - captureResult: CaptureResult[M[U]], - ): FoldableExprBuilder[V, M, U, P] = - new FoldableExprBuilder(Expr.TakeFromOutput(returnOutput, n, captureResult)) - - /** - * Takes the first element from the start of the traversable return value or None, if the return value is empty. - */ - def headOption( - implicit - traverseM: Traverse[M], - traverseFilterM: TraverseFilter[M], - ev: M[U] <:< Iterable[U], - captureAllResults: CaptureResult[M[U]], - captureHeadResult: CaptureResult[Option[U]], - ): FoldableExprBuilder[V, Option, U, P] = - new FoldableExprBuilder( - Expr.SelectFromOutput(take(1), NamedLens.id[M[U]].headOption, captureHeadResult), - ) - - /** - * Maps an expression over every element of the returned [[Functor]] and folds the evidence together. - */ - def map[R]( - buildFn: ValExprBuilder[U, U, P] => ExprBuilder[U, Id, R, P], - )(implicit - foldableM: Foldable[M], - functorM: Functor[M], - postEachOutput: CaptureInputAsResult, - postMap: CaptureResult[M[R]], - ): FoldableExprBuilder[V, M, R, P] = { - val mapExpr = buildFn( - new ValExprBuilder(Expr.ReturnInput(postEachOutput)), - ) - val next = Expr.MapOutput(returnOutput, mapExpr.returnOutput, postMap) - new FoldableExprBuilder(next) - } - - /** - * Maps an expression that produces the same return type constructor and flattens all the returned - * elements together based on the definition of [[FlatMap]]. - * - * This will also fold all the evidence together. - * - * @note there is no good definition of `flatMap` inside of an applicative data structure. The input - * to any outer expression builder almost always not be the same type as the type of input to - * the inner expression to be flattened. This means that you can't reference any expression outside - * of the scope of the expression builder function given to `.flatMap`. - * - * The only way to fix this would be to provide some way to carry the input from previous expressions - * around in some kind of stack or grab bag, from which you could grab values for another expression. - * Until then, this method is kind of useless. - */ - def flatMap[X]( - buildFn: ValExprBuilder[U, U, P] => ExprBuilder[U, M, X, P], - )(implicit - foldableM: Foldable[M], - flatMapM: FlatMap[M], - postEachInput: CaptureInputAsResult, - postFlatMap: CaptureResult[M[X]], - ): FoldableExprBuilder[V, M, X, P] = { - val flatMapExpr = buildFn( - new ValExprBuilder(Expr.ReturnInput(postEachInput)), - ) - val next = Expr.FlatMapOutput(returnOutput, flatMapExpr.returnOutput, postFlatMap) - new FoldableExprBuilder(next) - } - - /** - * Fold all the result values into a single result based on the definition of [[Monoid]]. - * - * @example - * {{{ - * // for better or for worse, the default definition of Monoid[Int] is summation - * assert(eval(const(List(1, 2, 3)).withOutputFoldable.fold).output.value == 6) - * }}} - */ - def fold( - implicit - foldableM: Foldable[M], - monoidU: Monoid[U], - captureResult: CaptureResult[U], - ): ValExprBuilder[V, U, P] = { - new ValExprBuilder(Expr.FoldOutput(returnOutput, captureResult)) - } - - /** - * Returns true if the result value is an empty [[Foldable]]. - */ - def isEmpty( - implicit - foldableM: Foldable[M], - captureResult: CaptureCondResult, - ): ValExprBuilder[V, Boolean, P] = - new ValExprBuilder(Expr.OutputIsEmpty(returnOutput, captureResult)) - - /** - * Computes the given condition expression for every element of the returned value until one returns true - * OR the returned value is empty. Otherwise -- if all elements of the non-empty returned value return false - * -- this expression will return false. - */ - def exists( - buildFn: ValExprBuilder[U, U, P] => ExprBuilder[U, Id, Boolean, P], - )(implicit - foldableM: Foldable[M], - postEachOutput: CaptureP[U, U, P], - captureResult: CaptureCondResult, - ): ValExprBuilder[V, Boolean, P] = { - val condExpr = buildFn(new ValExprBuilder(Expr.ReturnInput(postEachOutput))) - val next = Expr.ExistsInOutput(returnOutput, condExpr.returnOutput, captureResult) - new ValExprBuilder(next) - } - - /** - * Filters out all elements of the returned [[Foldable]] for which the given conditional expression returns false. - * - * What's left is all the elements of this expression's returned [[Foldable]] for which the given condition - * returns true. - */ - def filter( - buildFn: ValExprBuilder[U, U, P] => ExprBuilder[U, Id, Boolean, P], - )(implicit - foldableM: Foldable[M], - filterM: FunctorFilter[M], - captureInput: CaptureInputResult[U], - captureResult: CaptureResult[M[U]], - ): FoldableExprBuilder[V, M, U, P] = { - val condExpr = buildFn( - new ValExprBuilder(Expr.ReturnInput(captureInput)), - ) - new FoldableExprBuilder(Expr.FilterOutput(returnOutput, condExpr.returnOutput, captureResult)) - } - - /** - * Returns true if the returned [[Foldable]] shares any elements in common with the given set of valid values. - */ - def containsAny( - validValues: Set[U], - )(implicit - foldableM: Foldable[M], - filterM: FunctorFilter[M], - captureCond: CaptureCondResult, - captureFilterCond: CaptureInputCondResult, - captureFilterInput: CaptureInputResult[U], - captureFilterResult: CaptureResult[M[U]], - ): ValExprBuilder[V, Boolean, P] = - new ValExprBuilder[V, Boolean, P]( - Expr.Not( - filter(_ in validValues).isEmpty, - captureCond, - ), - ) -} - -/** - * Same as [[ExprBuilder]], but for concrete types. - * - * @see [[ExprBuilder]] - * - * TODO: This should probably share some methods with [[FoldableExprBuilder]]... or at least - * switching between builder types should be a lot easier. - */ -final class ValExprBuilder[V, R, P](override val returnOutput: Expr[V, R, P]) - extends ExprBuilder[V, Id, R, P](returnOutput) { - - // chains expression builder .get calls into a single select operation - @inline private def buildGetExpr[N[_], X]( - buildLens: NamedLens.Fn[R, N[X]], - )(implicit - captureResult: CaptureResult[N[X]], - ): Expr[V, N[X], P] = { - val lens = buildLens(NamedLens.id[R]) - // if the previous node was a SelectFromOutput, then combine the lenses and produce a single node - returnOutput match { - case prev: Expr.SelectFromOutput[V, s, R, P] => - // capture the starting type as an existential type parameter 's' - // it is ignored in the return type after the compile proves that this code is safe - Expr.SelectFromOutput[V, s, N[X], P](prev.inputExpr, prev.lens.andThen(lens), captureResult) - case _ => - // otherwise, build the lens as a new SelectFromOutput node - Expr.SelectFromOutput(returnOutput, lens, captureResult) - } - } - - /** - * Selects a field from the returned value using the given lens. - * - * @example - * {{{ - * // assuming you run this example in 2021... - * assert(const(LocalDate.now()).withOutputValue.get(_.select(_.getYear)).output.value == 2021) - * }}} - */ - def get[X]( - buildLens: NamedLens.Fn[R, X], - )(implicit - captureResult: CaptureResult[X], - ): ValExprBuilder[V, X, P] = - new ValExprBuilder(buildGetExpr[Id, X](buildLens)) - - /** - * Same as [[get]], except it expects the selected value to have a higher-kinded type and returns - * a [[FoldableExprBuilder]]. - * - * @example - * {{{ - * // selecting a value from a Map returns an Option, which is foldable - * assert(const(Map("A" -> 1)).withOutputValue.getFoldable(_.at("A")).isEmpty).output.value == false) - * }}} - * - * TODO: Rename? It only needs to be a higher-kinded type, but [[FoldableExprBuilder]] almost always - * requires a [[Foldable]] definition for any chained operations. Ideally, we would be able - * to avoid needing to distinguish the type of builder to use until after we have selected the field. - */ - def getFoldable[N[_], X]( - buildLens: NamedLens.Fn[R, N[X]], - )(implicit - captureResult: CaptureResult[N[X]], - ): FoldableExprBuilder[V, N, X, P] = - new FoldableExprBuilder(buildGetExpr(buildLens)) - - @deprecated( - "Use valuesOfType(...) to avoid needing this .value method or use .get(_.select(_.value)) instead.", - "0.17.0", - ) - def value[X]( - implicit - ev: R <:< TypedFact[X], - captureResult: CaptureResult[X], - ): ValExprBuilder[V, X, P] = - new ValExprBuilder(buildGetExpr[Id, X](_.field("value", _.value))) - - def in(accepted: Set[R])(implicit captureCond: CaptureCondResult): ValExprBuilder[V, Boolean, P] = - new ValExprBuilder(Expr.OutputWithinSet(returnOutput, accepted, captureCond)) - - def +( - rhs: Expr[V, R, P], - )(implicit - R: Addition[R], - captureResult: CaptureResult[R], - ): ValExprBuilder[V, R, P] = - new ValExprBuilder(ExprDsl.add(returnOutput, rhs)) - - def +( - rhs: R, - )(implicit - R: Addition[R], - captureResult: CaptureResult[R], - ): ValExprBuilder[V, R, P] = - new ValExprBuilder(ExprDsl.add(returnOutput, Expr.ConstOutput(rhs, Evidence.none, captureResult))) - - def addTo( - lhs: R, - )(implicit - R: Addition[R], - captureResult: CaptureResult[R], - ): ValExprBuilder[V, R, P] = - new ValExprBuilder(ExprDsl.add(Expr.ConstOutput(lhs, Evidence.none, captureResult), returnOutput)) - - def *( - rhs: Expr[V, R, P], - )(implicit - R: Multiplication[R], - captureResult: CaptureResult[R], - ): ValExprBuilder[V, R, P] = - new ValExprBuilder(ExprDsl.multiply(returnOutput, rhs)) - - def *( - rhs: R, - )(implicit - R: Multiplication[R], - captureResult: CaptureResult[R], - ): ValExprBuilder[V, R, P] = - new ValExprBuilder(ExprDsl.multiply(returnOutput, Expr.ConstOutput(rhs, Evidence.none, captureResult))) - - def multiplyTo( - lhs: R, - )(implicit - R: Multiplication[R], - captureResult: CaptureResult[R], - ): ValExprBuilder[V, R, P] = - new ValExprBuilder(ExprDsl.multiply(Expr.ConstOutput(lhs, Evidence.none, captureResult), returnOutput)) - - def -( - rhs: Expr[V, R, P], - )(implicit - R: Subtraction[R], - captureResult: CaptureResult[R], - ): ValExprBuilder[V, R, P] = - new ValExprBuilder(ExprDsl.subtract(returnOutput, rhs)) - - def -( - rhs: R, - )(implicit - R: Subtraction[R], - captureResult: CaptureResult[R], - ): ValExprBuilder[V, R, P] = - new ValExprBuilder(ExprDsl.subtract(returnOutput, Expr.ConstOutput(rhs, Evidence.none, captureResult))) - - def subtractFrom( - lhs: R, - )(implicit - R: Subtraction[R], - captureResult: CaptureResult[R], - ): ValExprBuilder[V, R, P] = - new ValExprBuilder(ExprDsl.subtract(Expr.ConstOutput(lhs, Evidence.none, captureResult), returnOutput)) - - def /( - rhs: Expr[V, R, P], - )(implicit - R: Division[R], - captureResult: CaptureResult[R], - ): ValExprBuilder[V, R, P] = - new ValExprBuilder(ExprDsl.divide(returnOutput, rhs)) - - def /( - rhs: R, - )(implicit - R: Division[R], - captureResult: CaptureResult[R], - ): ValExprBuilder[V, R, P] = - new ValExprBuilder(ExprDsl.divide(returnOutput, Expr.ConstOutput(rhs, Evidence.none, captureResult))) - - def divideFrom( - lhs: R, - )(implicit - R: Division[R], - captureResult: CaptureResult[R], - ): ValExprBuilder[V, R, P] = - new ValExprBuilder(ExprDsl.divide(Expr.ConstOutput(lhs, Evidence.none, captureResult), returnOutput)) - - def unary_-( - implicit - R: Negative[R], - captureResult: CaptureResult[R], - ): ValExprBuilder[V, R, P] = - new ValExprBuilder(ExprDsl.negative(returnOutput)) - - def withinWindow( - windowExpr: Expr[V, Window[R], P], - )(implicit - captureResult: CaptureCondResult, - ): ValExprBuilder[V, Boolean, P] = - new ValExprBuilder(ExprDsl.within(returnOutput, windowExpr)) - - def within( - window: Window[R], - )(implicit - captureWindow: CaptureP[V, Window[R], P], - captureConst: CaptureP[FactTable, Window[R], P], - captureResult: CaptureCondResult, - ): ValExprBuilder[V, Boolean, P] = - new ValExprBuilder( - Expr.OutputWithinWindow(returnOutput, embed(const(window)(captureConst))(captureWindow), captureResult), - ) - - @deprecated("Use === const(...) instead. This method doesn't fit with the overall DSL.", "0.17.0") - def isEqualTo( - value: R, - )(implicit - orderR: Order[R], - captureWindow: CaptureP[V, Window[R], P], - captureConst: CaptureP[FactTable, Window[R], P], - captureResult: CaptureCondResult, - ): ValExprBuilder[V, Boolean, P] = - within(Window.equalTo(value)) - - def ===( - value: R, - )(implicit - orderR: Order[R], - captureWindow: CaptureP[V, Window[R], P], - captureConst: CaptureP[FactTable, Window[R], P], - captureResult: CaptureCondResult, - ): ValExprBuilder[V, Boolean, P] = - within(Window.equalTo(value)) - - def ===( - valueExpr: Expr[V, R, P], - )(implicit - orderR: Order[R], - captureWindow: CaptureP[V, Window[R], P], - captureResult: CaptureCondResult, - ): ValExprBuilder[V, Boolean, P] = - withinWindow(Expr.WrapOutput(valueExpr, ExprConverter.asWindow[R](Window.equalTo(_)), captureWindow)) - - def !==( - value: R, - )(implicit - orderR: Order[R], - captureWindow: CaptureP[V, Window[R], P], - captureConst: CaptureP[FactTable, Window[R], P], - captureResult: CaptureCondResult, - ): ValExprBuilder[V, Boolean, P] = - Expr.Not(within(Window.equalTo(value)), captureResult) - - def !==( - valueExpr: Expr[V, R, P], - )(implicit - orderR: Order[R], - captureWindow: CaptureP[V, Window[R], P], - captureResult: CaptureCondResult, - ): ValExprBuilder[V, Boolean, P] = - Expr.Not(this === valueExpr, captureResult) - - def <( - value: R, - )(implicit - orderR: Order[R], - captureWindow: CaptureP[V, Window[R], P], - captureConst: CaptureP[FactTable, Window[R], P], - captureResult: CaptureCondResult, - ): ValExprBuilder[V, Boolean, P] = - within(Window.lessThan(value)) - - def <( - valueExpr: Expr[V, R, P], - )(implicit - orderR: Order[R], - captureWindow: CaptureP[V, Window[R], P], - captureResult: CaptureCondResult, - ): ValExprBuilder[V, Boolean, P] = - withinWindow(Expr.WrapOutput(valueExpr, ExprConverter.asWindow[R](Window.lessThan(_)), captureWindow)) - - def <=( - value: R, - )(implicit - orderR: Order[R], - captureWindow: CaptureP[V, Window[R], P], - captureConst: CaptureP[FactTable, Window[R], P], - captureResult: CaptureCondResult, - ): ValExprBuilder[V, Boolean, P] = - within(Window.lessThanOrEqual(value)) - - def <=( - valueExpr: Expr[V, R, P], - )(implicit - orderR: Order[R], - captureWindow: CaptureP[V, Window[R], P], - captureResult: CaptureCondResult, - ): ValExprBuilder[V, Boolean, P] = - withinWindow(Expr.WrapOutput(valueExpr, ExprConverter.asWindow[R](Window.lessThanOrEqual(_)), captureWindow)) - - def >( - value: R, - )(implicit - orderR: Order[R], - captureWindow: CaptureP[V, Window[R], P], - captureConst: CaptureP[FactTable, Window[R], P], - captureResult: CaptureCondResult, - ): ValExprBuilder[V, Boolean, P] = - within(Window.greaterThan(value)) - - def >( - valueExpr: Expr[V, R, P], - )(implicit - orderR: Order[R], - captureWindow: CaptureP[V, Window[R], P], - captureResult: CaptureCondResult, - ): ValExprBuilder[V, Boolean, P] = - withinWindow(Expr.WrapOutput(valueExpr, ExprConverter.asWindow[R](Window.greaterThan(_)), captureWindow)) - - def >=( - value: R, - )(implicit - orderR: Order[R], - captureWindow: CaptureP[V, Window[R], P], - captureConst: CaptureP[FactTable, Window[R], P], - captureResult: CaptureCondResult, - ): ValExprBuilder[V, Boolean, P] = - within(Window.greaterThanOrEqual(value)) - - def >=( - valueExpr: Expr[V, R, P], - )(implicit - orderR: Order[R], - captureWindow: CaptureP[V, Window[R], P], - captureResult: CaptureCondResult, - ): ValExprBuilder[V, Boolean, P] = - withinWindow(Expr.WrapOutput(valueExpr, ExprConverter.asWindow[R](Window.greaterThanOrEqual(_)), captureWindow)) -} diff --git a/core/src/main/scala/vapors/dsl/ExprBuilderCatsInstances.scala b/core/src/main/scala/vapors/dsl/ExprBuilderCatsInstances.scala deleted file mode 100644 index 18490165d..000000000 --- a/core/src/main/scala/vapors/dsl/ExprBuilderCatsInstances.scala +++ /dev/null @@ -1,252 +0,0 @@ -package com.rallyhealth - -package vapors.dsl - -import cats._ -import cats.data.Ior -import cats.syntax.all._ - -import scala.collection.{MapView, View} - -trait ExprBuilderCatsInstances { - - implicit def monadSet: Monad[Set] = alleycats.std.set.alleyCatsStdSetMonad - - implicit def traverseSet: Traverse[Set] = alleycats.std.set.alleyCatsSetTraverse - - implicit def traverseFilterSet: TraverseFilter[Set] = alleycats.std.set.alleyCatsSetTraverseFilter - - implicit def traverseMap[K]: Traverse[Map[K, *]] = alleycats.std.map.alleycatsStdInstancesForMap - - implicit def traverseFilterMap[K]: TraverseFilter[Map[K, *]] = alleycats.std.map.alleycatsStdMapTraverseFilter - - implicit def traverseMapView[K]: Traverse[MapView[K, *]] = new Traverse[MapView[K, *]] { - - override final def traverse[G[_], A, B]( - fa: MapView[K, A], - )( - f: A => G[B], - )(implicit - G: Applicative[G], - ): G[MapView[K, B]] = - traverseMap.traverse(fa.toMap)(f).map(_.view) - - override final def map[A, B](fa: MapView[K, A])(f: A => B): MapView[K, B] = fa.mapValues(f) - - override final def foldLeft[A, B]( - fa: MapView[K, A], - b: B, - )( - f: (B, A) => B, - ): B = - fa.foldLeft(b) { case (x, (_, a)) => f(x, a) } - - override final def foldRight[A, B]( - fa: MapView[K, A], - lb: Eval[B], - )( - f: (A, Eval[B]) => Eval[B], - ): Eval[B] = - Foldable.iterateRight(fa.values, lb)(f) - - override final def size[A](fa: MapView[K, A]): Long = fa.size.toLong - - override final def get[A](fa: MapView[K, A])(idx: Long): Option[A] = { - if (idx < 0L || Int.MaxValue < idx) None - else { - val n = idx.toInt - if (n >= fa.size) None - else Some(fa.valuesIterator.drop(n).next()) - } - } - - override final def isEmpty[A](fa: MapView[K, A]): Boolean = fa.isEmpty - - override final def fold[A](fa: MapView[K, A])(implicit A: Monoid[A]): A = A.combineAll(fa.values) - - override final def toList[A](fa: MapView[K, A]): List[A] = fa.values.toList - - override final def collectFirst[A, B](fa: MapView[K, A])(pf: PartialFunction[A, B]): Option[B] = - fa.collectFirst(new PartialFunction[(K, A), B] { - override final def isDefinedAt(x: (K, A)): Boolean = pf.isDefinedAt(x._2) - override final def apply(v1: (K, A)): B = pf(v1._2) - }) - - override final def collectFirstSome[A, B](fa: MapView[K, A])(f: A => Option[B]): Option[B] = - collectFirst(fa)(Function.unlift(f)) - } - - implicit def traverseFilterMapView[K]: TraverseFilter[MapView[K, *]] = new TraverseFilter[MapView[K, *]] { - override final def traverse: Traverse[MapView[K, *]] = traverseMapView - override final def traverseFilter[G[_], A, B]( - fa: MapView[K, A], - )( - f: A => G[Option[B]], - )(implicit - G: Applicative[G], - ): G[MapView[K, B]] = { - import cats.syntax.functor._ - traverseFilterMap.traverseFilter(fa.toMap)(f).map(_.view) - } - } - - implicit val vaporsInstancesForView: Traverse[View] - with TraverseFilter[View] - with Alternative[View] - with Monad[View] - with CoflatMap[View] - with Align[View] = - new Traverse[View] with TraverseFilter[View] with Alternative[View] with Monad[View] with CoflatMap[View] - with Align[View] { - - override final def traverse: Traverse[View] = this - - override final def empty[A]: View[A] = View.empty - - override final def combineK[A]( - x: View[A], - y: View[A], - ): View[A] = x ++ y - - override final def pure[A](x: A): View[A] = View(x) - - override final def map[A, B](fa: View[A])(f: A => B): View[B] = - fa.map(f) - - override final def flatMap[A, B](fa: View[A])(f: A => View[B]): View[B] = - fa.flatMap(f) - - override final def map2[A, B, Z]( - fa: View[A], - fb: View[B], - )( - f: (A, B) => Z, - ): View[Z] = - if (fb.isEmpty) View.empty // do O(1) work if fb is empty - else fa.flatMap(a => fb.map(b => f(a, b))) // already O(1) if fa is empty - - override final def map2Eval[A, B, Z]( - fa: View[A], - fb: Eval[View[B]], - )( - f: (A, B) => Z, - ): Eval[View[Z]] = - if (fa.isEmpty) Eval.now(View.empty) // no need to evaluate fb - else fb.map(fb => map2(fa, fb)(f)) - - override final def coflatMap[A, B](fa: View[A])(f: View[A] => B): View[B] = - fa.tails.to(View).init.map(f) - - override final def foldLeft[A, B]( - fa: View[A], - b: B, - )( - f: (B, A) => B, - ): B = - fa.foldLeft(b)(f) - - override final def foldRight[A, B]( - fa: View[A], - lb: Eval[B], - )( - f: (A, Eval[B]) => Eval[B], - ): Eval[B] = - Now(fa).flatMap { s => - // Note that we don't use pattern matching, since that may needlessly force the tail. - if (s.isEmpty) lb else f(s.head, Eval.defer(foldRight(s.tail, lb)(f))) - } - - override final def foldMap[A, B](fa: View[A])(f: A => B)(implicit B: Monoid[B]): B = - B.combineAll(fa.iterator.map(f)) - - override final def traverse[G[_], A, B](fa: View[A])(f: A => G[B])(implicit G: Applicative[G]): G[View[B]] = - Traverse[LazyList].traverse(fa.to[LazyList[A]](LazyList))(f).map(_.to(View)) - - override final def mapWithIndex[A, B](fa: View[A])(f: (A, Int) => B): View[B] = - fa.zipWithIndex.map(ai => f(ai._1, ai._2)) - - override final def zipWithIndex[A](fa: View[A]): View[(A, Int)] = - fa.zipWithIndex - - override final def tailRecM[A, B](a: A)(fn: A => View[Either[A, B]]): View[B] = - FlatMap[LazyList] - .tailRecM(a) { - fn.andThen(_.to[LazyList[Either[A, B]]](LazyList)) - } - .to(View) - - override final def exists[A](fa: View[A])(p: A => Boolean): Boolean = - fa.exists(p) - - override final def forall[A](fa: View[A])(p: A => Boolean): Boolean = - fa.forall(p) - - override final def get[A](fa: View[A])(idx: Long): Option[A] = - Traverse[LazyList].get(fa.to[LazyList[A]](LazyList))(idx) - - override final def isEmpty[A](fa: View[A]): Boolean = fa.isEmpty - - override final def foldM[G[_], A, B]( - fa: View[A], - z: B, - )( - f: (B, A) => G[B], - )(implicit - G: Monad[G], - ): G[B] = - Traverse[LazyList].foldM(fa.to[LazyList[A]](LazyList), z)(f) - - override final def fold[A](fa: View[A])(implicit A: Monoid[A]): A = A.combineAll(fa) - - override final def toList[A](fa: View[A]): List[A] = fa.toList - - override final def toIterable[A](fa: View[A]): Iterable[A] = fa - - override def reduceLeftOption[A](fa: View[A])(f: (A, A) => A): Option[A] = - fa.reduceLeftOption(f) - - override final def find[A](fa: View[A])(f: A => Boolean): Option[A] = fa.find(f) - - override final def algebra[A]: Monoid[View[A]] = new Monoid[View[A]] { - override final def empty: View[A] = View.empty - override final def combine( - x: View[A], - y: View[A], - ): View[A] = x ++ y - } - - override final def collectFirst[A, B](fa: View[A])(pf: PartialFunction[A, B]): Option[B] = fa.collectFirst(pf) - - override final def collectFirstSome[A, B](fa: View[A])(f: A => Option[B]): Option[B] = - fa.collectFirst(Function.unlift(f)) - - override final def align[A, B]( - fa: View[A], - fb: View[B], - ): View[Ior[A, B]] = - alignWith(fa, fb)(identity) - - override final def alignWith[A, B, C]( - fa: View[A], - fb: View[B], - )( - f: Ior[A, B] => C, - ): View[C] = - Align[LazyList].alignWith(fa.to[LazyList[A]](LazyList), fb.to[LazyList[B]](LazyList))(f).to(View) - - override final def traverseFilter[G[_], A, B]( - fa: View[A], - )( - f: A => G[Option[B]], - )(implicit - G: Applicative[G], - ): G[View[B]] = - TraverseFilter[LazyList].traverseFilter(fa.to[LazyList[A]](LazyList))(f).map(_.to(View)) - - override def filterA[G[_], A](fa: View[A])(f: A => G[Boolean])(implicit G: Applicative[G]): G[View[A]] = - TraverseFilter[LazyList].filterA(fa.to[LazyList[A]](LazyList))(f).map(_.to(View)) - - override def mapFilter[A, B](fa: View[A])(f: A => Option[B]): View[B] = - fa.collect(Function.unlift(f)) - } -} diff --git a/core/src/main/scala/vapors/dsl/ExprDsl.scala b/core/src/main/scala/vapors/dsl/ExprDsl.scala deleted file mode 100644 index 9dded77df..000000000 --- a/core/src/main/scala/vapors/dsl/ExprDsl.scala +++ /dev/null @@ -1,300 +0,0 @@ -package com.rallyhealth - -package vapors.dsl - -import vapors.algebra.{CaptureP, Expr, ExprResult} -import vapors.data._ -import vapors.interpreter.{ExprInput, InterpretExprAsResultFn} -import vapors.lens.NamedLens -import vapors.logic.{Conjunction, Disjunction, Negation} -import vapors.math._ - -import cats.data.NonEmptyList -import cats.{Foldable, Monoid} - -object ExprDsl extends ExprDsl - -// TODO: Remove methods that are not useful anymore and move less-useful methods to a separate object -trait ExprDsl extends TimeFunctions with WrapExprSyntax with WrapEachExprSyntax { - - /** - * Evaluates the given [[RootExpr]] with the given [[FactTable]] to produce the [[ExprResult]]. - * - * To access the value of the result, you can get the `result.output.value`. [[Evidence]] for the - * value can be accessed from `result.output.evidence`. Any parameter captured in [[CaptureP]] is - * accessible from `result.param`. - * - * You can also apply a post-processing [[ExprResult.Visitor]] to analyze the results in more depth - * in a recursive and type-safe way. - */ - def eval[R, P](facts: FactTable)(query: RootExpr[R, P]): ExprResult[FactTable, R, P] = { - InterpretExprAsResultFn(query)(ExprInput.fromFactTable(facts)) - } - - /** - * Creates an embeddable [[RootExpr]] that always returns a given constant value. - * - * @see [[Expr.ConstOutput]] - */ - def const[R, P]( - value: R, - evidence: Evidence = Evidence.none, - )(implicit - capture: CaptureRootExpr[R, P], - ): Expr.ConstOutput[FactTable, R, P] = - Expr.ConstOutput(value, evidence, capture) - - /** - * Uses the value of a given fact and also considers the fact as evidence of its own value. - */ - def factValue[R, P]( - typedFact: TypedFact[R], - )(implicit - capture: CaptureRootExpr[R, P], - ): Expr.ConstOutput[FactTable, R, P] = - const(typedFact.value, Evidence(typedFact)) - - /** - * @note For better type inference, you should consider using [[ExprBuilder.returnInput]] - * - * This method may be deprecated in the future. - * - * @see [[Expr.ReturnInput]] - */ - def input[V, P]( - implicit - capture: CaptureP[V, V, P], - ): Expr.ReturnInput[V, P] = - Expr.ReturnInput(capture) - - /** - * Start the creation of an [[Expr.Define]] for a [[FactType]]. - */ - def define[T](factType: FactType[T]): DefinitionExprBuilder[T] = new DefinitionExprBuilder(factType) - - /** - * Apply the given definitions to add the produced [[Fact]]s to the [[FactTable]] for the life of the - * given `subExpr`. - * - * @param definitions a set of [[Expr.Definition]]s that will all be concatenated to the [[FactTable]] - * @param subExpr an expression that will see the defined [[Fact]]s in its [[FactTable]] - */ - def usingDefinitions[V, R, P]( - definitions: Expr.Definition[P]*, - )( - subExpr: Expr[V, R, P], - )(implicit - captureResult: CaptureP[V, R, P], - ): Expr.UsingDefinitions[V, R, P] = - Expr.UsingDefinitions(definitions.toVector, subExpr, captureResult) - - /** - * Typically, this function will be applied implicitly by the DSL. If it doesn't you may have to explicitly - * choose the type to embed. For better type inference, you should use [[ExprBuilder.embedConst]]. - * - * This method may be deprecated in the future. - * - * @see [[Expr.ConstOutput]] - */ - def embed[V, R, P]( - expr: RootExpr[R, P], - )(implicit - captureResult: CaptureP[V, R, P], - ): Expr.Embed[V, R, P] = - Expr.Embed(expr, captureResult) - - /** - * Concatenates the outputs of the expressions given. - */ - def concat[V, M[_], R, P](expressions: Expr[V, M[R], P]*): ConcatOutputExprBuilder[V, M, R, P] = - new ConcatOutputExprBuilder(expressions.to(LazyList)) - - /** - * @see [[Expr.And]] for more details of how [[Conjunction]] works during evaluation. - */ - def and[V, R : Conjunction : ExtractBoolean, P]( - first: Expr[V, R, P], - second: Expr[V, R, P], - remaining: Expr[V, R, P]*, - )(implicit - captureResult: CaptureP[V, R, P], - ): Expr.And[V, R, P] = { - val subExpressions = first :: NonEmptyList.of(second, remaining: _*) - Expr.And(subExpressions, captureResult) - } - - /** - * @see [[Expr.Or]] for more details of how [[Disjunction]] works during evaluation. - */ - def or[V, R : Disjunction : ExtractBoolean, P]( - first: Expr[V, R, P], - second: Expr[V, R, P], - remaining: Expr[V, R, P]*, - )(implicit - captureResult: CaptureP[V, R, P], - ): Expr.Or[V, R, P] = { - val subExpressions = first :: NonEmptyList.of(second, remaining: _*) - Expr.Or(subExpressions, captureResult) - } - - /** - * @see [[Expr.Not]] for more details of how [[Negation]] works during evaluation. - */ - def not[V, R : Negation, P]( - sub: Expr[V, R, P], - )(implicit - captureResult: CaptureP[V, R, P], - ): Expr.Not[V, R, P] = - Expr.Not(sub, captureResult) - - /** - * Builds an if / then / elif / else style conditional branching expression. - */ - def when[V, R, P](condExpr: CondExpr[V, P]): WhenExprBuilder[V, P] = new WhenExprBuilder(condExpr) - - /** - * Grabs all facts of a given set of types and returns them in order of fact type alphabetically, - * then by their natural ordering (as defined on the [[FactType]]). - */ - def factsOfType[T, P]( - factTypeSet: FactTypeSet[T], - )(implicit - captureInput: CaptureFromFacts[T, P], - captureAllResults: CaptureRootExpr[Seq[TypedFact[T]], P], - ): FoldableExprBuilder[FactTable, Seq, TypedFact[T], P] = - new FoldableExprBuilder( - Expr.WithFactsOfType[T, Seq[TypedFact[T]], P]( - factTypeSet, - input(captureInput), - captureAllResults, - ), - ) - - /** - * Same as [[factsOfType]], but maps the facts into their values. - */ - def valuesOfType[T, P]( - factTypeSet: FactTypeSet[T], - )(implicit - captureInput: CaptureFromFacts[T, P], - captureAllFacts: CaptureRootExpr[Seq[TypedFact[T]], P], - captureEachResult: CaptureP[TypedFact[T], T, P], - captureEachInput: CaptureP[TypedFact[T], TypedFact[T], P], - captureAllResults: CaptureP[FactTable, Seq[T], P], - ): FoldableExprBuilder[FactTable, Seq, T, P] = - factsOfType(factTypeSet).map(_.value) - - /** - * Takes a sequence of expressions and produces an expression of sequence of all the items. - * - * @see [[wrapSeq]] for varargs syntactic sugar. - * - * If you would like to put the results into a heterogeneous sequence, then you should use the [[wrap]] method. - */ - def sequence[V, R, P]( - expressions: Seq[Expr[V, R, P]], - )(implicit - captureResult: CaptureP[V, Seq[R], P], - ): Expr.WrapOutputSeq[V, R, P] = Expr.WrapOutputSeq(expressions, captureResult) - - /** - * Syntactic sugar for [[sequence]]. - * - * You should use this method over [[sequence]] when you are writing your expression in the embedded DSL. - * If you are abstracting over a sequence of expressions, then you should use [[sequence]]. - */ - @inline final def wrapSeq[V, R, P]( - expressions: Expr[V, R, P]*, - )(implicit - captureResult: CaptureP[V, Seq[R], P], - ): Expr.WrapOutputSeq[V, R, P] = sequence(expressions) - - /* - * The following DSL functions will probably be deprecated in favor of their ExprBuilder equivalents - */ - - def collectSome[V, M[_] : Foldable, U, R : Monoid, P]( - inputExpr: Expr[V, M[U], P], - collectExpr: ValExpr[U, Option[R], P], - )(implicit - capture: CaptureP[V, R, P], - ): Expr.CollectFromOutput[V, M, U, R, P] = - Expr.CollectFromOutput(inputExpr, collectExpr, capture) - - def selectFrom[V, S, R, P]( - inputExpr: Expr[V, S, P], - lens: NamedLens[S, R], - )(implicit - captureInput: CaptureP[V, R, P], - ): Expr.SelectFromOutput[V, S, R, P] = - Expr.SelectFromOutput(inputExpr, lens, captureInput) - - def add[V, R : Addition, P]( - lhs: Expr[V, R, P], - rhs: Expr[V, R, P], - )(implicit - capture: CaptureP[V, R, P], - ): Expr.AddOutputs[V, R, P] = - Expr.AddOutputs(NonEmptyList.of(lhs, rhs), capture) - - def multiply[V, R : Multiplication, P]( - lhs: Expr[V, R, P], - rhs: Expr[V, R, P], - )(implicit - capture: CaptureP[V, R, P], - ): Expr.MultiplyOutputs[V, R, P] = - Expr.MultiplyOutputs(NonEmptyList.of(lhs, rhs), capture) - - def subtract[V, R : Subtraction, P]( - lhs: Expr[V, R, P], - rhs: Expr[V, R, P], - )(implicit - capture: CaptureP[V, R, P], - ): Expr.SubtractOutputs[V, R, P] = - Expr.SubtractOutputs(NonEmptyList.of(lhs, rhs), capture) - - def divide[V, R : Division, P]( - lhs: Expr[V, R, P], - rhs: Expr[V, R, P], - )(implicit - capture: CaptureP[V, R, P], - ): Expr.DivideOutputs[V, R, P] = - Expr.DivideOutputs(NonEmptyList.of(lhs, rhs), capture) - - def pow[V, P]( - base: Expr[V, Double, P], - exp: Expr[V, Double, P], - )(implicit - capture: CaptureP[V, Double, P], - ): Expr.ExponentiateOutputs[V, P] = - Expr.ExponentiateOutputs(base, exp, capture) - - def negative[V, R : Negative, P]( - inputExpr: Expr[V, R, P], - )(implicit - capture: CaptureP[V, R, P], - ): Expr.NegativeOutput[V, R, P] = - Expr.NegativeOutput(inputExpr, capture) - - def exists[V, M[_] : Foldable, U, P]( - inputExpr: Expr[V, M[U], P], - condExpr: ValCondExpr[U, P], - )(implicit - capture: CaptureP[V, Boolean, P], - ): Expr.ExistsInOutput[V, M, U, P] = - Expr.ExistsInOutput(inputExpr, condExpr, capture) - - def returnInput[V, P]( - implicit - capture: CaptureP[V, V, P], - ): Expr.ReturnInput[V, P] = - Expr.ReturnInput(capture) - - def within[V, R, P]( - inputExpr: Expr[V, R, P], - windowExpr: Expr[V, Window[R], P], - )(implicit - capture: CaptureP[V, Boolean, P], - ): Expr.OutputWithinWindow[V, R, P] = - Expr.OutputWithinWindow(inputExpr, windowExpr, capture) -} diff --git a/core/src/main/scala/vapors/dsl/HListOperationWrapper.scala b/core/src/main/scala/vapors/dsl/HListOperationWrapper.scala deleted file mode 100644 index a5d1a53da..000000000 --- a/core/src/main/scala/vapors/dsl/HListOperationWrapper.scala +++ /dev/null @@ -1,50 +0,0 @@ -package com.rallyhealth - -package vapors.dsl - -import vapors.algebra.{CaptureP, Expr, ExprConverter, NonEmptyExprHList} - -import shapeless.ops.hlist.Tupler -import shapeless.{Generic, HList} - -trait HListOperationWrapper[V, M[_], L <: HList, P] extends Any { - - type Op[R] <: Expr[V, M[R], P] - - protected def exprHList: NonEmptyExprHList[V, M, L, P] - - protected def buildOp[R]( - converter: ExprConverter[L, R], - captureResult: CaptureP[V, M[R], P], - ): Op[R] - - final def asTuple[T]( - implicit - tupler: Tupler.Aux[L, T], - captureResult: CaptureP[V, M[T], P], - ): Op[T] = - buildOp( - ExprConverter.asTuple, - captureResult, - ) - - final def asHList( - implicit - captureResult: CaptureP[V, M[L], P], - ): Op[L] = - buildOp( - ExprConverter.asHListIdentity, - captureResult, - ) - - final def as[R]( - implicit - gen: Generic.Aux[R, L], - captureResult: CaptureP[V, M[R], P], - ): Op[R] = - buildOp( - ExprConverter.asProductType, - captureResult, - ) - -} diff --git a/core/src/main/scala/vapors/dsl/MapViewExprBuilder.scala b/core/src/main/scala/vapors/dsl/MapViewExprBuilder.scala deleted file mode 100644 index 90b27842f..000000000 --- a/core/src/main/scala/vapors/dsl/MapViewExprBuilder.scala +++ /dev/null @@ -1,91 +0,0 @@ -package com.rallyhealth - -package vapors.dsl - -import vapors.algebra.{CaptureP, Expr} -import vapors.lens.{DataPath, NamedLens} - -import cats.Id -import shapeless.Nat - -import scala.collection.{MapView, View} - -final class MapViewExprBuilder[V, K, U, P](private val inputExpr: Expr[V, MapView[K, U], P]) extends AnyVal { - - /** - * Apply the given expression over every value of the Map, without affecting the keys. - * - * TODO: The signature of this method is difficult to work with because the definition of the - * [[FoldableExprBuilder.groupBy]] method means every value is foldable. However, this - * method assumes gives you a [[ValExprBuilder]] which is almost never helpful. - */ - def mapValues[R]( - buildExpr: ValExprBuilder[(K, U), U, P] => ExprBuilder[(K, U), Id, R, P], - )(implicit - captureView: CaptureP[V, View[(K, U)], P], - captureEachTuple: CaptureP[(K, U), (K, U), P], - captureEachTupleKey: CaptureP[(K, U), K, P], - captureEachTupleValue: CaptureP[(K, U), U, P], - captureEachResultTuple: CaptureP[(K, U), (K, R), P], - captureViewResult: CaptureP[V, View[(K, R)], P], - captureMapViewResult: CaptureP[V, MapView[K, R], P], - ): MapViewExprBuilder[V, K, R, P] = - MapViewExprBuilder - .asFoldableBuilder(this) - .map { kv => - val selectKeyFromTuple = kv.get(_.copy(path = DataPath.empty.atKey(Nat._1), get = _._1)) - val selectValueFromTuple = kv.get(_.copy(path = DataPath.empty.atKey(Nat._2), get = _._2)) - val mappedValue = buildExpr(new ValExprBuilder(selectValueFromTuple)) - wrap( - selectKeyFromTuple.returnOutput, - mappedValue.returnOutput, - ).asTuple[(K, R)] - } - .toMap - - /** - * Returns every entry of the returned Map as a lazy sequential [[View]] of 2-tuples. - */ - def values( - implicit - captureView: CaptureP[V, View[(K, U)], P], - captureEachTuple: CaptureP[(K, U), (K, U), P], - captureEachTupleValue: CaptureP[(K, U), U, P], - captureResult: CaptureP[V, View[U], P], - ): FoldableExprBuilder[V, View, U, P] = { - new FoldableExprBuilder( - Expr.MapOutput( - Expr.SelectFromOutput( - inputExpr, - NamedLens.id[MapView[K, U]].asIterable.to(View), - captureView, - ), - Expr.SelectFromOutput( - Expr.ReturnInput(captureEachTuple), - NamedLens[(K, U), U](DataPath.empty.atKey(Nat._2), _._2), - captureEachTupleValue, - ), - captureResult, - ), - ) - } -} - -object MapViewExprBuilder { - - implicit def asExpr[V, K, U, P](builder: MapViewExprBuilder[V, K, U, P]): Expr[V, MapView[K, U], P] = - builder.inputExpr - - implicit def asFoldableBuilder[V, K, U, P]( - builder: MapViewExprBuilder[V, K, U, P], - )(implicit - captureView: CaptureP[V, View[(K, U)], P], - ): FoldableExprBuilder[V, View, (K, U), P] = - new FoldableExprBuilder( - Expr.SelectFromOutput( - builder.inputExpr, - NamedLens.id[MapView[K, U]].asIterable.to(View), - captureView, - ), - ) -} diff --git a/core/src/main/scala/vapors/dsl/TimeFunctions.scala b/core/src/main/scala/vapors/dsl/TimeFunctions.scala deleted file mode 100644 index 251cd7233..000000000 --- a/core/src/main/scala/vapors/dsl/TimeFunctions.scala +++ /dev/null @@ -1,132 +0,0 @@ -package com.rallyhealth - -package vapors.dsl - -import vapors.algebra.{CaptureP, Expr} -import vapors.data.FactTable -import vapors.time.{CountTime, ModifyTime} - -import shapeless._ - -import java.time.{Clock, Instant, LocalDate} - -/** - * Provides common functions work working with time. - * - * This works by providing an escape hatch for evaluating a custom Scala function (rather than building - * all the operations out of [[Expr]] nodes). - * - * @see [[Expr.CustomFunction]] - */ -trait TimeFunctions { - - /** - * Adds the result of the `rhs` temporal amount expression to the result of the `lhs` temporal expression. - * - * Requires a definition of [[ModifyTime]] for the two types. This is a safer API than working directly with - * Java's Time library because it forbids adding [[java.time.Duration]]s to a [[LocalDate]] or adding a - * [[java.time.Period]] to an [[Instant]]. Java Time will just throw an exception if the types are incompatible. - * - * @param lhsExpr an expression that computes a date and / or time - * @param rhsExpr an expression that produces the duration of time to add (can be negative) - * - */ - // TODO: Can this be converted to Addition[T] if we make the second argument a different type? Maybe AddLeft[T, D]? - final def dateAdd[V, T, D, P]( - lhsExpr: Expr[V, T, P], - rhsExpr: Expr[V, D, P], - )(implicit - modTime: ModifyTime[T, D], - argTypeInfo: Typeable[T :: D :: HNil], - captureArgResult: CaptureP[V, T :: D :: HNil, P], - captureResult: CaptureP[V, T, P], - ): Expr[V, T, P] = { - val argExpr = wrap(lhsExpr, rhsExpr).asHList.returnOutput - val fn: T :: D :: HNil => T = { - case lhs :: rhs :: HNil => - ModifyTime.addTo(lhs, rhs) - } - Expr.CustomFunction(argExpr, "date_add", fn, captureResult) - } - - /** - * Computes the difference between the `lhsExpr` and the `rhsExpr` as an integral value of the given unit - * (can be negative). The result will be rounded down to the maximum number of whole units between the two - * temporal instances that can be counted. - * - * This requires an instance of [[CountTime]] to make sure that the given unit type is safe to count for - * the given temporal types. This is a safer API than working directly with Java's Time library because it - * forbids subtracting two different temporal types from each other, without first converting them appropriately. - * Java Time will just throw an exception if the types are incompatible. - * - * @param lhsExpr an expression that computes the temporal instant of when to start counting - * @param rhsExpr an expression that computes the temporal instant of when to stop counting - * (either counting units of time in the past or future from lhsExpr) - * @param roundToUnitExpr the type of unit to round down to - */ - final def dateDiff[V, T, U, P]( - lhsExpr: Expr[V, T, P], - rhsExpr: Expr[V, T, P], - roundToUnitExpr: Expr[V, U, P], - )(implicit - countTime: CountTime[T, U], - argTypeInfo: Typeable[T :: T :: U :: HNil], - captureArgResult: CaptureP[V, T :: T :: U :: HNil, P], - captureResult: CaptureP[V, Long, P], - ): Expr[V, Long, P] = { - val argExpr = wrap(lhsExpr, rhsExpr, roundToUnitExpr).asHList.returnOutput - val fn: T :: T :: U :: HNil => Long = { - case lhs :: rhs :: unit :: HNil => - CountTime.between(lhs, rhs, unit) - } - Expr.CustomFunction(argExpr, "date_diff", fn, captureResult) - } - - /** - * Returns an expression that computes the current [[LocalDate]] when it is invoke, using the system - * [[Clock]] with the default timezone information of host machine / JVM. - */ - final def today[V, P]( - implicit - captureClock: CaptureP[FactTable, Clock, P], - captureEmbed: CaptureP[V, Clock, P], - captureResult: CaptureP[V, LocalDate, P], - ): Expr[V, LocalDate, P] = - today(Clock.systemDefaultZone()) - - /** - * Returns an expression that computes the current [[LocalDate]] when it is invoked, based on the given [[Clock]]. - */ - final def today[V, P]( - clock: Clock, - )(implicit - captureClock: CaptureP[FactTable, Clock, P], - captureEmbed: CaptureP[V, Clock, P], - captureResult: CaptureP[V, LocalDate, P], - ): Expr[V, LocalDate, P] = - Expr.CustomFunction[V, Clock, LocalDate, P](embed(const(clock)), "today", LocalDate.now, captureResult) - - /** - * Returns an expression that computes the current [[Instant]] when it is invoked, using the UTC [[Clock]]. - */ - final def now[V, P]( - implicit - captureClock: CaptureP[FactTable, Clock, P], - captureEmbed: CaptureP[V, Clock, P], - captureResult: CaptureP[V, Instant, P], - ): Expr[V, Instant, P] = - now(Clock.systemUTC()) - - /** - * Returns an expression that computes the current [[Instant]] when it is invoked, based on the given [[Clock]]. - */ - final def now[V, P]( - clock: Clock, - )(implicit - captureClock: CaptureP[FactTable, Clock, P], - captureEmbed: CaptureP[V, Clock, P], - captureResult: CaptureP[V, Instant, P], - ): Expr[V, Instant, P] = - Expr.CustomFunction[V, Clock, Instant, P](embed(const(clock)), "now", Instant.now, captureResult) - -} diff --git a/core/src/main/scala/vapors/dsl/WhenExprBuilder.scala b/core/src/main/scala/vapors/dsl/WhenExprBuilder.scala deleted file mode 100644 index 1452870a9..000000000 --- a/core/src/main/scala/vapors/dsl/WhenExprBuilder.scala +++ /dev/null @@ -1,32 +0,0 @@ -package com.rallyhealth - -package vapors.dsl - -import vapors.algebra.{CaptureP, ConditionBranch, Expr} - -import cats.data.NonEmptyList - -final class WhenExprBuilder[V, P](private val whenExpr: CondExpr[V, P]) extends AnyVal { - - def thenReturn[R](thenExpr: Expr[V, R, P]): WhenElseExprBuilder[V, R, P] = - new WhenElseExprBuilder(NonEmptyList.of(ConditionBranch(whenExpr, thenExpr))) -} - -final class WhenElifExprBuilder[V, R, P](private val t: (CondExpr[V, P], NonEmptyList[ConditionBranch[V, R, P]])) - extends AnyVal { - - def thenReturn(thenExpr: Expr[V, R, P]): WhenElseExprBuilder[V, R, P] = - new WhenElseExprBuilder(ConditionBranch(t._1, thenExpr) :: t._2) -} - -final class WhenElseExprBuilder[V, R, P](private val branches: NonEmptyList[ConditionBranch[V, R, P]]) extends AnyVal { - - def elseReturn( - elseExpr: Expr[V, R, P], - )(implicit - captureResult: CaptureP[V, R, P], - ): Expr.When[V, R, P] = - Expr.When(branches.reverse, elseExpr, captureResult) - - def elif(elifExpr: CondExpr[V, P]): WhenElifExprBuilder[V, R, P] = new WhenElifExprBuilder((elifExpr, branches)) -} diff --git a/core/src/main/scala/vapors/dsl/WithFactsOfTypeBuilder.scala b/core/src/main/scala/vapors/dsl/WithFactsOfTypeBuilder.scala deleted file mode 100644 index 93841e4b6..000000000 --- a/core/src/main/scala/vapors/dsl/WithFactsOfTypeBuilder.scala +++ /dev/null @@ -1,37 +0,0 @@ -package com.rallyhealth - -package vapors.dsl - -import vapors.algebra.{CaptureP, Expr} -import vapors.data.{FactTypeSet, TypedFact} - -/** - * @note this is not a value class because the input [[FactTypeSet]] is also a value class AND the [[CaptureP]] - * implicit is needed in the original [[withFactsOfType]] method in order for type inference to work properly. - * We could ask for the implicit again, but that is confusing for the caller who would have to look at the - * source code to know which instance is actually used. - */ -final class WithFactsOfTypeBuilder[T, P]( - factTypeSet: FactTypeSet[T], -)(implicit - captureInput: CaptureFromFacts[T, P], -) { - - def where[M[_], U]( - buildSubExpr: ExprBuilder.FoldableFn[Seq, TypedFact[T], M, U, P], - )(implicit - captureResult: CaptureRootExpr[M[U], P], - ): Expr.WithFactsOfType[T, M[U], P] = - Expr.WithFactsOfType( - factTypeSet, - buildSubExpr(new FoldableExprBuilder(input)).returnOutput, - captureResult, - ) - - def returnInput( - implicit - captureInput: CaptureFromFacts[T, P], - captureResult: CaptureRootExpr[Seq[TypedFact[T]], P], - ): Expr.WithFactsOfType[T, Seq[TypedFact[T]], P] = - Expr.WithFactsOfType(factTypeSet, input(captureInput), captureResult) -} diff --git a/core/src/main/scala/vapors/dsl/WithOutputSyntax.scala b/core/src/main/scala/vapors/dsl/WithOutputSyntax.scala deleted file mode 100644 index 667d201ef..000000000 --- a/core/src/main/scala/vapors/dsl/WithOutputSyntax.scala +++ /dev/null @@ -1,38 +0,0 @@ -package com.rallyhealth - -package vapors.dsl - -import vapors.algebra.Expr - -import cats.Foldable - -trait WithOutputSyntax { - - implicit def thenChainValue[V, R, P](expr: Expr[V, R, P]): WithOutputValueExprBuilder[V, R, P] = - new WithOutputValueExprBuilder(expr) - - implicit def thenChainFoldable[V, M[_], R, P](expr: Expr[V, M[R], P]): WithOutputFoldableExprBuilder[V, M, R, P] = - new WithOutputFoldableExprBuilder(expr) - -} - -// TODO: These classes would not be needed if everything returned builders -final class WithOutputValueExprBuilder[V, R, P](private val expr: Expr[V, R, P]) extends AnyVal { - - /** - * Converts any [[Expr]] node into a [[ValExprBuilder]]. - */ - def withOutputValue: ValExprBuilder[V, R, P] = new ValExprBuilder(expr) -} - -final class WithOutputFoldableExprBuilder[V, M[_], R, P](private val expr: Expr[V, M[R], P]) extends AnyVal { - - /** - * Converts any [[Expr]] for which there is a [[Foldable]] definition for the return type constructor - * into a [[FoldableExprBuilder]]. - */ - def withOutputFoldable( - implicit - foldableM: Foldable[M], - ): FoldableExprBuilder[V, M, R, P] = new FoldableExprBuilder(expr) -} diff --git a/core/src/main/scala/vapors/dsl/WrapEachExprSyntax.scala b/core/src/main/scala/vapors/dsl/WrapEachExprSyntax.scala deleted file mode 100644 index 0135937cf..000000000 --- a/core/src/main/scala/vapors/dsl/WrapEachExprSyntax.scala +++ /dev/null @@ -1,172 +0,0 @@ -package com.rallyhealth - -package vapors.dsl - -import vapors.algebra.{CaptureP, Expr, ExprConverter, NonEmptyExprHList} - -import cats.{Align, FunctorFilter} -import shapeless.{::, HList, HNil} - -/** - * Provides the ability to wrap expressions with the same input and captured parameter but different outputs, - * and then transform this [[HList]] of expressions into an [[Expr]] of a higher-kinded wrapper around an - * [[HList]] return type. - * - * @see similar to [[WrapExprSyntax]] accept this operates over a type-constructor to allow performing additional - * capabilities to the returned value. - */ -trait WrapEachExprSyntax { - - def wrapEach[V, M[_], E1, E2, P]( - e1: Expr[V, M[E1], P], - e2: Expr[V, M[E2], P], - ): WrapEachExprHListWrapper[V, M, E1 :: E2 :: HNil, P] = - new WrapEachExprHListWrapper(e1 :: NonEmptyExprHList.tailK(e2)) - - def wrapEach[V, M[_], E1, E2, E3, P]( - e1: Expr[V, M[E1], P], - e2: Expr[V, M[E2], P], - e3: Expr[V, M[E3], P], - ): WrapEachExprHListWrapper[V, M, E1 :: E2 :: E3 :: HNil, P] = - new WrapEachExprHListWrapper(e1 :: e2 :: NonEmptyExprHList.tailK(e3)) - - def wrapEach[V, M[_], E1, E2, E3, E4, P]( - e1: Expr[V, M[E1], P], - e2: Expr[V, M[E2], P], - e3: Expr[V, M[E3], P], - e4: Expr[V, M[E4], P], - ): WrapEachExprHListWrapper[V, M, E1 :: E2 :: E3 :: E4 :: HNil, P] = - new WrapEachExprHListWrapper(e1 :: e2 :: e3 :: NonEmptyExprHList.tailK(e4)) - - def wrapEach[V, M[_], E1, E2, E3, E4, E5, P]( - e1: Expr[V, M[E1], P], - e2: Expr[V, M[E2], P], - e3: Expr[V, M[E3], P], - e4: Expr[V, M[E4], P], - e5: Expr[V, M[E5], P], - ): WrapEachExprHListWrapper[V, M, E1 :: E2 :: E3 :: E4 :: E5 :: HNil, P] = - new WrapEachExprHListWrapper(e1 :: e2 :: e3 :: e4 :: NonEmptyExprHList.tailK(e5)) - - def wrapEach[V, M[_], E1, E2, E3, E4, E5, E6, P]( - e1: Expr[V, M[E1], P], - e2: Expr[V, M[E2], P], - e3: Expr[V, M[E3], P], - e4: Expr[V, M[E4], P], - e5: Expr[V, M[E5], P], - e6: Expr[V, M[E6], P], - ): WrapEachExprHListWrapper[V, M, E1 :: E2 :: E3 :: E4 :: E5 :: E6 :: HNil, P] = - new WrapEachExprHListWrapper(e1 :: e2 :: e3 :: e4 :: e5 :: NonEmptyExprHList.tailK(e6)) - - def wrapEach[V, M[_], E1, E2, E3, E4, E5, E6, E7, P]( - e1: Expr[V, M[E1], P], - e2: Expr[V, M[E2], P], - e3: Expr[V, M[E3], P], - e4: Expr[V, M[E4], P], - e5: Expr[V, M[E5], P], - e6: Expr[V, M[E6], P], - e7: Expr[V, M[E7], P], - ): WrapEachExprHListWrapper[V, M, E1 :: E2 :: E3 :: E4 :: E5 :: E6 :: E7 :: HNil, P] = - new WrapEachExprHListWrapper(e1 :: e2 :: e3 :: e4 :: e5 :: e6 :: NonEmptyExprHList.tailK(e7)) - - def wrapEach[V, M[_], E1, E2, E3, E4, E5, E6, E7, E8, P]( - e1: Expr[V, M[E1], P], - e2: Expr[V, M[E2], P], - e3: Expr[V, M[E3], P], - e4: Expr[V, M[E4], P], - e5: Expr[V, M[E5], P], - e6: Expr[V, M[E6], P], - e7: Expr[V, M[E7], P], - e8: Expr[V, M[E8], P], - ): WrapEachExprHListWrapper[V, M, E1 :: E2 :: E3 :: E4 :: E5 :: E6 :: E7 :: E8 :: HNil, P] = - new WrapEachExprHListWrapper(e1 :: e2 :: e3 :: e4 :: e5 :: e6 :: e7 :: NonEmptyExprHList.tailK(e8)) - - def wrapEach[V, M[_], E1, E2, E3, E4, E5, E6, E7, E8, E9, P]( - e1: Expr[V, M[E1], P], - e2: Expr[V, M[E2], P], - e3: Expr[V, M[E3], P], - e4: Expr[V, M[E4], P], - e5: Expr[V, M[E5], P], - e6: Expr[V, M[E6], P], - e7: Expr[V, M[E7], P], - e8: Expr[V, M[E8], P], - e9: Expr[V, M[E9], P], - ): WrapEachExprHListWrapper[V, M, E1 :: E2 :: E3 :: E4 :: E5 :: E6 :: E7 :: E8 :: E9 :: HNil, P] = - new WrapEachExprHListWrapper(e1 :: e2 :: e3 :: e4 :: e5 :: e6 :: e7 :: e8 :: NonEmptyExprHList.tailK(e9)) - - def wrapEach[V, M[_], E1, E2, E3, E4, E5, E6, E7, E8, E9, E10, P]( - e1: Expr[V, M[E1], P], - e2: Expr[V, M[E2], P], - e3: Expr[V, M[E3], P], - e4: Expr[V, M[E4], P], - e5: Expr[V, M[E5], P], - e6: Expr[V, M[E6], P], - e7: Expr[V, M[E7], P], - e8: Expr[V, M[E8], P], - e9: Expr[V, M[E9], P], - e10: Expr[V, M[E10], P], - ): WrapEachExprHListWrapper[V, M, E1 :: E2 :: E3 :: E4 :: E5 :: E6 :: E7 :: E8 :: E9 :: E10 :: HNil, P] = - new WrapEachExprHListWrapper(e1 :: e2 :: e3 :: e4 :: e5 :: e6 :: e7 :: e8 :: e9 :: NonEmptyExprHList.tailK(e10)) - - def wrapEach[V, M[_], E1, E2, E3, E4, E5, E6, E7, E8, E9, E10, E11, P]( - e1: Expr[V, M[E1], P], - e2: Expr[V, M[E2], P], - e3: Expr[V, M[E3], P], - e4: Expr[V, M[E4], P], - e5: Expr[V, M[E5], P], - e6: Expr[V, M[E6], P], - e7: Expr[V, M[E7], P], - e8: Expr[V, M[E8], P], - e9: Expr[V, M[E9], P], - e10: Expr[V, M[E10], P], - e11: Expr[V, M[E11], P], - ): WrapEachExprHListWrapper[V, M, E1 :: E2 :: E3 :: E4 :: E5 :: E6 :: E7 :: E8 :: E9 :: E10 :: E11 :: HNil, P] = - new WrapEachExprHListWrapper( - e1 :: e2 :: e3 :: e4 :: e5 :: e6 :: e7 :: e8 :: e9 :: e10 :: NonEmptyExprHList.tailK(e11), - ) - - def wrapEach[V, M[_], E1, E2, E3, E4, E5, E6, E7, E8, E9, E10, E11, E12, P]( - e1: Expr[V, M[E1], P], - e2: Expr[V, M[E2], P], - e3: Expr[V, M[E3], P], - e4: Expr[V, M[E4], P], - e5: Expr[V, M[E5], P], - e6: Expr[V, M[E6], P], - e7: Expr[V, M[E7], P], - e8: Expr[V, M[E8], P], - e9: Expr[V, M[E9], P], - e10: Expr[V, M[E10], P], - e11: Expr[V, M[E11], P], - e12: Expr[V, M[E12], P], - ): WrapEachExprHListWrapper[ - V, - M, - E1 :: E2 :: E3 :: E4 :: E5 :: E6 :: E7 :: E8 :: E9 :: E10 :: E11 :: E12 :: HNil, - P, - ] = - new WrapEachExprHListWrapper( - e1 :: e2 :: e3 :: e4 :: e5 :: e6 :: e7 :: e8 :: e9 :: e10 :: e11 :: NonEmptyExprHList.tailK(e12), - ) -} - -final class WrapEachExprHListWrapper[V, M[_], L <: HList, P](private val exprHList: NonEmptyExprHList[V, M, L, P]) - extends AnyVal { - - def zippedToShortest( - implicit - alignM: Align[M], - filterM: FunctorFilter[M], - ): ZippedToShortestExprWrapper[V, M, L, P] = new ZippedToShortestExprWrapper(exprHList) -} - -final class ZippedToShortestExprWrapper[V, M[_] : Align : FunctorFilter, L <: HList, P]( - override protected val exprHList: NonEmptyExprHList[V, M, L, P], -) extends HListOperationWrapper[V, M, L, P] { - - override type Op[R] = Expr.ZipOutput[V, M, L, R, P] - - override protected def buildOp[R]( - converter: ExprConverter[L, R], - captureResult: CaptureP[V, M[R], P], - ): Expr.ZipOutput[V, M, L, R, P] = - Expr.ZipOutput(exprHList, converter, captureResult) -} diff --git a/core/src/main/scala/vapors/dsl/WrapExprSyntax.scala b/core/src/main/scala/vapors/dsl/WrapExprSyntax.scala deleted file mode 100644 index 784d9df62..000000000 --- a/core/src/main/scala/vapors/dsl/WrapExprSyntax.scala +++ /dev/null @@ -1,168 +0,0 @@ -package com.rallyhealth - -package vapors.dsl - -import vapors.algebra.{CaptureP, Expr, ExprConverter, NonEmptyExprHList} - -import cats.Id -import shapeless.{::, Generic, HList, HNil} - -/** - * Provides the ability to wrap expressions with the same input and captured parameter but different outputs, - * and then transform this [[HList]] of expressions into an [[Expr]] of an [[HList]] return type. - * - * @see similar to [[WrapEachExprSyntax]] except it does not require a shared higher-kinded type constructor - * for the output value. - */ -trait WrapExprSyntax { - - def wrap[V, E1, P](e1: Expr[V, E1, P]): ExprHListWrapper[V, E1 :: HNil, P] = - new ExprHListWrapper(NonEmptyExprHList.tail(e1)) - - def wrap[V, E1, E2, P]( - e1: Expr[V, E1, P], - e2: Expr[V, E2, P], - ): ExprHListWrapper[V, E1 :: E2 :: HNil, P] = - new ExprHListWrapper(e1 :: NonEmptyExprHList.tail(e2)) - - def wrap[V, E1, E2, E3, P]( - e1: Expr[V, E1, P], - e2: Expr[V, E2, P], - e3: Expr[V, E3, P], - ): ExprHListWrapper[V, E1 :: E2 :: E3 :: HNil, P] = - new ExprHListWrapper(e1 :: e2 :: NonEmptyExprHList.tail(e3)) - - def wrap[V, E1, E2, E3, E4, P]( - e1: Expr[V, E1, P], - e2: Expr[V, E2, P], - e3: Expr[V, E3, P], - e4: Expr[V, E4, P], - ): ExprHListWrapper[V, E1 :: E2 :: E3 :: E4 :: HNil, P] = - new ExprHListWrapper(e1 :: e2 :: e3 :: NonEmptyExprHList.tail(e4)) - - def wrap[V, E1, E2, E3, E4, E5, P]( - e1: Expr[V, E1, P], - e2: Expr[V, E2, P], - e3: Expr[V, E3, P], - e4: Expr[V, E4, P], - e5: Expr[V, E5, P], - ): ExprHListWrapper[V, E1 :: E2 :: E3 :: E4 :: E5 :: HNil, P] = - new ExprHListWrapper(e1 :: e2 :: e3 :: e4 :: NonEmptyExprHList.tail(e5)) - - def wrap[V, E1, E2, E3, E4, E5, E6, P]( - e1: Expr[V, E1, P], - e2: Expr[V, E2, P], - e3: Expr[V, E3, P], - e4: Expr[V, E4, P], - e5: Expr[V, E5, P], - e6: Expr[V, E6, P], - ): ExprHListWrapper[V, E1 :: E2 :: E3 :: E4 :: E5 :: E6 :: HNil, P] = - new ExprHListWrapper(e1 :: e2 :: e3 :: e4 :: e5 :: NonEmptyExprHList.tail(e6)) - - def wrap[V, E1, E2, E3, E4, E5, E6, E7, P]( - e1: Expr[V, E1, P], - e2: Expr[V, E2, P], - e3: Expr[V, E3, P], - e4: Expr[V, E4, P], - e5: Expr[V, E5, P], - e6: Expr[V, E6, P], - e7: Expr[V, E7, P], - ): ExprHListWrapper[V, E1 :: E2 :: E3 :: E4 :: E5 :: E6 :: E7 :: HNil, P] = - new ExprHListWrapper(e1 :: e2 :: e3 :: e4 :: e5 :: e6 :: NonEmptyExprHList.tail(e7)) - - def wrap[V, E1, E2, E3, E4, E5, E6, E7, E8, P]( - e1: Expr[V, E1, P], - e2: Expr[V, E2, P], - e3: Expr[V, E3, P], - e4: Expr[V, E4, P], - e5: Expr[V, E5, P], - e6: Expr[V, E6, P], - e7: Expr[V, E7, P], - e8: Expr[V, E8, P], - ): ExprHListWrapper[V, E1 :: E2 :: E3 :: E4 :: E5 :: E6 :: E7 :: E8 :: HNil, P] = - new ExprHListWrapper(e1 :: e2 :: e3 :: e4 :: e5 :: e6 :: e7 :: NonEmptyExprHList.tail(e8)) - - def wrap[V, E1, E2, E3, E4, E5, E6, E7, E8, E9, P]( - e1: Expr[V, E1, P], - e2: Expr[V, E2, P], - e3: Expr[V, E3, P], - e4: Expr[V, E4, P], - e5: Expr[V, E5, P], - e6: Expr[V, E6, P], - e7: Expr[V, E7, P], - e8: Expr[V, E8, P], - e9: Expr[V, E9, P], - ): ExprHListWrapper[V, E1 :: E2 :: E3 :: E4 :: E5 :: E6 :: E7 :: E8 :: E9 :: HNil, P] = - new ExprHListWrapper(e1 :: e2 :: e3 :: e4 :: e5 :: e6 :: e7 :: e8 :: NonEmptyExprHList.tail(e9)) - - def wrap[V, E1, E2, E3, E4, E5, E6, E7, E8, E9, E10, P]( - e1: Expr[V, E1, P], - e2: Expr[V, E2, P], - e3: Expr[V, E3, P], - e4: Expr[V, E4, P], - e5: Expr[V, E5, P], - e6: Expr[V, E6, P], - e7: Expr[V, E7, P], - e8: Expr[V, E8, P], - e9: Expr[V, E9, P], - e10: Expr[V, E10, P], - ): ExprHListWrapper[V, E1 :: E2 :: E3 :: E4 :: E5 :: E6 :: E7 :: E8 :: E9 :: E10 :: HNil, P] = - new ExprHListWrapper(e1 :: e2 :: e3 :: e4 :: e5 :: e6 :: e7 :: e8 :: e9 :: NonEmptyExprHList.tail(e10)) - - def wrap[V, E1, E2, E3, E4, E5, E6, E7, E8, E9, E10, E11, P]( - e1: Expr[V, E1, P], - e2: Expr[V, E2, P], - e3: Expr[V, E3, P], - e4: Expr[V, E4, P], - e5: Expr[V, E5, P], - e6: Expr[V, E6, P], - e7: Expr[V, E7, P], - e8: Expr[V, E8, P], - e9: Expr[V, E9, P], - e10: Expr[V, E10, P], - e11: Expr[V, E11, P], - ): ExprHListWrapper[V, E1 :: E2 :: E3 :: E4 :: E5 :: E6 :: E7 :: E8 :: E9 :: E10 :: E11 :: HNil, P] = - new ExprHListWrapper(e1 :: e2 :: e3 :: e4 :: e5 :: e6 :: e7 :: e8 :: e9 :: e10 :: NonEmptyExprHList.tail(e11)) - - def wrap[V, E1, E2, E3, E4, E5, E6, E7, E8, E9, E10, E11, E12, P]( - e1: Expr[V, E1, P], - e2: Expr[V, E2, P], - e3: Expr[V, E3, P], - e4: Expr[V, E4, P], - e5: Expr[V, E5, P], - e6: Expr[V, E6, P], - e7: Expr[V, E7, P], - e8: Expr[V, E8, P], - e9: Expr[V, E9, P], - e10: Expr[V, E10, P], - e11: Expr[V, E11, P], - e12: Expr[V, E12, P], - ): ExprHListWrapper[V, E1 :: E2 :: E3 :: E4 :: E5 :: E6 :: E7 :: E8 :: E9 :: E10 :: E11 :: E12 :: HNil, P] = - new ExprHListWrapper( - e1 :: e2 :: e3 :: e4 :: e5 :: e6 :: e7 :: e8 :: e9 :: e10 :: e11 :: NonEmptyExprHList.tail(e12), - ) - -} - -final class ExprHListWrapper[V, L <: HList, P](override protected val exprHList: NonEmptyExprHList[V, Id, L, P]) - extends AnyVal - with HListOperationWrapper[V, Id, L, P] { - - override type Op[R] = Expr.WrapOutputHList[V, L, R, P] - - override protected def buildOp[R]( - converter: ExprConverter[L, R], - captureResult: CaptureP[V, Id[R], P], - ): Expr.WrapOutputHList[V, L, R, P] = - Expr.WrapOutputHList(exprHList, converter, captureResult) -} - -final class GenericIdentity[R] extends Generic[R] { - override type Repr = R - override def to(repr: R): Repr = repr - override def from(repr: Repr): R = repr -} - -object GenericIdentity { - def apply[R]: GenericIdentity[R] = new GenericIdentity[R] -} diff --git a/core/src/main/scala/vapors/dsl/package.scala b/core/src/main/scala/vapors/dsl/package.scala deleted file mode 100644 index 603854984..000000000 --- a/core/src/main/scala/vapors/dsl/package.scala +++ /dev/null @@ -1,25 +0,0 @@ -package com.rallyhealth - -package vapors - -import vapors.algebra.{CaptureP, Expr} -import vapors.data.{FactTable, TypedFact} - -package object dsl extends ExprDsl with ExprBuilderSyntax with WithOutputSyntax with ExprBuilderCatsInstances { - - final type CondExpr[V, P] = Expr[V, Boolean, P] - - final type ValExpr[V, R, P] = Expr[V, R, P] - final type ValCondExpr[V, P] = ValExpr[V, Boolean, P] - - /** - * An [[Expr]] that has no input dependency and only operates on the [[FactTable]]. - * - * A root expression can be embedded inside of any other expression using an [[Expr.Embed]] node - * (these will typically be applied automatically, but you can always forcibly embed the expressions). - */ - final type RootExpr[R, P] = Expr[FactTable, R, P] - - final type CaptureRootExpr[R, P] = CaptureP[FactTable, R, P] - final type CaptureFromFacts[T, P] = CaptureP[Seq[TypedFact[T]], Seq[TypedFact[T]], P] -} diff --git a/core/src/main/scala/vapors/interpreter/ExprInput.scala b/core/src/main/scala/vapors/interpreter/ExprInput.scala deleted file mode 100644 index d5b31e022..000000000 --- a/core/src/main/scala/vapors/interpreter/ExprInput.scala +++ /dev/null @@ -1,38 +0,0 @@ -package com.rallyhealth - -package vapors.interpreter - -import vapors.data.{Evidence, FactTable} - -final case class ExprInput[V]( - value: V, - evidence: Evidence, - factTable: FactTable, -) { - - @inline def withValue[U]( - value: U, - evidence: Evidence = this.evidence, - ): ExprInput[U] = copy(value = value, evidence = evidence) -} - -object ExprInput { - - type Init = ExprInput[FactTable] - - @inline def fromFactTable(factTable: FactTable): Init = - ExprInput(factTable, Evidence.none, factTable) - - @inline def fromValue[V]( - value: V, - evidence: Evidence, - factTable: FactTable, - ): ExprInput[V] = - ExprInput(value, evidence, factTable) - - @inline def fromValue[V]( - value: V, - evidence: Evidence = Evidence.none, - ): ExprInput[V] = - fromValue(value, evidence, FactTable(evidence.factSet)) -} diff --git a/core/src/main/scala/vapors/interpreter/ExprOutput.scala b/core/src/main/scala/vapors/interpreter/ExprOutput.scala deleted file mode 100644 index d3eb9490f..000000000 --- a/core/src/main/scala/vapors/interpreter/ExprOutput.scala +++ /dev/null @@ -1,152 +0,0 @@ -package com.rallyhealth - -package vapors.interpreter - -import vapors.data.{Evidence, ExtractBoolean, ExtractValue} -import vapors.logic.{Conjunction, Disjunction, Negation} - -import cats.Monoid - -/** - * The result of a computation without any debugging information (i.e. captured parameter or result nodes) - * - * @param value the result of the computation - * @param evidence the facts used in the computation - */ -final case class ExprOutput[R]( - value: R, - evidence: Evidence, -) - -object ExprOutput { - - /** - * Logical Conjunction (aka AND) - * - * - If either side is false, the result is false - * - Evidence of falseness is accumulated - * - Evidence of truthiness requires evidence of truth on both sides, otherwise no evidence of truth - * - * Examples: - * - * | X is True | Evidence of X | Y is True | Evidence of Y | Result is True | Evidence of Result | - * | --------- | ------------- | --------- | ------------- | -------------- | ------------------ | - * | T | {} | T | {} | T | {} | - * | T | {} | F | {} | F | {} | - * | T | {} | T | {A} | T | {} | - * | T | {} | F | {A} | F | {A} | - * | T | {B} | T | {A} | T | {A, B} | - * | T | {B} | F | {} | F | {} | - * | T | {B} | T | {} | T | {} | - * | T | {B} | F | {A} | F | {A} | - * | F | {} | T | {A} | F | {} | - * | F | {B} | F | {A} | F | {A, B} | - * | F | {B} | T | {A} | F | {B} | - * | F | {B} | F | {} | F | {B} | - * | F | {} | F | {A} | F | {} | - */ - implicit def conjunction[R : Conjunction : ExtractBoolean]: Conjunction[ExprOutput[R]] = - (lhs: ExprOutput[R], rhs: ExprOutput[R]) => { - import cats.syntax.apply._ - @inline def isTrue(output: ExprOutput[R]): Boolean = ExtractValue[Boolean](output.value) - val value = Conjunction[R].conjunction(lhs.value, rhs.value) - val evidence = { - if (ExtractValue[Boolean](value)) { - // only combine evidence of truthiness if both sides are true - val evTrueL = Option.when(isTrue(lhs))(lhs.evidence) - val evTrueR = Option.when(isTrue(rhs))(rhs.evidence) - (evTrueL, evTrueR).mapN(_ ++ _).getOrElse(Evidence.none) - } else { - val evFalseL = Option.unless(isTrue(lhs))(lhs.evidence) - val evFalseR = Option.unless(isTrue(rhs))(rhs.evidence) - // combine any evidence of falseness - (evFalseL ++ evFalseR).foldLeft(Evidence.none) { case (l, r) => l ++ r } - } - } - ExprOutput(value, evidence) - } - - /** - * Logical Disjunction (aka inclusive OR) - * - * - If either side is true, the result is true - * - Evidence of truthiness is accumulated - * - Evidence of falseness requires evidence of false on both sides, otherwise there is no evidence of false - * - * Examples: - * - * | X is True | Evidence of X | Y is True | Evidence of Y | Result is True | Evidence of Result | - * | --------- | ------------- | --------- | ------------- | -------------- | ------------------ | - * | T | {} | T | {} | T | {} | - * | T | {} | F | {} | T | {} | - * | T | {} | T | {A} | T | {A} | - * | T | {} | F | {A} | T | {} | - * | T | {B} | T | {A} | T | {A, B} | - * | T | {B} | F | {} | T | {B} | - * | T | {B} | T | {} | T | {B} | - * | T | {B} | F | {A} | T | {B} | - * | F | {} | T | {A} | T | {A} | - * | F | {B} | F | {A} | F | {A, B} | - * | F | {B} | T | {A} | T | {A} | - * | F | {B} | F | {} | F | {} | - * | F | {} | F | {A} | F | {} | - */ - implicit def disjunction[R : Disjunction : ExtractBoolean]: Disjunction[ExprOutput[R]] = - (lhs: ExprOutput[R], rhs: ExprOutput[R]) => { - import cats.syntax.apply._ - @inline def isTrue(output: ExprOutput[R]): Boolean = ExtractValue[Boolean](output.value) - val value = Disjunction[R].disjunction(lhs.value, rhs.value) - val evidence = { - if (ExtractValue[Boolean](value)) { - // combine all evidence of truthiness from sides that are truthy - val evTrueL = Option.when(isTrue(lhs))(lhs.evidence) - val evTrueR = Option.when(isTrue(rhs))(rhs.evidence) - (evTrueL ++ evTrueR).foldLeft(Evidence.none) { case (l, r) => l ++ r } - } else { - // only combine evidence of falseness if both sides are false - val evFalseL = Option.unless(lhs.evidence.isEmpty)(lhs.evidence) - val evFalseR = Option.unless(rhs.evidence.isEmpty)(rhs.evidence) - (evFalseL, evFalseR).mapN(_ ++ _).getOrElse(Evidence.none) - } - } - ExprOutput(value, evidence) - } - - /** - * Negates the result value without affecting the [[Evidence]]. - */ - implicit def negation[R : Negation]: Negation[ExprOutput[R]] = { output => - val negatedValue = Negation[R].negation(output.value) - output.copy(value = negatedValue) - } - - /** - * Combine two monoidal values and union their evidence. - * - * @note This is not _always_ safe. There may be some combinations of values in which you must combine - * [[Evidence]] for the resulting value differently based on the inputs. However, this is not the - * general purpose or intent of saying something is a [[Monoid]], so this definition should be - * generally safe. For example, there is no standard definition of [[Monoid]] for Boolean, because - * there is no safe assumption for and "empty" boolean value. However, evidence of true || true is - * not necessarily the same as evidence of true && true. Any definitions for which this distinction - * matters will typically use its own typeclasses, such as [[Conjunction]] or [[Disjunction]]. - */ - implicit def monoid[A : Monoid]: Monoid[ExprOutput[A]] = { - new Monoid[ExprOutput[A]] { - - override final def empty: ExprOutput[A] = { - ExprOutput(Monoid[A].empty, Evidence.none) - } - - override final def combine( - x: ExprOutput[A], - y: ExprOutput[A], - ): ExprOutput[A] = { - ExprOutput( - Monoid[A].combine(x.value, y.value), - x.evidence ++ y.evidence, - ) - } - } - } -} diff --git a/core/src/main/scala/vapors/interpreter/InterpretExprAsResultFn.scala b/core/src/main/scala/vapors/interpreter/InterpretExprAsResultFn.scala deleted file mode 100644 index 06f996b34..000000000 --- a/core/src/main/scala/vapors/interpreter/InterpretExprAsResultFn.scala +++ /dev/null @@ -1,614 +0,0 @@ -package com.rallyhealth - -package vapors.interpreter - -import vapors.algebra.{ConditionBranch, Expr, ExprResult, NonEmptyExprHList} -import vapors.data._ -import vapors.interpreter.InterpretExprAsSimpleOutputFn.SimpleOutputFnFunctorBuilder -import vapors.logic._ -import vapors.math._ - -import cats._ -import cats.data.Chain -import shapeless.HList - -import scala.collection.MapView -import scala.collection.immutable.BitSet - -/** - * This is the main interpreter for the [[Expr]] algebra. It recursively processes every node in the tree - * and combines the [[ExprResult]] in a symmetric fashion. - * - * The end result of interpreting an [[Expr]] is a function from [[ExprInput]] => [[ExprResult]] (which - * itself contains an [[ExprOutput]] and a captured [[P]] parameter inside of the [[ExprResult.Context]]). - * - * This interpreter calls itself recursively for every sub-expression node by calling the [[Expr.visit]] - * and passing the appropriate definition of [[Expr.Visitor]] (i.e. either `this` or some appropriately - * parameterized version of [[InterpretExprAsResultFn]]). - * - * In some cases, we cannot recursively handle [[ExprResult]]s, so we use the [[InterpretExprAsSimpleOutputFn]] - * to get a simplified output function that can be combined in a [[Semigroupal]] fashion. - */ -final class InterpretExprAsResultFn[V, P] extends Expr.Visitor[V, P, Lambda[r => ExprInput[V] => ExprResult[V, r, P]]] { - - import cats.syntax.all._ - import vapors.syntax.math._ - - override def visitAddOutputs[R : Addition](expr: Expr.AddOutputs[V, R, P]): ExprInput[V] => ExprResult[V, R, P] = { - input => - val inputResults = expr.inputExprList.map { inputExpr => - inputExpr.visit(this)(input) - } - val inputResultList = inputResults.toList - val outputValue = inputResultList.map(_.output.value).reduceLeft(_ + _) - val allEvidence = inputResultList.foldMap(_.output.evidence) - val allParams = inputResultList.map(_.param) - resultOfManySubExpr(expr, input, outputValue, allEvidence, allParams) { - ExprResult.AddOutputs(_, _, inputResultList) - } - } - - override def visitAnd[R : Conjunction : ExtractBoolean]( - expr: Expr.And[V, R, P], - ): ExprInput[V] => ExprResult[V, R, P] = { input => - val inputResults = expr.inputExprList.map { inputExpr => - inputExpr.visit(this)(input) - } - val combinedOutput = inputResults.map(_.output).reduceLeft(Conjunction[ExprOutput[R]].conjunction) - val inputResultList = inputResults.toList - val allParams = inputResultList.map(_.param) - resultOfManySubExpr(expr, input, combinedOutput.value, combinedOutput.evidence, allParams) { - ExprResult.And(_, _, inputResultList) - } - } - - override def visitCollectSomeOutput[M[_] : Foldable, U, R : Monoid]( - expr: Expr.CollectFromOutput[V, M, U, R, P], - ): ExprInput[V] => ExprResult[V, R, P] = { input => - val inputResult = expr.inputExpr.visit(this)(input) - val collectFn = expr.collectExpr.visit(InterpretExprAsResultFn()) - val (combinedResult, combinedEvidence, allParams) = inputResult.output.value.collectFoldSome { elem => - // If given a fact, use it as evidence, otherwise keep input evidence for this value of the collection - val inputEvidence = Evidence.fromAny(elem).getOrElse(inputResult.output.evidence) - val collectInput = input.withValue(elem, inputEvidence) - val collectResult = collectFn(collectInput) - // TODO: Keep all params / replay steps instead of just the ones that return true? - collectResult.output.value.map { result => - ( - result, - collectResult.output.evidence, - collectResult.param :: Nil, - ) - } - } - resultOfManySubExpr(expr, input, combinedResult, combinedEvidence, allParams) { - ExprResult.CollectFromOutput(_, _, inputResult) - } - } - - override def visitConcatOutput[M[_] : MonoidK, R]( - expr: Expr.ConcatOutput[V, M, R, P], - ): ExprInput[V] => ExprResult[V, M[R], P] = { input => - implicit val monoidMR: Monoid[M[R]] = MonoidK[M].algebra[R] - val inputResults = expr.inputExprList.map(_.visit(this)(input)) - val allParams = inputResults.map(_.param).toList - val allEvidence = inputResults.foldMap(_.output.evidence) - val outputValues = inputResults.foldMap(_.output.value) - resultOfManySubExpr(expr, input, outputValues, allEvidence, allParams) { - ExprResult.ConcatOutput(_, _, inputResults) - } - } - - override def visitConstOutput[R](expr: Expr.ConstOutput[V, R, P]): ExprInput[V] => ExprResult[V, R, P] = { input => - resultOfPureExpr(expr, input, expr.value, input.evidence) { - ExprResult.ConstOutput(_, _) - } - } - - override def visitCustomFunction[A, R]( - expr: Expr.CustomFunction[V, A, R, P], - ): ExprInput[V] => ExprResult[V, R, P] = { input => - val argResult = InterpretExprAsResultFn(expr.inputExpr)(input) - val value = expr.evaluate(argResult.output.value) - resultOfPureExpr(expr, input, value, argResult.output.evidence) { - ExprResult.CustomFunction(_, _, argResult) - } - } - - override def visitDefine[M[_] : Foldable, T]( - expr: Expr.Define[M, T, P], - ): ExprInput[V] => ExprResult[V, FactSet, P] = { input => - val definitionFn = expr.definitionExpr.visit(InterpretExprAsResultFn()) - val definitionContext = input.withValue(input.factTable) - val definitionResult = definitionFn(definitionContext) - val definedFactSet = definitionResult.output.value.foldMap { definitionValue => - FactSet(DerivedFact(expr.factType, definitionValue, definitionResult.output.evidence)) - } - val output = ExprOutput(definedFactSet, definitionResult.output.evidence) - val postParam = expr.capture.foldToParam(expr, definitionContext, output, definitionResult.param :: Nil) - ExprResult.Define(expr, ExprResult.Context(input, output, postParam), definitionResult) - } - - override def visitEmbed[R](expr: Expr.Embed[V, R, P]): ExprInput[V] => ExprResult[V, R, P] = { input => - val embeddedFn = expr.embeddedExpr.visit(InterpretExprAsResultFn()) - val embeddedInput = input.withValue(input.factTable) - val embeddedResult = embeddedFn(embeddedInput) - // The evidence of the surrounding context is not relevant to the evidence of the embedded expression - // so we ignore it and leave it up to the parent expression to combine evidence as it sees fit. - resultOfSingleSubExpr(expr, input, embeddedResult) { - ExprResult.Embed(_, _, embeddedResult) - } - } - - override def visitExistsInOutput[M[_] : Foldable, U]( - expr: Expr.ExistsInOutput[V, M, U, P], - ): ExprInput[V] => ExprResult[V, Boolean, P] = { input => - val inputResult = expr.inputExpr.visit(this)(input) - val conditionFn = expr.conditionExpr.visit(InterpretExprAsResultFn()) - val (allMatchedIndexes, allEvidence, allCondResults) = inputResult.output.value.toList.zipWithIndex.collectFold { - case (elem, idx) => - // If given a fact, use it as evidence, otherwise keep input evidence for this value of the collection - val inputEvidence = Evidence.fromAny(elem).getOrElse(inputResult.output.evidence) - val conditionInput = input.withValue(elem, inputEvidence) - val conditionResult = conditionFn(conditionInput) - val isMatch = conditionResult.output.value - val (matchedIdx, accEvidence, accResults) = { - if (isMatch) (Chain(idx), conditionResult.output.evidence, Chain(conditionResult)) - else (Chain.nil, Evidence.none, Chain.nil) - } - (matchedIdx, accEvidence, accResults) - } - val matchedIndexSet = allMatchedIndexes.iterator.to(BitSet) - val condResultList = allCondResults.toList - val allParams = condResultList.map(_.param) - // TODO: Better way to capture the param from the inputResult separate from the condResultList params? - resultOfManySubExpr(expr, input, matchedIndexSet.nonEmpty, allEvidence, inputResult.param :: allParams) { - ExprResult.ExistsInOutput(_, _, inputResult, condResultList, matchedIndexSet) - } - } - - override def visitExponentiateOutputs( - expr: Expr.ExponentiateOutputs[V, P], - ): ExprInput[V] => ExprResult[V, Double, P] = { input => - val baseResult = expr.baseExpr.visit(this)(input) - val exponentResult = expr.exponentExpr.visit(this)(input) - val base = baseResult.output.value - val exponent = exponentResult.output.value - val output = Math.pow(base, exponent) - val allEvidence = baseResult.output.evidence ++ exponentResult.output.evidence - val allParams = List(baseResult.param, exponentResult.param) - resultOfManySubExpr(expr, input, output, allEvidence, allParams) { - ExprResult.ExponentiateOutputs(_, _, baseResult, exponentResult) - } - } - - override def visitFilterOutput[M[_] : Foldable : FunctorFilter, U]( - expr: Expr.FilterOutput[V, M, U, P], - ): ExprInput[V] => ExprResult[V, M[U], P] = { input => - implicit val functorM: Functor[M] = FunctorFilter[M].functor - val inputResult = expr.inputExpr.visit(this)(input) - val condFn = expr.condExpr.visit(InterpretExprAsResultFn()) - val condResults = inputResult.output.value.map { elem => - val inputEvidence = Evidence.fromAny(elem).getOrElse(inputResult.output.evidence) - val condInput = input.withValue(elem, inputEvidence) - condFn(condInput) - } - val matchingValues = condResults.collect { - case condResult if condResult.output.value => condResult.input.value - } - val (matchingEvidence, matchingParams) = condResults.collectFoldSome { result => - Option.when(result.output.value) { - (result.output.evidence, result.param :: Nil) - } - } - val condResultList = condResults.toList - // TODO: Better way to capture the param from the inputResult separate from the condResultList params? - resultOfManySubExpr(expr, input, matchingValues, matchingEvidence, inputResult.param :: matchingParams) { - ExprResult.FilterOutput(_, _, inputResult, condResultList) - } - } - - override def visitFlatMapOutput[M[_] : Foldable : FlatMap, U, X]( - expr: Expr.FlatMapOutput[V, M, U, X, P], - ): ExprInput[V] => ExprResult[V, M[X], P] = { input => - val inputResult = expr.inputExpr.visit(this)(input) - val flatMapFn = expr.flatMapExpr.visit(InterpretExprAsResultFn()) - val allResults = inputResult.output.value.map { elem => - // If given a fact, use it as evidence, otherwise keep input evidence for this value of the collection - val inputEvidence = Evidence.fromAny(elem).getOrElse(inputResult.output.evidence) - val flatMapInput = input.withValue(elem, inputEvidence) - flatMapFn(flatMapInput) - } - val allValues = allResults.flatMap(_.output.value) - // TODO: Shouldn't I only look at the evidence of results that aren't empty? - val (allEvidence, allParams) = allResults.foldMap { elemResult => - (elemResult.output.evidence, elemResult.param :: Nil) - } - val subOps = allResults.toList - resultOfManySubExpr(expr, input, allValues, allEvidence, allParams) { - ExprResult.FlatMapOutput(_, _, inputResult, subOps) - } - } - - override def visitGroupOutput[M[_] : Foldable, U : Order, K]( - expr: Expr.GroupOutput[V, M, U, K, P], - ): ExprInput[V] => ExprResult[V, MapView[K, Seq[U]], P] = { input => - val inputResult = expr.inputExpr.visit(this)(input) - val values = inputResult.output.value - val grouped = values.toIterable.groupBy(expr.groupByLens.get).view.mapValues(_.to(LazyList)) - resultOfManySubExpr(expr, input, grouped, inputResult.output.evidence, inputResult.param :: Nil) { - ExprResult.GroupOutput(_, _, inputResult) - } - } - - override def visitMapOutput[M[_] : Foldable : Functor, U, R]( - expr: Expr.MapOutput[V, M, U, R, P], - ): ExprInput[V] => ExprResult[V, M[R], P] = { input => - val inputResult = expr.inputExpr.visit(this)(input) - val mapFn = expr.mapExpr.visit(InterpretExprAsResultFn()) - val allResults = inputResult.output.value.map { elem => - // If given a fact, use it as evidence, otherwise keep input evidence for this value of the collection - val inputEvidence = Evidence.fromAny(elem).getOrElse(inputResult.output.evidence) - val mapInput = input.withValue(elem, inputEvidence) - mapFn(mapInput) - } - val allValues = allResults.map(_.output.value) - val (allEvidence, allParams) = allResults.foldMap { elemResult => - (elemResult.output.evidence, elemResult.param :: Nil) - } - val subOps = allResults.toList - resultOfManySubExpr(expr, input, allValues, allEvidence, allParams) { - ExprResult.MapOutput(_, _, inputResult, subOps) - } - } - - override def visitMultiplyOutputs[R : Multiplication]( - expr: Expr.MultiplyOutputs[V, R, P], - ): ExprInput[V] => ExprResult[V, R, P] = { input => - val inputResults = expr.inputExprList.map { inputExpr => - inputExpr.visit(this)(input) - } - val inputResultList = inputResults.toList - val outputValue = inputResultList.map(_.output.value).reduceLeft(_ * _) - val allEvidence = inputResultList.foldMap(_.output.evidence) - val allParams = inputResultList.map(_.param) - resultOfManySubExpr(expr, input, outputValue, allEvidence, allParams) { - ExprResult.MultiplyOutputs(_, _, inputResultList) - } - } - - override def visitNegativeOutput[R : Negative]( - expr: Expr.NegativeOutput[V, R, P], - ): ExprInput[V] => ExprResult[V, R, P] = { input => - val inputResult = expr.inputExpr.visit(this)(input) - val outputValue = Negative[R].negative(inputResult.output.value) - resultOfManySubExpr(expr, input, outputValue, inputResult.output.evidence, inputResult.param :: Nil) { - ExprResult.NegativeOutput(_, _, inputResult) - } - } - - override def visitNot[R : Negation](expr: Expr.Not[V, R, P]): ExprInput[V] => ExprResult[V, R, P] = { input => - val inputResult = expr.inputExpr.visit(this)(input) - val outputValue = Negation[R].negation(inputResult.output.value) - resultOfManySubExpr(expr, input, outputValue, inputResult.output.evidence, inputResult.param :: Nil) { - ExprResult.Not(_, _, inputResult) - } - } - - override def visitOr[R : Disjunction : ExtractBoolean]( - expr: Expr.Or[V, R, P], - ): ExprInput[V] => ExprResult[V, R, P] = { input => - val inputResults = expr.inputExprList.map { inputExpr => - inputExpr.visit(this)(input) - } - val combinedOutput = inputResults.map(_.output).reduceLeft(Disjunction[ExprOutput[R]].disjunction) - val inputResultList = inputResults.toList - val allParams = inputResultList.map(_.param) - resultOfManySubExpr(expr, input, combinedOutput.value, combinedOutput.evidence, allParams) { - ExprResult.Or(_, _, inputResultList) - } - } - - override def visitOutputIsEmpty[M[_] : Foldable, R]( - expr: Expr.OutputIsEmpty[V, M, R, P], - ): ExprInput[V] => ExprResult[V, Boolean, P] = { input => - val inputResult = expr.inputExpr.visit(this)(input) - val isEmpty = inputResult.output.value.isEmpty - resultOfPureExpr(expr, input, isEmpty, inputResult.output.evidence) { - ExprResult.OutputIsEmpty(_, _, inputResult) - } - } - - override def visitOutputWithinSet[R]( - expr: Expr.OutputWithinSet[V, R, P], - ): ExprInput[V] => ExprResult[V, Boolean, P] = { input => - val inputResult = expr.inputExpr.visit(this)(input) - val isWithinSet = expr.accepted.contains(inputResult.output.value) - resultOfPureExpr(expr, input, isWithinSet, inputResult.output.evidence) { - ExprResult.OutputWithinSet(_, _, inputResult) - } - } - - override def visitOutputWithinWindow[R]( - expr: Expr.OutputWithinWindow[V, R, P], - ): ExprInput[V] => ExprResult[V, Boolean, P] = { input => - val inputResult = expr.inputExpr.visit(this)(input) - val windowResult = expr.windowExpr.visit(this)(input) - val isWithinWindow = windowResult.output.value.contains(inputResult.output.value) - resultOfPureExpr(expr, input, isWithinWindow, inputResult.output.evidence ++ windowResult.output.evidence) { - ExprResult.OutputWithinWindow(_, _, inputResult) - } - } - - override def visitFoldOutput[M[_] : Foldable, R : Monoid]( - expr: Expr.FoldOutput[V, M, R, P], - ): ExprInput[V] => ExprResult[V, R, P] = { input => - val inputResult = expr.inputExpr.visit(this)(input) - val resultValue = inputResult.output.value.fold - resultOfPureExpr(expr, input, resultValue, inputResult.output.evidence) { - ExprResult.FoldOutput(_, _, inputResult) - } - } - - override def visitReturnInput(expr: Expr.ReturnInput[V, P]): ExprInput[V] => ExprResult[V, V, P] = { input => - resultOfPureExpr(expr, input, input.value, input.evidence ++ Evidence.fromAnyOrNone(input.value)) { - ExprResult.ReturnInput(_, _) - } - } - - override def visitSelectFromOutput[S, R]( - expr: Expr.SelectFromOutput[V, S, R, P], - ): ExprInput[V] => ExprResult[V, R, P] = { input => - val inputResult = expr.inputExpr.visit(this)(input) - val selected = expr.lens.get(inputResult.output.value) - resultOfManySubExpr(expr, input, selected, inputResult.output.evidence, inputResult.param :: Nil) { - ExprResult.SelectFromOutput(_, _, inputResult) - } - } - - override def visitSortOutput[M[_], R](expr: Expr.SortOutput[V, M, R, P]): ExprInput[V] => ExprResult[V, M[R], P] = { - input => - val inputResult = expr.inputExpr.visit(this)(input) - val unsorted = inputResult.output.value - val sorted = expr.sorter(unsorted) - resultOfManySubExpr(expr, input, sorted, inputResult.output.evidence, inputResult.param :: Nil) { - ExprResult.SortOutput(_, _, inputResult) - } - } - - override def visitSubtractOutputs[R : Subtraction]( - expr: Expr.SubtractOutputs[V, R, P], - ): ExprInput[V] => ExprResult[V, R, P] = { input => - val allResults = expr.inputExprList.map(_.visit(this)(input)) - val allResultsList = allResults.toList - val addResult = allResultsList.map(_.output.value).reduceLeft(_ - _) - val allEvidence = allResultsList.foldMap(_.output.evidence) - val allParams = allResultsList.map(_.param) - resultOfManySubExpr(expr, input, addResult, allEvidence, allParams) { - ExprResult.SubtractOutputs(_, _, allResultsList) - } - } - - override def visitDivideOutputs[R : Division]( - expr: Expr.DivideOutputs[V, R, P], - ): ExprInput[V] => ExprResult[V, R, P] = { input => - val allResults = expr.inputExprList.map(_.visit(this)(input)) - val allResultsList = allResults.toList - val addResult = allResultsList.map(_.output.value).reduceLeft(_ / _) - val allEvidence = allResultsList.foldMap(_.output.evidence) - val allParams = allResultsList.map(_.param) - resultOfManySubExpr(expr, input, addResult, allEvidence, allParams) { - ExprResult.DivideOutputs(_, _, allResultsList) - } - } - - override def visitTakeFromOutput[M[_] : Traverse : TraverseFilter, R]( - expr: Expr.TakeFromOutput[V, M, R, P], - ): ExprInput[V] => ExprResult[V, M[R], P] = { input => - val inputResult = expr.inputExpr.visit(this)(input) - val values = inputResult.output.value - val takeWindow: Window[Int] = expr.take match { - case pos if pos > 0 => Window.between(0, pos) - case 0 => Window.empty - case neg if neg < 0 => - val totalSize = values.size.toInt - Window.between(totalSize + neg, totalSize) - } - // TODO: Handle evidence when the input is a set / sequence of facts - val selectedValues = values.zipWithIndex.collect { - case (elem, idx) if takeWindow.contains(idx) => elem - } - resultOfManySubExpr(expr, input, selectedValues, inputResult.output.evidence, inputResult.param :: Nil) { - ExprResult.TakeFromOutput(_, _, inputResult) - } - } - - override def visitWrapOutputSeq[R](expr: Expr.WrapOutputSeq[V, R, P]): ExprInput[V] => ExprResult[V, Seq[R], P] = { - input => - val inputResultList = expr.inputExprList.to(LazyList).map(_.visit(this)(input)) - val allEvidence = inputResultList.foldMap(_.output.evidence) - val allParams = inputResultList.foldMap(_.param :: Nil) - val outputValues = inputResultList.map(_.output.value) - resultOfManySubExpr(expr, input, outputValues, allEvidence, allParams) { - ExprResult.WrapOutputSeq(_, _, inputResultList) - } - } - - override def visitWrapOutput[L, R](expr: Expr.WrapOutput[V, L, R, P]): ExprInput[V] => ExprResult[V, R, P] = { - input => - val inputResult = expr.inputExpr.visit(this)(input) - val convertedValue = expr.converter(inputResult.output.value) - resultOfManySubExpr(expr, input, convertedValue, inputResult.output.evidence, inputResult.param :: Nil) { - ExprResult.WrapOutput(_, _, inputResult) - } - } - - override def visitWrapOutputHList[T <: HList, R]( - expr: Expr.WrapOutputHList[V, T, R, P], - ): ExprInput[V] => ExprResult[V, R, P] = { input => - val (tupleOutput, allParams) = visitHListOfScalarExprAndCombineOutput(expr.inputExprHList, input) - val value = expr.converter(tupleOutput.value) - resultOfManySubExpr(expr, input, value, tupleOutput.evidence, allParams) { - ExprResult.WrapOutputHList(_, _) - } - } - - override def visitUsingDefinitions[R](expr: Expr.UsingDefinitions[V, R, P]): ExprInput[V] => ExprResult[V, R, P] = { - input => - val definitionVisitor = new InterpretExprAsResultFn[FactTable, P] - val definitionInput = input.withValue(input.factTable) - val (declaredFacts, evidence, declaredParams) = expr.definitions.foldMap { defExpr => - val definitionFn = defExpr.visit(definitionVisitor) - val definitionResult = definitionFn(definitionInput) - (definitionResult.output.value, definitionResult.output.evidence, definitionResult.param :: Nil) - } - val subInput = input.copy(factTable = input.factTable.addAll(declaredFacts)) - val subFn = expr.subExpr.visit(this) - val subResult = subFn(subInput) - // TODO: Come up with a better way to combine the CaptureP params from expressions that have multiple - // sub expressions with different meanings. - val allParams = subResult.param :: declaredParams - val postParam = expr.capture.foldToParam(expr, input, subResult.output, allParams) - ExprResult.UsingDefinitions(expr, ExprResult.Context(input, subResult.output, postParam), subResult) - } - - override def visitWhen[R](expr: Expr.When[V, R, P]): ExprInput[V] => ExprResult[V, R, P] = { input => - val (maybeConditionResult, condParams) = { - expr.conditionBranches.foldLeft((None, Nil): (Option[(ConditionBranch[V, R, P], Evidence)], List[Eval[P]])) { - case (acc @ (Some(_), _), _) => acc - case ((None, params), cond) => - val whenResult = cond.whenExpr.visit(this)(input) - val conditionMet = whenResult.output.value - (Option.when(conditionMet)((cond, whenResult.output.evidence)), whenResult.param :: params) - } - } - val (thenExpr, condEvidence) = maybeConditionResult - .map { - case (branch, evidence) => (branch.thenExpr, evidence) - } - .getOrElse { - (expr.defaultExpr, Evidence.none) - } - val thenResult = thenExpr.visit(this)(input) - // union the evidence for the condition with the evidence for the output - val allEvidence = condEvidence.union(thenResult.output.evidence) - // TODO: Better way to organize the params than to just put the thenExpr param at the head? - val allParams = thenResult.param :: condParams - resultOfManySubExpr(expr, input, thenResult.output.value, allEvidence, allParams) { - ExprResult.When(_, _, maybeConditionResult.map(_._1), thenResult) - } - } - - override def visitWithFactsOfType[T, R]( - expr: Expr.WithFactsOfType[T, R, P], - ): ExprInput[V] => ExprResult[V, R, P] = { input => - val inputFactTable = input.withValue(input.factTable) - val withMatchingFactsFn = expr.subExpr.visit(InterpretExprAsResultFn()) - val matchingFacts = input.factTable.getSortedSeq(expr.factTypeSet) - // facts will always be added as their own evidence when used, so we do not need to add them to the evidence here - val subInput = input.withValue[Seq[TypedFact[T]]](matchingFacts) - val subResult = withMatchingFactsFn(subInput) - val postParam = expr.capture.foldToParam(expr, inputFactTable, subResult.output, subResult.param :: Nil) - ExprResult.WithFactsOfType( - expr, - ExprResult.Context(input, subResult.output, postParam), - subResult, - ) - } - - override def visitZipOutput[M[_] : Align : FunctorFilter, L <: HList, R]( - expr: Expr.ZipOutput[V, M, L, R, P], - ): ExprInput[V] => ExprResult[V, M[R], P] = { input => - val (tupleOutput, allParams) = - visitHListOfHigherKindedExprAndZipCombinedToShortestOutput(expr.inputExprHList, input) - val outputValues = FunctorFilter[M].functor.map(tupleOutput.value)(expr.converter) - resultOfManySubExpr(expr, input, outputValues, tupleOutput.evidence, allParams) { - ExprResult.ZipOutput(_, _) - } - } - - @inline private def resultOfPureExpr[R]( - expr: Expr[V, R, P], - input: ExprInput[V], - result: R, - evidence: Evidence, - )( - buildPostOp: (expr.type, ExprResult.Context[V, R, P]) => ExprResult[V, R, P], - ): ExprResult[V, R, P] = { - resultOfManySubExpr(expr, input, result, evidence, Nil)(buildPostOp) - } - - @inline private def resultOfManySubExpr[R]( - expr: Expr[V, R, P], - input: ExprInput[V], - result: R, - evidence: Evidence, - capturedParams: List[Eval[P]], - )( - buildResult: (expr.type, ExprResult.Context[V, R, P]) => ExprResult[V, R, P], - ): ExprResult[V, R, P] = { - val output = ExprOutput(result, evidence) - val param = expr.capture.foldToParam(expr, input, output, capturedParams) - buildResult(expr, ExprResult.Context(input, output, param)) - } - - @inline private def resultOfSingleSubExpr[R]( - expr: Expr[V, R, P], - input: ExprInput[V], - subResult: ExprResult[_, R, P], - )( - buildPostOp: (expr.type, ExprResult.Context[V, R, P]) => ExprResult[V, R, P], - ): ExprResult[V, R, P] = { - resultOfManySubExpr(expr, input, subResult.output.value, subResult.output.evidence, subResult.param :: Nil) { - buildPostOp - } - } - - /** - * This type and functor are used for HList operations that require the [[InterpretExprAsSimpleOutputFn]] interpreter - */ - private object SimpleOutputFnFunctorBuilder extends SimpleOutputFnFunctorBuilder[V, P] - import SimpleOutputFnFunctorBuilder._ - - /** - * Uses the [[SimpleOutputFunctor]] to visit and map over each expression of the given `exprHList` and return - * a simplified output of [[InterpretExprAsSimpleOutputFn.GenSimpleOutput]] combined as a Monoid. - * - * The evidence sets are unioned together, the params are combined in a list, and the values are mapped into - * an [[HList]] of the given type. - * - * @see [[NonEmptyExprHList.visitProduct]] for more details and examples - */ - private def visitHListOfScalarExprAndCombineOutput[L <: HList]( - exprHList: NonEmptyExprHList[V, Id, L, P], - input: ExprInput[V], - ): SimpleOutput[L] = { - implicit val id: Functor[Id] with Semigroupal[Id] = cats.catsInstancesForId - exprHList.visitProduct(new InterpretExprAsSimpleOutputFn).apply(input) - } - - /** - * Similar to [[visitHListOfScalarExprAndCombineOutput]] except that it handles an HList of expressions that return - * higher-kinded functor wrapped values by aligning and zipping the values as HList values of each element - * inside the wrapper. - * - * In other words, it only returns a wrapped HList for each element of the shortest-length output value. - * - * @see [[NonEmptyExprHList.visitZippedToShortest]] for more details and examples. - */ - private def visitHListOfHigherKindedExprAndZipCombinedToShortestOutput[M[_] : Align : FunctorFilter, L <: HList]( - exprHList: NonEmptyExprHList[V, M, L, P], - input: ExprInput[V], - ): SimpleOutput[M[L]] = { - exprHList.visitZippedToShortest(new InterpretExprAsSimpleOutputFn).apply(input) - } -} - -object InterpretExprAsResultFn { - - final def apply[V, P](): InterpretExprAsResultFn[V, P] = new InterpretExprAsResultFn - - final def apply[V, R, P](expr: Expr[V, R, P])(input: ExprInput[V]): ExprResult[V, R, P] = { - expr.visit(InterpretExprAsResultFn())(input) - } -} diff --git a/core/src/main/scala/vapors/interpreter/InterpretExprAsSimpleOutputFn.scala b/core/src/main/scala/vapors/interpreter/InterpretExprAsSimpleOutputFn.scala deleted file mode 100644 index 6b70f2133..000000000 --- a/core/src/main/scala/vapors/interpreter/InterpretExprAsSimpleOutputFn.scala +++ /dev/null @@ -1,57 +0,0 @@ -package com.rallyhealth - -package vapors.interpreter - -import vapors.algebra.Expr -import vapors.interpreter.InterpretExprAsSimpleOutputFn.GenSimpleOutput - -import cats.{Eval, Functor, Semigroupal} - -/** - * Interprets an [[Expr]] as a function from [[ExprInput]] to a tuple of [[ExprOutput]] and a list of captured params. - * - * This is necessary to define a [[Functor]] and [[Semigroupal]] for an expression, so that we can recursively map - * over the whole [[com.rallyhealth.vapors.algebra.NonEmptyExprHList]] to get an [[shapeless.HList]] result. - * - * Since the [[InterpretExprAsResultFn]] return type is dependent on the input [[Expr]], it is impossible to define - * these instances. By simplifying to the more generic return type of [[GenSimpleOutput]] we are able to leverage - * the standard function, tuple, and list instances from cats over the well defined type constructor of [[ExprOutput]]. - * - * @see [[GenSimpleOutput]] for the interpreted function return type. - * @see [[InterpretExprAsResultFn]] for the implementation details. - */ -class InterpretExprAsSimpleOutputFn[V, P] extends VisitGenericExprWithProxyFn[V, P, GenSimpleOutput[*, P]] { - - override protected def visitGeneric[U, R]( - expr: Expr[U, R, P], - input: ExprInput[U], - ): GenSimpleOutput[R, P] = { - val result = InterpretExprAsResultFn(expr)(input) - // strip the output and captured param from the full ExprResult - (result.output, result.param :: Nil) - } -} - -object InterpretExprAsSimpleOutputFn { - type GenSimpleOutput[R, P] = (ExprOutput[R], List[Eval[P]]) - - class SimpleOutputFnFunctorBuilder[V, P] { - type SimpleOutput[R] = GenSimpleOutput[R, P] - type SimpleOutputFn[R] = ExprInput[V] => SimpleOutput[R] - - implicit object SimpleOutputFunctor extends Functor[SimpleOutputFn] with Semigroupal[SimpleOutputFn] { - override def map[A, B](fa: SimpleOutputFn[A])(f: A => B): SimpleOutputFn[B] = fa.andThen { - case (o, params) => (o.copy(value = f(o.value)), params) - } - - override def product[A, B]( - fa: SimpleOutputFn[A], - fb: SimpleOutputFn[B], - ): SimpleOutputFn[(A, B)] = { input => - val (a, aParams) = fa(input) - val (b, bParams) = fb(input) - (ExprOutput((a.value, b.value), a.evidence & b.evidence), aParams ::: bParams) - } - } - } -} diff --git a/core/src/main/scala/vapors/interpreter/VisitGenericExprWithProxyFn.scala b/core/src/main/scala/vapors/interpreter/VisitGenericExprWithProxyFn.scala deleted file mode 100644 index c73955549..000000000 --- a/core/src/main/scala/vapors/interpreter/VisitGenericExprWithProxyFn.scala +++ /dev/null @@ -1,147 +0,0 @@ -package com.rallyhealth - -package vapors.interpreter - -import vapors.algebra.Expr -import vapors.algebra.Expr.Visitor -import vapors.data.{ExtractBoolean, FactSet} -import vapors.logic.{Conjunction, Disjunction, Negation} -import vapors.math._ - -import cats._ -import cats.kernel.Monoid -import shapeless.HList - -import scala.collection.MapView - -/** - * A function interpreter from [[ExprInput]] to any output type constructor that passes all expressions nodes into - * a single common [[visitGeneric]] function. - * - * This class makes it easier to define common behavior when interpreting expressions as functions when you do not - * need to take into account any specific method signatures or expression node types. It is useful for defining - * behavior that only relies on the common methods and values of [[Expr]] and [[ExprInput]]. - */ -abstract class VisitGenericExprWithProxyFn[V, P, G[_]] extends Visitor[V, P, Lambda[r => ExprInput[V] => G[r]]] { - - protected def visitGeneric[U, R]( - expr: Expr[U, R, P], - input: ExprInput[U], - ): G[R] - - override def visitAddOutputs[R : Addition](expr: Expr.AddOutputs[V, R, P]): ExprInput[V] => G[R] = - visitGeneric(expr, _) - - override def visitAnd[R : Conjunction : ExtractBoolean](expr: Expr.And[V, R, P]): ExprInput[V] => G[R] = - visitGeneric(expr, _) - - override def visitCollectSomeOutput[M[_] : Foldable, U, R : Monoid]( - expr: Expr.CollectFromOutput[V, M, U, R, P], - ): ExprInput[V] => G[R] = visitGeneric(expr, _) - - override def visitConcatOutput[M[_] : MonoidK, R](expr: Expr.ConcatOutput[V, M, R, P]): ExprInput[V] => G[M[R]] = - visitGeneric(expr, _) - - override def visitConstOutput[R](expr: Expr.ConstOutput[V, R, P]): ExprInput[V] => G[R] = - visitGeneric(expr, _) - - override def visitCustomFunction[A, R](expr: Expr.CustomFunction[V, A, R, P]): ExprInput[V] => G[R] = - visitGeneric(expr, _) - - override def visitDefine[M[_] : Foldable, T](expr: Expr.Define[M, T, P]): ExprInput[V] => G[FactSet] = { input => - visitGeneric(expr, input.withValue(input.factTable)) - } - - override def visitDivideOutputs[R : Division](expr: Expr.DivideOutputs[V, R, P]): ExprInput[V] => G[R] = - visitGeneric(expr, _) - - override def visitEmbed[R](expr: Expr.Embed[V, R, P]): ExprInput[V] => G[R] = - visitGeneric(expr, _) - - override def visitExistsInOutput[M[_] : Foldable, U]( - expr: Expr.ExistsInOutput[V, M, U, P], - ): ExprInput[V] => G[Boolean] = visitGeneric(expr, _) - - override def visitExponentiateOutputs(expr: Expr.ExponentiateOutputs[V, P]): ExprInput[V] => G[Double] = - visitGeneric(expr, _) - - override def visitFilterOutput[M[_] : Foldable : FunctorFilter, R]( - expr: Expr.FilterOutput[V, M, R, P], - ): ExprInput[V] => G[M[R]] = visitGeneric(expr, _) - - override def visitFlatMapOutput[M[_] : Foldable : FlatMap, U, X]( - expr: Expr.FlatMapOutput[V, M, U, X, P], - ): ExprInput[V] => G[M[X]] = visitGeneric(expr, _) - - override def visitGroupOutput[M[_] : Foldable, U : Order, K]( - expr: Expr.GroupOutput[V, M, U, K, P], - ): ExprInput[V] => G[MapView[K, Seq[U]]] = visitGeneric(expr, _) - - override def visitMapOutput[M[_] : Foldable : Functor, U, R]( - expr: Expr.MapOutput[V, M, U, R, P], - ): ExprInput[V] => G[M[R]] = visitGeneric(expr, _) - - override def visitMultiplyOutputs[R : Multiplication](expr: Expr.MultiplyOutputs[V, R, P]): ExprInput[V] => G[R] = - visitGeneric(expr, _) - - override def visitNegativeOutput[R : Negative](expr: Expr.NegativeOutput[V, R, P]): ExprInput[V] => G[R] = - visitGeneric(expr, _) - - override def visitNot[R : Negation](expr: Expr.Not[V, R, P]): ExprInput[V] => G[R] = - visitGeneric(expr, _) - - override def visitOr[R : Disjunction : ExtractBoolean](expr: Expr.Or[V, R, P]): ExprInput[V] => G[R] = - visitGeneric(expr, _) - - override def visitOutputIsEmpty[M[_] : Foldable, R]( - expr: Expr.OutputIsEmpty[V, M, R, P], - ): ExprInput[V] => G[Boolean] = visitGeneric(expr, _) - - override def visitOutputWithinSet[R](expr: Expr.OutputWithinSet[V, R, P]): ExprInput[V] => G[Boolean] = - visitGeneric(expr, _) - - override def visitOutputWithinWindow[R](expr: Expr.OutputWithinWindow[V, R, P]): ExprInput[V] => G[Boolean] = - visitGeneric(expr, _) - - override def visitFoldOutput[M[_] : Foldable, R : Monoid](expr: Expr.FoldOutput[V, M, R, P]): ExprInput[V] => G[R] = - visitGeneric(expr, _) - - override def visitReturnInput(expr: Expr.ReturnInput[V, P]): ExprInput[V] => G[V] = - visitGeneric(expr, _) - - override def visitSelectFromOutput[S, R](expr: Expr.SelectFromOutput[V, S, R, P]): ExprInput[V] => G[R] = - visitGeneric(expr, _) - - override def visitSortOutput[M[_], R](expr: Expr.SortOutput[V, M, R, P]): ExprInput[V] => G[M[R]] = - visitGeneric(expr, _) - - override def visitSubtractOutputs[R : Subtraction](expr: Expr.SubtractOutputs[V, R, P]): ExprInput[V] => G[R] = - visitGeneric(expr, _) - - override def visitTakeFromOutput[M[_] : Traverse : TraverseFilter, R]( - expr: Expr.TakeFromOutput[V, M, R, P], - ): ExprInput[V] => G[M[R]] = visitGeneric(expr, _) - - override def visitWrapOutputSeq[R](expr: Expr.WrapOutputSeq[V, R, P]): ExprInput[V] => G[Seq[R]] = - visitGeneric(expr, _) - - override def visitUsingDefinitions[R](expr: Expr.UsingDefinitions[V, R, P]): ExprInput[V] => G[R] = - visitGeneric(expr, _) - - override def visitWhen[R](expr: Expr.When[V, R, P]): ExprInput[V] => G[R] = - visitGeneric(expr, _) - - override def visitWithFactsOfType[T, R](expr: Expr.WithFactsOfType[T, R, P]): ExprInput[V] => G[R] = { input => - visitGeneric(expr, input.withValue(input.factTable)) - } - - override def visitWrapOutput[L, R](expr: Expr.WrapOutput[V, L, R, P]): ExprInput[V] => G[R] = - visitGeneric(expr, _) - - override def visitWrapOutputHList[T <: HList, R](expr: Expr.WrapOutputHList[V, T, R, P]): ExprInput[V] => G[R] = - visitGeneric(expr, _) - - override def visitZipOutput[M[_] : Align : FunctorFilter, L <: HList, R]( - expr: Expr.ZipOutput[V, M, L, R, P], - ): ExprInput[V] => G[M[R]] = visitGeneric(expr, _) -} diff --git a/core/src/main/scala/vapors/lens/NamedLens.scala b/core/src/main/scala/vapors/lens/NamedLens.scala deleted file mode 100644 index 316f45342..000000000 --- a/core/src/main/scala/vapors/lens/NamedLens.scala +++ /dev/null @@ -1,266 +0,0 @@ -package com.rallyhealth.vapors.lens - -import cats.arrow.Compose -import cats.data.NonEmptySet -import cats.kernel.Semigroup -import com.rallyhealth.vapors.lens.NamedLens.AsIterableBuilder -import com.rallyhealth.vapors.v1.lens.IndexedSyntax -import shapeless.ops.hlist -import shapeless.{Generic, HList} - -import scala.collection.{Factory, MapView, View} - -/** - * @see [[com.rallyhealth.vapors.v1.lens.VariantLens]] - */ -object NamedLens extends NamedLensLowPriorityImplicits { - - /** - * The type of lens that returns the value it is given. - */ - type Id[A] = NamedLens[A, A] - - /** - * A singleton implementation of the [[identity]] lens. - */ - val Id: NamedLens[Any, Any] = NamedLens(DataPath.empty, identity[Any]) - - /** - * The only definition of the [[Id]] lens. - */ - def id[A]: NamedLens[A, A] = Id.asInstanceOf[Id[A]] - - /** - * A function for building a lens from the [[Id]] lens. - */ - type Fn[A, B] = NamedLens.Id[A] => NamedLens[A, B] - - implicit object ComposeInstance extends Compose[NamedLens] { - override def compose[A, B, C]( - f: NamedLens[B, C], - g: NamedLens[A, B], - ): NamedLens[A, C] = - g.andThen(f) - } - - /** - * A fancy trick using macros that allows you to access the surrounding context of a method - * invocation. By wrapping the call with this class, we can access the given [[lens]] to - * apply the appropriate transformation. - * - * @param lens the lens to add the `.select` method to. - */ - implicit final class Selector[A, B](val lens: NamedLens[A, B]) extends AnyVal { - - /** - * Create a [[NamedLens]] that selects a field based on a given function and applies the appropriate - * [[com.rallyhealth.vapors.v1.lens.DataPath.atField]] operator. - * - * @note this uses a macro and only works for `val`s or `def`s with no arguments. - * - * @param getter a function that selects a value of type [[C]] from the return type [[B]] of the current lens - */ - def select[C](getter: B => C): NamedLens[A, C] = macro NamedLensMacros.selectImpl[A, B, C] - } - - /** - * If the lens returns a [[IterableOnce]] of 2-tuples, then allow calling operations defined by [[AsMapBuilder]] - */ - implicit def asMap[A, B, C[x] <: IterableOnce[x], K, E]( - lens: NamedLens[A, B], - )(implicit - ev: B <:< C[(K, E)], - ): AsMapBuilder[A, C, K, E] = - new AsMapBuilder[A, C, K, E](lens.as[C[(K, E)]]) - - final class AsMapBuilder[A, C[x] <: IterableOnce[x], K, V](private val lens: NamedLens[A, C[(K, V)]]) extends AnyVal { - - /** - * Returns the right-side of all the 2-tuples as an [[Iterable]] of values. - */ - def values: NamedLens[A, Iterable[V]] = lens.copy( - get = lens.get.andThen(_.iterator.to(View).map(_._2)), - ) - - /** - * @see [[AsIterableBuilder.to]] but the values are 2-tuples - */ - def to[B](factory: Factory[(K, V), B]): NamedLens[A, B] = - lens.copy(get = lens.get.andThen(factory.fromSpecific(_))) - - /** - * Group the 2-tuples by the left side and create a [[Map]]. - * - * If there are duplicate keys, then the last entry in the sequence wins the spot and the others are dropped. - */ - def toMap: NamedLens[A, Map[K, V]] = lens.copy( - get = lens.get.andThen(Map.from(_)), - ) - - /** - * Same as [[toMap]], but creates a lazy [[MapView]] instead. - */ - def toMapView: NamedLens[A, MapView[K, V]] = lens.copy( - get = lens.get.andThen(Map.from(_).view), - ) - } - - // TODO: Make this more generally useful and convert back to a NamedLens implicitly - final class AsIterableBuilder[A, C[x] <: IterableOnce[x], E](private val lens: NamedLens[A, C[E]]) extends AnyVal { - - /** - * Use the given Scala collection companion object to convert this [[IterableOnce]] into the desired output. - * - * TODO: Should there be any path information for conversion? - * List => Map seems important information as values with duplicate keys could be dropped - */ - def to[B](factory: Factory[E, B]): NamedLens[A, B] = - lens.copy(get = lens.get.andThen(factory.fromSpecific(_))) - - /** - * Get the first element of this [[IterableOnce]], if it isn't empty. - */ - def headOption: NamedLens[A, Option[E]] = - lens.copy( - path = lens.path.atHead, - get = lens.get.andThen(b => View.from[E](b).headOption), - ) - } - - implicit final class AsHListBuilder[A, L <: HList](private val lens: NamedLens[A, L]) extends AnyVal { - - /** - * Convert the resulting [[HList]] into a tuple. - */ - def tupled[T](implicit tupler: hlist.Tupler.Aux[L, T]): NamedLens[A, T] = - lens.copy( - path = lens.path, - get = lens.get.andThen(tupler.apply), - ) - } -} - -sealed trait NamedLensLowPriorityImplicits { - - /** - * Wrap the result of this lens with an [[AsIterableBuilder]] for helper operations on [[IterableOnce]] types. - */ - implicit def asIterable[A, B, C[x] <: IterableOnce[x], E]( - lens: NamedLens[A, B], - )(implicit - ev: B <:< C[E], - ): AsIterableBuilder[A, C, E] = - new AsIterableBuilder(lens.as[C[E]]) -} - -/** - * @see [[com.rallyhealth.vapors.v1.lens.VariantLens]] for documentation, except this lens is invariant. - */ -final case class NamedLens[A, B]( - path: DataPath, - get: A => B, -) { outer => - - /** - * Composes another lens to run on the output of this lens. - * - * @note Because extension methods don't retain type information well, - * this is still useful despite the [[Compose]] instance. - */ - def andThen[C](lens: NamedLens[B, C]): NamedLens[A, C] = { - copy( - path = this.path ++ lens.path, - get = get.andThen(lens.get), - ) - } - - // Helpful for building a tuple with the lens itself. Identical to identity, but you don't have to specify the type. - def self: NamedLens[A, B] = this - - /** - * Access a field from the object returned by this lens. - * - * Typically, you would not call this directly, but instead by calling the [[NamedLens.Selector.select]] - * extension method on this lens. The `.select` method uses macros to make sure that the `name` param is - * set properly. - * - * @param name the name of the field - * @param getter a function to extract the value of this field from the object - * TODO: Use HList to make this more safe, instead of relying on the macro to use this properly. - */ - def field[C]( - name: String, - getter: B => C, - ): NamedLens[A, C] = { - copy( - path = path.atField(name), - get = this.get.andThen(getter), - ) - } - - /** - * Used for accessing the value for a given key in this object. - * - * This requires that there is an [[Indexed]] definition for how to access a value from the - * indexable object returned by the current lens. - * - * @param key either a value of the appropriate key type OR a [[shapeless.Nat]] for accessing a - * specifically typed value from a tuple or [[HList]] - */ - def at[K : ValidDataPathKey, V]( - key: K, - )(implicit - CI: Indexed[B, K, V], - ): NamedLens[A, V] = { - copy( - path = path.atKey(key), - get = get.andThen(b => CI.get(b)(key)), - ) - } - - /** - * Filters the given set of keys from this indexable object. - * - * @note Unfortunately this does not work for [[HList]] the same way that [[at]] does. - * - * TODO: It may be possible to make this work for tuples / HList with varargs instead of [[NonEmptySet]]. - */ - def filterKeys[K : ValidDataPathKey, V : Semigroup]( - keys: NonEmptySet[K], - )(implicit - CI: Indexed[B, K, V], - ): NamedLens[A, V] = { - import IndexedSyntax._ - copy( - path = path.filterKeys(keys), - get = get.andThen(_.filterKeys(keys)), - ) - } - - /** - * Upcasts the lens. - * - * @note this would not be necessary if [[NamedLens]] used appropriate variance, however, variance for lenses - * introduces a lot of issues. - */ - def as[V](implicit ev: B <:< V): NamedLens[A, V] = - this.copy(get = this.get.andThen(ev)) - - /** - * Converts the result into an [[HList]] if possible. - * - * Uses recursive implicit resolution at compile-time to derive the appropriate heterogenious return type. - * - * @see [[Generic]] - */ - def asHList[L <: HList](implicit gen: Generic.Aux[B, L]): NamedLens[A, L] = copy(get = this.get.andThen(gen.to)) - - // TODO: Are these runtime down casts needed? Why not just use .asInstanceOf (with a safe wrapper) - - def asIterable[V](implicit ev: B <:< IterableOnce[V]): NamedLens.AsIterableBuilder[A, IterableOnce, V] = - new NamedLens.AsIterableBuilder(this.copy(get = this.get.andThen(ev))) - - def asMap[K, V](implicit ev: B <:< IterableOnce[(K, V)]): NamedLens.AsMapBuilder[A, IterableOnce, K, V] = - new NamedLens.AsMapBuilder(this.copy(get = this.get.andThen(ev))) - -} diff --git a/core/src/main/scala/vapors/lens/NamedLensMacros.scala b/core/src/main/scala/vapors/lens/NamedLensMacros.scala deleted file mode 100644 index 257b3f726..000000000 --- a/core/src/main/scala/vapors/lens/NamedLensMacros.scala +++ /dev/null @@ -1,53 +0,0 @@ -package com.rallyhealth - -package vapors.lens - -import vapors.v1.lens.JavaBeanCompat - -import scala.annotation.tailrec -import scala.reflect.macros.blackbox - -object NamedLensMacros { - - def selectImpl[A : c.WeakTypeTag, B : c.WeakTypeTag, C : c.WeakTypeTag]( - c: blackbox.Context, - )( - getter: c.Expr[B => C], - ): c.Expr[NamedLens[A, C]] = { - import c.universe._ - val selectChain = getter.tree match { - case Function(_, rhs) => rhs - } - val fieldNames = selectTermNameList(c)(selectChain).map(JavaBeanCompat.unbeanify) - val fieldNameLiterals = c.Expr[List[String]](q"$fieldNames") - val dataPathToAppend = reify { - DataPath(fieldNameLiterals.splice.map(DataPath.Field)) - } - val lensToAppend = reify { - NamedLens[B, C](dataPathToAppend.splice, getter.splice) - } - reify { - c.prefix.splice.asInstanceOf[NamedLens.Selector[A, B]].lens.andThen(lensToAppend.splice) - } - } - - def selectTermNameList(c: blackbox.Context)(tree: c.mirror.universe.Tree): List[String] = { - import c.universe._ - @tailrec def loop( - remaining: Tree, - fields: List[String], - ): List[String] = remaining match { - // once we have figured out the target of all of the select operations, - // ignore the name (probably anonymous anyway) and return the field names - case Ident(_) => fields - // strip parameterless method application for Java compat - case Apply(sub: Select, Nil) => loop(sub, fields) - // recursive case: selects the term from the result of the given expression - case Select(init, TermName(last)) => loop(init, last :: fields) - // if the expression does more than just select or apply zero parameter methods, then stop - case _ => c.abort(remaining.pos, "Can only extract term names from a chain of vals or parameterless methods") - } - loop(tree, Nil) - } - -} diff --git a/core/src/main/scala/vapors/lens/package.scala b/core/src/main/scala/vapors/lens/package.scala deleted file mode 100644 index 3780237eb..000000000 --- a/core/src/main/scala/vapors/lens/package.scala +++ /dev/null @@ -1,15 +0,0 @@ -package com.rallyhealth - -package vapors - -package object lens { - - type DataPath = v1.lens.DataPath - final val DataPath = v1.lens.DataPath - - type Indexed[C, K, V] = v1.lens.Indexed[C, K, V] - final val Indexed = v1.lens.Indexed - - type ValidDataPathKey[K] = v1.lens.ValidDataPathKey[K] - final val ValidDataPathKey = v1.lens.ValidDataPathKey -} diff --git a/core/src/main/scala/vapors/logic/Conjunction.scala b/core/src/main/scala/vapors/logic/Conjunction.scala deleted file mode 100644 index 6aeb1a8f7..000000000 --- a/core/src/main/scala/vapors/logic/Conjunction.scala +++ /dev/null @@ -1,47 +0,0 @@ -package com.rallyhealth - -package vapors.logic - -import vapors.interpreter.InterpretExprAsResultFn - -import cats.{Invariant, Semigroupal} - -/** - * Defines logical conjunction (aka AND) for a specific type. - * - * @see for more details on how this works, check out - * [[InterpretExprAsResultFn.Output.conjunction]] - */ -trait Conjunction[A] { - - def conjunction( - lhs: A, - rhs: A, - ): A -} - -object Conjunction { - - @inline final def apply[A](implicit A: Conjunction[A]): Conjunction[A] = A - - implicit final object BooleanConjunction extends Conjunction[Boolean] { - override def conjunction( - lhs: Boolean, - rhs: Boolean, - ): Boolean = lhs && rhs - } - - implicit final object FunctorInstances extends Invariant[Conjunction] with Semigroupal[Conjunction] { - - override def product[A, B]( - fa: Conjunction[A], - fb: Conjunction[B], - ): Conjunction[(A, B)] = { (lhs: (A, B), rhs: (A, B)) => - (fa.conjunction(lhs._1, rhs._1), fb.conjunction(lhs._2, rhs._2)) - } - - override def imap[A, B](fa: Conjunction[A])(f: A => B)(g: B => A): Conjunction[B] = { (lhs: B, rhs: B) => - f(fa.conjunction(g(lhs), g(rhs))) - } - } -} diff --git a/core/src/main/scala/vapors/logic/Disjunction.scala b/core/src/main/scala/vapors/logic/Disjunction.scala deleted file mode 100644 index 46ae12353..000000000 --- a/core/src/main/scala/vapors/logic/Disjunction.scala +++ /dev/null @@ -1,47 +0,0 @@ -package com.rallyhealth - -package vapors.logic - -import vapors.interpreter.InterpretExprAsResultFn - -import cats.{Invariant, Semigroupal} - -/** - * Defines logical disjunction (aka OR) for a specific type. - * - * @see for more details on how this works, check out - * [[InterpretExprAsResultFn.Output.disjunction]] - */ -trait Disjunction[A] { - - def disjunction( - lhs: A, - rhs: A, - ): A -} - -object Disjunction { - - @inline final def apply[A](implicit A: Disjunction[A]): Disjunction[A] = A - - implicit final object BooleanDisjunction extends Disjunction[Boolean] { - override def disjunction( - lhs: Boolean, - rhs: Boolean, - ): Boolean = lhs || rhs - } - - implicit final object FunctorInstances extends Invariant[Disjunction] with Semigroupal[Disjunction] { - - override def product[A, B]( - fa: Disjunction[A], - fb: Disjunction[B], - ): Disjunction[(A, B)] = { (lhs: (A, B), rhs: (A, B)) => - (fa.disjunction(lhs._1, lhs._1), fb.disjunction(lhs._2, rhs._2)) - } - - override def imap[A, B](fa: Disjunction[A])(f: A => B)(g: B => A): Disjunction[B] = { (lhs: B, rhs: B) => - f(fa.disjunction(g(lhs), g(rhs))) - } - } -} diff --git a/core/src/main/scala/vapors/logic/Negation.scala b/core/src/main/scala/vapors/logic/Negation.scala deleted file mode 100644 index f5c47d075..000000000 --- a/core/src/main/scala/vapors/logic/Negation.scala +++ /dev/null @@ -1,38 +0,0 @@ -package com.rallyhealth - -package vapors.logic - -import cats.{Invariant, Semigroupal} - -/** - * Defines logical negaction (aka NOT) for a specific type. - * - * @see for more details on how this works, check out - * [[InterpretExprAsResultFn.Output.negation]] - */ -trait Negation[A] { - def negation(value: A): A -} - -object Negation { - - @inline final def apply[A](implicit A: Negation[A]): Negation[A] = A - - implicit final object BooleanNegation extends Negation[Boolean] { - override def negation(value: Boolean): Boolean = !value - } - - implicit final object InvariantInstances extends Invariant[Negation] with Semigroupal[Negation] { - - override def product[A, B]( - fa: Negation[A], - fb: Negation[B], - ): Negation[(A, B)] = { value => - (fa.negation(value._1), fb.negation(value._2)) - } - - override def imap[A, B](fa: Negation[A])(f: A => B)(g: B => A): Negation[B] = { value => - f(fa.negation(g(value))) - } - } -} diff --git a/core/src/main/scala/vapors/math/Addition.scala b/core/src/main/scala/vapors/math/Addition.scala deleted file mode 100644 index be71a6ece..000000000 --- a/core/src/main/scala/vapors/math/Addition.scala +++ /dev/null @@ -1,19 +0,0 @@ -package com.rallyhealth - -package vapors.math - -/** - * Defines addition of two values for a specific type. - */ -trait Addition[A] { - - def add( - lhs: A, - rhs: A, - ): A -} - -object Addition extends NumericalImplicits { - - def apply[A](implicit A: Addition[A]): Addition[A] = A -} diff --git a/core/src/main/scala/vapors/math/Division.scala b/core/src/main/scala/vapors/math/Division.scala deleted file mode 100644 index 7fb99c599..000000000 --- a/core/src/main/scala/vapors/math/Division.scala +++ /dev/null @@ -1,20 +0,0 @@ -package com.rallyhealth - -package vapors.math - -/** - * Defines division for a specific type. - * - * @note order of arguments matter, as division is not commutative. - */ -trait Division[A] { - - def quot( - dividend: A, - divisor: A, - ): A // quotient = dividend/divisor -} - -object Division extends NumericalImplicits { - def apply[A](implicit A: Division[A]): Division[A] = A -} diff --git a/core/src/main/scala/vapors/math/Multiplication.scala b/core/src/main/scala/vapors/math/Multiplication.scala deleted file mode 100644 index d8639067c..000000000 --- a/core/src/main/scala/vapors/math/Multiplication.scala +++ /dev/null @@ -1,18 +0,0 @@ -package com.rallyhealth - -package vapors.math - -/** - * Defines multiplication for a specific type. - */ -trait Multiplication[A] { - - def multiply( - lhs: A, - rhs: A, - ): A -} - -object Multiplication extends NumericalImplicits { - def apply[A](implicit A: Multiplication[A]): Multiplication[A] = A -} diff --git a/core/src/main/scala/vapors/math/Negative.scala b/core/src/main/scala/vapors/math/Negative.scala deleted file mode 100644 index 45c023069..000000000 --- a/core/src/main/scala/vapors/math/Negative.scala +++ /dev/null @@ -1,19 +0,0 @@ -package com.rallyhealth - -package vapors.math - -/** - * Defines inverting a single value to it's negative. - * - * @note not to be confused with [[com.rallyhealth.vapors.core.logic.Negation]], which is the formal logic - * definition of negation of the conclusion. - */ -trait Negative[A] { - - def negative(value: A): A -} - -object Negative extends NumericalImplicits { - - def apply[A](implicit A: Negative[A]): Negative[A] = A -} diff --git a/core/src/main/scala/vapors/math/NumericalImplicits.scala b/core/src/main/scala/vapors/math/NumericalImplicits.scala deleted file mode 100644 index 36ce9479e..000000000 --- a/core/src/main/scala/vapors/math/NumericalImplicits.scala +++ /dev/null @@ -1,61 +0,0 @@ -package com.rallyhealth - -package vapors.math - -/** - * Defines default implementations for [[Numeric]]-based arithmetic operators. - */ -private[math] trait NumericalImplicits { - - implicit def integral[A : Integral]: FromIntegral[A] = new FromIntegral[A] - implicit def fractional[A : Fractional]: FromFractional[A] = new FromFractional[A] -} - -/** - * Defines all arithmetic type-classes from Scala's [[Numeric]] definition. - */ -abstract class FromNumeric[A : Numeric] - extends Addition[A] - with Subtraction[A] - with Negative[A] - with Multiplication[A] { - import Numeric.Implicits._ - - override def add( - lhs: A, - rhs: A, - ): A = lhs + rhs - - override def subtract( - lhs: A, - rhs: A, - ): A = lhs - rhs - - override def negative(value: A): A = -value - - override def multiply( - lhs: A, - rhs: A, - ): A = lhs * rhs -} - -/** - * Defines all arithmetic type-classes from Scala's [[Numeric]] definition. - */ -final class FromIntegral[A : Integral] extends FromNumeric[A] with Division[A] { - import Integral.Implicits._ - - override def quot( - dividend: A, - divisor: A, - ): A = dividend / divisor -} - -final class FromFractional[A : Fractional] extends FromNumeric[A] with Division[A] { - import Fractional.Implicits._ - - override def quot( - dividend: A, - divisor: A, - ): A = dividend / divisor -} diff --git a/core/src/main/scala/vapors/math/Subtraction.scala b/core/src/main/scala/vapors/math/Subtraction.scala deleted file mode 100644 index e5f2a23c3..000000000 --- a/core/src/main/scala/vapors/math/Subtraction.scala +++ /dev/null @@ -1,21 +0,0 @@ -package com.rallyhealth - -package vapors.math - -/** - * Defines subtraction of two values for a specific type. - * - * @note order of arguments matter, as subtraction is not commutative. - */ -trait Subtraction[A] { - - def subtract( - lhs: A, - rhs: A, - ): A -} - -object Subtraction extends NumericalImplicits { - - def apply[A](implicit A: Subtraction[A]): Subtraction[A] = A -} diff --git a/core/src/main/scala/vapors/package.scala b/core/src/main/scala/vapors/package.scala deleted file mode 100644 index 7a8dc7f88..000000000 --- a/core/src/main/scala/vapors/package.scala +++ /dev/null @@ -1,346 +0,0 @@ -package com.rallyhealth - -import vapors.algebra.{CaptureP, Expr} -import vapors.data.{FactTable, TypedFact} -import vapors.dsl.{ExprBuilderCatsInstances, ExprBuilderSyntax, ExprDsl, WithOutputSyntax} - -import shapeless.HList - -package object vapors { - - // $COVERAGE-OFF$ - object core { - - @deprecated("Use com.rallyhealth.vapors.algebra instead.", "0.16.0") - object algebra { - - @deprecated("Use com.rallyhealth.vapors.algebra.CaptureP instead.", "0.16.0") - final type CaptureP[V, R, P] = vapors.algebra.CaptureP[V, R, P] - - @deprecated("Use com.rallyhealth.vapors.algebra.CaptureP instead.", "0.16.0") - final val CaptureP = vapors.algebra.CaptureP - - @deprecated("Use com.rallyhealth.vapors.algebra.Expr instead.", "0.16.0") - final type Expr[V, R, P] = vapors.algebra.Expr[V, R, P] - - @deprecated("Use com.rallyhealth.vapors.algebra.Expr instead.", "0.16.0") - final val Expr = vapors.algebra.Expr - - @deprecated("Use com.rallyhealth.vapors.algebra.ConditionBranch instead.", "0.16.0") - final type ConditionBranch[V, R, P] = vapors.algebra.ConditionBranch[V, R, P] - - @deprecated("Use com.rallyhealth.vapors.algebra.ConditionBranch instead.", "0.16.0") - final val ConditionBranch = vapors.algebra.ConditionBranch - - @deprecated("Use com.rallyhealth.vapors.algebra.ExprConverter instead.", "0.16.0") - final type ExprConverter[L, R] = vapors.algebra.ExprConverter[L, R] - - @deprecated("Use com.rallyhealth.vapors.algebra.ExprConverter instead.", "0.16.0") - final val ExprConverter = vapors.algebra.ExprConverter - - @deprecated("Use com.rallyhealth.vapors.algebra.ExprResult instead.", "0.16.0") - final type ExprResult[V, R, P] = vapors.algebra.ExprResult[V, R, P] - - @deprecated("Use com.rallyhealth.vapors.algebra.ExprResult instead.", "0.16.0") - final val ExprResult = vapors.algebra.ExprResult - - @deprecated("Use com.rallyhealth.vapors.algebra.ExprSorter instead.", "0.16.0") - final type ExprSorter[M[_], R] = vapors.algebra.ExprSorter[M, R] - - @deprecated("Use com.rallyhealth.vapors.algebra.ExprSorter instead.", "0.16.0") - final val ExprSorter = vapors.algebra.ExprSorter - - @deprecated("Use com.rallyhealth.vapors.algebra.NonEmptyExprHList instead.", "0.16.0") - final type NonEmptyExprHList[V, M[_], L <: HList, P] = vapors.algebra.NonEmptyExprHList[V, M, L, P] - - @deprecated("Use com.rallyhealth.vapors.algebra.NonEmptyExprHList instead.", "0.16.0") - final val NonEmptyExprHList = vapors.algebra.NonEmptyExprHList - } - - @deprecated("Use com.rallyhealth.vapors.data instead.", "0.16.0") - object data { - - @deprecated("Use com.rallyhealth.vapors.data.Bounded instead.", "0.16.0") - final type Bounded[A] = vapors.data.Bounded[A] - - @deprecated("Use com.rallyhealth.vapors.data.Bounded instead.", "0.16.0") - final val Bounded = vapors.data.Bounded - - @deprecated("Use com.rallyhealth.vapors.data.Evidence instead.", "0.16.0") - final type Evidence = vapors.data.Evidence - - @deprecated("Use com.rallyhealth.vapors.data.Evidence instead.", "0.16.0") - final val Evidence = vapors.data.Evidence - - @deprecated("Use com.rallyhealth.vapors.data.ExtractBoolean instead.", "0.16.0") - final type ExtractBoolean[-T] = vapors.data.ExtractValue[T, Boolean] - - @deprecated("Use com.rallyhealth.vapors.data.ExtractBoolean instead.", "0.16.0") - final val ExtractBoolean = vapors.data.ExtractBoolean - - @deprecated("Use com.rallyhealth.vapors.data.ExtractInstant instead.", "0.16.0") - final type ExtractInstant[-T] = vapors.data.ExtractInstant[T] - - @deprecated("Use com.rallyhealth.vapors.data.ExtractInstant instead.", "0.16.0") - final val ExtractInstant = vapors.data.ExtractInstant - - @deprecated("Use com.rallyhealth.vapors.data.ExtractValue instead.", "0.16.0") - final type ExtractValue[-T, +V] = vapors.data.ExtractValue[T, V] - - @deprecated("Use com.rallyhealth.vapors.data.ExtractValue instead.", "0.16.0") - final val ExtractValue = vapors.data.ExtractValue - - @deprecated("Use com.rallyhealth.vapors.data.Fact instead.", "0.16.0") - final type Fact = vapors.data.Fact - - @deprecated("Use com.rallyhealth.vapors.data.Fact instead.", "0.16.0") - final val Fact = vapors.data.Fact - - @deprecated("Use com.rallyhealth.vapors.data.FactSet instead.", "0.16.0") - final type FactSet = vapors.data.FactSet - - @deprecated("Use com.rallyhealth.vapors.data.FactSet instead.", "0.16.0") - final val FactSet = vapors.data.FactSet - - @deprecated("Use com.rallyhealth.vapors.data.FactTable instead.", "0.16.0") - final type FactTable = vapors.data.FactTable - - @deprecated("Use com.rallyhealth.vapors.data.FactTable instead.", "0.16.0") - final val FactTable = vapors.data.FactTable - - @deprecated("Use com.rallyhealth.vapors.data.FactType instead.", "0.16.0") - final type FactType[T] = vapors.data.FactType[T] - - @deprecated("Use com.rallyhealth.vapors.data.FactType instead.", "0.16.0") - final val FactType = vapors.data.FactType - - @deprecated("Use com.rallyhealth.vapors.data.FactType instead.", "0.16.0") - final type FactTypeSet[A] = vapors.data.FactTypeSet[A] - - @deprecated("Use com.rallyhealth.vapors.data.FactTypeSet instead.", "0.16.0") - final val FactTypeSet = vapors.data.FactTypeSet - - @deprecated("Use com.rallyhealth.vapors.data.TimeOrder instead.", "0.16.0") - final type TimeOrder = vapors.data.TimeOrder - - @deprecated("Use com.rallyhealth.vapors.data.TimeOrder instead.", "0.16.0") - final val TimeOrder = vapors.data.TimeOrder - - @deprecated("Use com.rallyhealth.vapors.data.TypedFact instead.", "0.16.0") - final type TypedFact[A] = vapors.data.TypedFact[A] - - @deprecated("Use com.rallyhealth.vapors.data.TypedFact instead.", "0.16.0") - final val TypedFact = vapors.data.TypedFact - - @deprecated("Use com.rallyhealth.vapors.data.TypedFact instead.", "0.16.0") - final type TypedFactSet[A] = vapors.data.TypedFactSet[A] - - @deprecated("Use com.rallyhealth.vapors.data.TypedFact instead.", "0.16.0") - final val TypedFactSet = vapors.data.TypedFactSet - - @deprecated("Use com.rallyhealth.vapors.data.Window instead.", "0.16.0") - final type Window[A] = vapors.data.Window[A] - - @deprecated("Use com.rallyhealth.vapors.data.Window instead.", "0.16.0") - final val Window = vapors.data.Window - } - - @deprecated("Use com.rallyhealth.vapors.dsl instead.", "0.16.0") - object dsl extends ExprDsl with ExprBuilderSyntax with WithOutputSyntax with ExprBuilderCatsInstances { - - @deprecated("Use com.rallyhealth.vapors.dsl.CondExr instead.", "0.16.0") - final type CondExpr[V, P] = Expr[V, Boolean, P] - - @deprecated("Use com.rallyhealth.vapors.dsl.ValExpr instead.", "0.16.0") - final type ValExpr[V, R, P] = Expr[V, R, P] - - @deprecated("Use com.rallyhealth.vapors.dsl.ValCondExpr instead.", "0.16.0") - final type ValCondExpr[V, P] = ValExpr[V, Boolean, P] - - @deprecated("Use com.rallyhealth.vapors.dsl.RootExpr instead.", "0.16.0") - final type RootExpr[R, P] = Expr[FactTable, R, P] - - @deprecated("Use com.rallyhealth.vapors.dsl.CaptureRootExpr instead.", "0.16.0") - final type CaptureRootExpr[R, P] = CaptureP[FactTable, R, P] - - @deprecated("Use com.rallyhealth.vapors.dsl.CaptureFromFacts instead.", "0.16.0") - final type CaptureFromFacts[T, P] = CaptureP[Seq[TypedFact[T]], Seq[TypedFact[T]], P] - - @deprecated("Use com.rallyhealth.vapors.dsl.ConcatOutputExprBuilder instead.", "0.16.0") - final type ConcatOutputExprBuilder[V, M[_], R, P] = vapors.dsl.ConcatOutputExprBuilder[V, M, R, P] - - @deprecated("Use com.rallyhealth.vapors.dsl.ConcatOutputExprBuilder instead.", "0.16.0") - final val ConcatOutputExprBuilder = vapors.dsl.ConcatOutputExprBuilder - - @deprecated("Use com.rallyhealth.vapors.dsl.ExprBuilder instead.", "0.16.0") - final type ExprBuilder[V, M[_], U, P] = vapors.dsl.ExprBuilder[V, M, U, P] - - @deprecated("Use com.rallyhealth.vapors.dsl.ExprBuilder instead.", "0.16.0") - final val ExprBuilder = vapors.dsl.ExprBuilder - - @deprecated("Use com.rallyhealth.vapors.dsl.FoldableExprBuilder instead.", "0.16.0") - final type FoldableExprBuilder[V, M[_], U, P] = vapors.dsl.FoldableExprBuilder[V, M, U, P] - - @deprecated("Use com.rallyhealth.vapors.dsl.ValExprBuilder instead.", "0.16.0") - final type ValExprBuilder[V, R, P] = vapors.dsl.ValExprBuilder[V, R, P] - - @deprecated("Use com.rallyhealth.vapors.dsl.ExprDsl instead.", "0.16.0") - final type ExprDsl = vapors.dsl.ExprDsl - - @deprecated("Use com.rallyhealth.vapors.dsl.ExprDsl instead.", "0.16.0") - final val ExprDsl = vapors.dsl.ExprDsl - } - - @deprecated("Use com.rallyhealth.vapors.interpreter instead.", "0.16.0") - object interpreter { - - @deprecated("Use com.rallyhealth.vapors.interpreter.ExprInput instead.", "0.16.0") - final type ExprInput[V] = vapors.interpreter.ExprInput[V] - - @deprecated("Use com.rallyhealth.vapors.interpreter.ExprInput instead.", "0.16.0") - final val ExprInput = vapors.interpreter.ExprInput - - @deprecated("Use com.rallyhealth.vapors.interpreter.ExprOutput instead.", "0.16.0") - final type ExprOutput[R] = vapors.interpreter.ExprOutput[R] - - @deprecated("Use com.rallyhealth.vapors.interpreter.ExprOutput instead.", "0.16.0") - final val ExprOutput = vapors.interpreter.ExprOutput - - @deprecated("Use com.rallyhealth.vapors.interpreter.InterpretExprAsResultFn instead.", "0.16.0") - final type InterpretExprAsResultFn[V, P] = vapors.interpreter.InterpretExprAsResultFn[V, P] - - @deprecated("Use com.rallyhealth.vapors.interpreter.InterpretExprAsResultFn instead.", "0.16.0") - final val InterpretExprAsResultFn = vapors.interpreter.InterpretExprAsResultFn - - @deprecated("Use com.rallyhealth.vapors.interpreter.InterpretExprAsSimpleOutputFn instead.", "0.16.0") - final type InterpretExprAsSimpleOutputFn[V, P] = vapors.interpreter.InterpretExprAsSimpleOutputFn[V, P] - - @deprecated("Use com.rallyhealth.vapors.interpreter.InterpretExprAsSimpleOutputFn instead.", "0.16.0") - final val InterpretExprAsSimpleOutputFn = vapors.interpreter.InterpretExprAsSimpleOutputFn - - @deprecated("Use com.rallyhealth.vapors.interpreter.InterpretExprAsSimpleOutputFn instead.", "0.16.0") - final type VisitGenericExprWithProxyFn[V, P, G[_]] = vapors.interpreter.VisitGenericExprWithProxyFn[V, P, G] - } - - @deprecated("Use com.rallyhealth.vapors.lens instead.", "0.16.0") - object lens { - - @deprecated("Use com.rallyhealth.vapors.lens.DataPath instead.", "0.16.0") - final type DataPath = vapors.lens.DataPath - - @deprecated("Use com.rallyhealth.vapors.lens.DataPath instead.", "0.16.0") - final val DataPath = vapors.lens.DataPath - - @deprecated("Use com.rallyhealth.vapors.lens.Indexed instead.", "0.16.0") - final type Indexed[C, K, V] = vapors.lens.Indexed[C, K, V] - - @deprecated("Use com.rallyhealth.vapors.lens.Indexed instead.", "0.16.0") - final val Indexed = vapors.lens.Indexed - - @deprecated("Use com.rallyhealth.vapors.lens.NamedLens instead.", "0.16.0") - final type NamedLens[A, B] = vapors.lens.NamedLens[A, B] - - @deprecated("Use com.rallyhealth.vapors.lens.NamedLens instead.", "0.16.0") - final val NamedLens = vapors.lens.NamedLens - - @deprecated("Use com.rallyhealth.vapors.lens.ValidDataPathKey instead.", "0.16.0") - final type ValidDataPathKey[K] = vapors.lens.ValidDataPathKey[K] - - @deprecated("Use com.rallyhealth.vapors.lens.ValidDataPathKey instead.", "0.16.0") - final val ValidDataPathKey = vapors.lens.ValidDataPathKey - } - - @deprecated("Use com.rallyhealth.vapors.logic instead.", "0.16.0") - object logic { - - @deprecated("Use com.rallyhealth.vapors.logic.Conjunction instead.", "0.16.0") - final type Conjunction[A] = vapors.logic.Conjunction[A] - - @deprecated("Use com.rallyhealth.vapors.logic.Conjunction instead.", "0.16.0") - final val Conjunction = vapors.logic.Conjunction - - @deprecated("Use com.rallyhealth.vapors.logic.Disjunction instead.", "0.16.0") - final type Disjunction[A] = vapors.logic.Disjunction[A] - - @deprecated("Use com.rallyhealth.vapors.logic.Disjunction instead.", "0.16.0") - final val Disjunction = vapors.logic.Disjunction - - @deprecated("Use com.rallyhealth.vapors.logic.Negation instead.", "0.16.0") - final type Negation[A] = vapors.logic.Negation[A] - - @deprecated("Use com.rallyhealth.vapors.logic.Negation instead.", "0.16.0") - final val Negation = vapors.logic.Negation - } - - @deprecated("Use com.rallyhealth.vapors.math instead.", "0.16.0") - object math { - - @deprecated("Use com.rallyhealth.vapors.math.Addition instead.", "0.16.0") - final type Addition[A] = vapors.math.Addition[A] - - @deprecated("Use com.rallyhealth.vapors.math.Addition instead.", "0.16.0") - final val Addition = vapors.math.Addition - - @deprecated("Use com.rallyhealth.vapors.math.Division instead.", "0.16.0") - final type Division[A] = vapors.math.Division[A] - - @deprecated("Use com.rallyhealth.vapors.math.Division instead.", "0.16.0") - final val Division = vapors.math.Division - - @deprecated("Use com.rallyhealth.vapors.math.Multiplication instead.", "0.16.0") - final type Multiplication[A] = vapors.math.Multiplication[A] - - @deprecated("Use com.rallyhealth.vapors.math.Multiplication instead.", "0.16.0") - final val Multiplication = vapors.math.Multiplication - - @deprecated("Use com.rallyhealth.vapors.math.Negative instead.", "0.16.0") - final type Negative[A] = vapors.math.Negative[A] - - @deprecated("Use com.rallyhealth.vapors.math.Negative instead.", "0.16.0") - final val Negative = vapors.math.Negative - - @deprecated("Use com.rallyhealth.vapors.math.Subtraction instead.", "0.16.0") - final type Subtraction[A] = vapors.math.Subtraction[A] - - @deprecated("Use com.rallyhealth.vapors.math.Subtraction instead.", "0.16.0") - final val Subtraction = vapors.math.Subtraction - } - - @deprecated("Use com.rallyhealth.vapors.syntax instead.", "0.16.0") - object syntax { - - @deprecated("Use com.rallyhealth.vapors.syntax.all instead.", "0.16.0") - final val all = vapors.syntax.all - - @deprecated("Use com.rallyhealth.vapors.syntax.indexed instead.", "0.16.0") - final val indexed = vapors.syntax.indexed - - @deprecated("Use com.rallyhealth.vapors.syntax.math instead.", "0.16.0") - final val math = vapors.syntax.math - } - - @deprecated("Use com.rallyhealth.vapors.time instead.", "0.16.0") - object time { - - @deprecated("Use com.rallyhealth.vapors.time.CountTime instead.", "0.16.0") - final type CountTime[-T, -U] = vapors.time.CountTime[T, U] - - @deprecated("Use com.rallyhealth.vapors.time.CountTime instead.", "0.16.0") - final val CountTime = vapors.time.CountTime - - @deprecated("Use com.rallyhealth.vapors.time.ModifyTime instead.", "0.16.0") - final type ModifyTime[T, -D] = vapors.time.ModifyTime[T, D] - - @deprecated("Use com.rallyhealth.vapors.time.ModifyTime instead.", "0.16.0") - final val ModifyTime = vapors.time.ModifyTime - } - - @deprecated("Use com.rallyhealth.vapors.util instead.", "0.16.0") - object util { - - @deprecated("Use com.rallyhealth.vapors.util.ReflectUtils instead.", "0.16.0") - final val ReflectUtils = vapors.util.ReflectUtils - } - } - // $COVERAGE-ON$ -} diff --git a/core/src/main/scala/vapors/syntax/MathSyntax.scala b/core/src/main/scala/vapors/syntax/MathSyntax.scala deleted file mode 100644 index e6a696672..000000000 --- a/core/src/main/scala/vapors/syntax/MathSyntax.scala +++ /dev/null @@ -1,23 +0,0 @@ -package com.rallyhealth - -package vapors.syntax - -import vapors.math._ - -trait MathSyntax { - - implicit def math[A](value: A): MathOps[A] = new MathOps(value) -} - -final class MathOps[A](private val lhs: A) extends AnyVal { - - def +(rhs: A)(implicit A: Addition[A]): A = A.add(lhs, rhs) - - def -(rhs: A)(implicit A: Subtraction[A]): A = A.subtract(lhs, rhs) - - def unary_-(implicit A: Negative[A]): A = A.negative(lhs) - - def *(rhs: A)(implicit A: Multiplication[A]): A = A.multiply(lhs, rhs) - - def /(rhs: A)(implicit A: Division[A]): A = A.quot(lhs, rhs) -} diff --git a/core/src/main/scala/vapors/syntax/package.scala b/core/src/main/scala/vapors/syntax/package.scala deleted file mode 100644 index 509ea1305..000000000 --- a/core/src/main/scala/vapors/syntax/package.scala +++ /dev/null @@ -1,14 +0,0 @@ -package com.rallyhealth - -package vapors - -package object syntax { - - type IndexedSyntax = vapors.v1.lens.IndexedSyntax - - object all extends IndexedSyntax with MathSyntax - - object indexed extends IndexedSyntax - - object math extends MathSyntax -} diff --git a/core/src/main/scala/vapors/time/CountTime.scala b/core/src/main/scala/vapors/time/CountTime.scala deleted file mode 100644 index 9d2e11dd8..000000000 --- a/core/src/main/scala/vapors/time/CountTime.scala +++ /dev/null @@ -1,46 +0,0 @@ -package com.rallyhealth - -package vapors.time - -import java.time.temporal.{ChronoUnit, Temporal, TemporalUnit} - -trait CountTime[-T, -U] { - - /** - * Count the whole number of complete time units between the start and end. - * - * @param start the start of the time range (inclusive) - * @param end the end of the time range (inclusive) - * @param truncateToUnit the unit to round down to - * @return the whole number of units between the start up to, but not including, the end - */ - def between( - start: T, - end: T, - truncateToUnit: U, - ): Long -} - -object CountTime { - - def between[T, U]( - start: T, - end: T, - roundDownToUnit: U, - )(implicit - diffTime: CountTime[T, U], - ): Long = diffTime.between(start, end, roundDownToUnit) - - private final class OfTemporal[-T <: Temporal] extends CountTime[T, TemporalUnit] { - override def between( - start: T, - end: T, - unit: TemporalUnit, - ): Long = start.until(end, unit) - } - - @inline private def temporal[T <: Temporal]: CountTime[T, ChronoUnit] = new OfTemporal[T] - - // TODO: Restrict invalid units by type - implicit val ofTemporal: CountTime[Temporal, ChronoUnit] = temporal -} diff --git a/core/src/main/scala/vapors/time/ModifyTime.scala b/core/src/main/scala/vapors/time/ModifyTime.scala deleted file mode 100644 index 4139b33ef..000000000 --- a/core/src/main/scala/vapors/time/ModifyTime.scala +++ /dev/null @@ -1,67 +0,0 @@ -package com.rallyhealth - -package vapors.time - -import java.time.temporal.{Temporal, TemporalAmount} -import java.time._ - -/** - * Defines the capability of adding or subtracting an amount of time to a temporal moment. - */ -trait ModifyTime[T, -D] { - - /** - * Add a positive or negative temporal amount to a temporal moment. - */ - def addTo( - temporal: T, - amount: D, - ): T -} - -object ModifyTime { - - def addTo[T, D]( - temporal: T, - amount: D, - )(implicit - modifier: ModifyTime[T, D], - ): T = modifier.addTo(temporal, amount) - - private final class OfTemporal[T <: Temporal, -D <: TemporalAmount] extends ModifyTime[T, D] { - override def addTo( - temporal: T, - amount: D, - ): T = amount.addTo(temporal).asInstanceOf[T] - } - - @inline private def temporal[T <: Temporal, D <: TemporalAmount]: ModifyTime[T, D] = new OfTemporal[T, D] - - /** - * Allows adding a [[Duration]] to a [[Instant]]. - * - * An [[Instant]] does not support adding or subtracting year, month, or day [[Period]]s. - */ - implicit val ofInstant: ModifyTime[Instant, Duration] = temporal - - /** - * Allows adding a [[Period]] to a [[LocalDate]]. - * - * A [[LocalDate]] does not support adding or subtracting nanosecond, minute, hour, etc [[Duration]]s. - */ - implicit val ofLocalDate: ModifyTime[LocalDate, Period] = temporal - - /** - * Allows adding any [[TemporalAmount]] to a [[LocalDateTime]]. - * - * A [[LocalDateTime]] supports adding or subtracting time [[Duration]]s as well as date [[Period]]s. - */ - implicit val ofLocalDateTime: ModifyTime[LocalDateTime, TemporalAmount] = temporal - - /** - * Allows adding any [[TemporalAmount]] to a [[ZonedDateTime]]. - * - * A [[ZonedDateTime]] supports adding or subtracting time [[Duration]]s as well as date [[Period]]s. - */ - implicit val ofZonedDateTime: ModifyTime[ZonedDateTime, TemporalAmount] = temporal -} diff --git a/core/src/main/scala/vapors/util/ReflectUtils.scala b/core/src/main/scala/vapors/util/ReflectUtils.scala deleted file mode 100644 index 84b5cd3e7..000000000 --- a/core/src/main/scala/vapors/util/ReflectUtils.scala +++ /dev/null @@ -1,18 +0,0 @@ -package com.rallyhealth - -package vapors.util - -import scala.reflect.runtime.universe.{typeOf, TypeTag} - -object ReflectUtils { - - /** - * Returns the full class name or type name but with the package path removed. - * - * Specifically, it removes all portions of the path that start with a lowercase letter. - * This is to work better with enumeration types and inner classes. It is a simple heuristic - * (albeit, an arbitrary one), so if you want the type name to include the surrounding object - * name, that object needs to start with a capital letter. - */ - def typeNameOf[T : TypeTag]: String = typeOf[T].toString.split('.').dropWhile(_.charAt(0).isLower).mkString(".") -} diff --git a/core/src/test/scala/vapors/algebra/CapturePSpec.scala b/core/src/test/scala/vapors/algebra/CapturePSpec.scala deleted file mode 100644 index 1a7a1b2fc..000000000 --- a/core/src/test/scala/vapors/algebra/CapturePSpec.scala +++ /dev/null @@ -1,32 +0,0 @@ -package com.rallyhealth - -package vapors.algebra - -import vapors.data.Evidence -import vapors.dsl._ -import vapors.example.{FactTypes, JoeSchmoe, TimeRange} - -import org.scalactic.TypeCheckedTripleEquals -import org.scalatest.wordspec.AnyWordSpec - -class CapturePSpec extends AnyWordSpec with TypeCheckedTripleEquals { - - "CaptureP" should { - - "capture a timestamp range post processing param" should { - - import vapors.example.CaptureTimeRange._ - - "find a single fact from a query" in { - val q = factsOfType(FactTypes.WeightMeasurement).exists { - _.get(_.select(_.value.value)) > 18.0 - } - val result = eval(JoeSchmoe.factTable)(q) - assert(result.param.value === TimeRange(JoeSchmoe.weight.value.timestamp)) - assert(result.output.value) - assert(result.output.evidence.nonEmpty) - assertResult(Evidence(JoeSchmoe.weight))(result.output.evidence) - } - } - } -} diff --git a/core/src/test/scala/vapors/data/FactTableSpec.scala b/core/src/test/scala/vapors/data/FactTableSpec.scala deleted file mode 100644 index 853efb0d7..000000000 --- a/core/src/test/scala/vapors/data/FactTableSpec.scala +++ /dev/null @@ -1,39 +0,0 @@ -package com.rallyhealth - -package vapors.data - -import vapors.example.{FactTypes, GenericMeasurement} - -import org.scalatest.wordspec.AnyWordSpec - -import java.time.Instant - -class FactTableSpec extends AnyWordSpec { - - "FactTable" when { - - "given facts that have the same value sort order" should { - val now = Instant.now() - val measurementsAtSameTime = Seq( - GenericMeasurement("systolic", 120, "mmHg", now), - GenericMeasurement("diastolic", 80, "mmHg", now), - ).map(FactTypes.GenericMeasurement(_)) - val unsortedMeasurements = measurementsAtSameTime.toSet - // GenericMeasurements are only sorted by timestamp, so these should all produce a comparison of 0 - val sortedMeasurements = - measurementsAtSameTime.sorted(TypedFact.orderTypedFactByValue[GenericMeasurement].toOrdering) - - "not use that Order instance to remove duplicates" in { - assertResult(unsortedMeasurements) { - FactTable(measurementsAtSameTime).getSet(FactTypes.GenericMeasurement) - } - } - - "return the results as a Seq in the expected sort order" in { - assertResult(sortedMeasurements) { - FactTable(measurementsAtSameTime).getSortedSeq(FactTypes.GenericMeasurement) - } - } - } - } -} diff --git a/core/src/test/scala/vapors/data/WindowSpec.scala b/core/src/test/scala/vapors/data/WindowSpec.scala deleted file mode 100644 index 29ccdbaca..000000000 --- a/core/src/test/scala/vapors/data/WindowSpec.scala +++ /dev/null @@ -1,242 +0,0 @@ -package com.rallyhealth - -package vapors.data - -import cats.Order -import org.scalacheck.Arbitrary -import org.scalacheck.ops._ -import org.scalactic.Equivalence -import org.scalatest.wordspec.AnyWordSpec -import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks._ - -import java.time.{LocalDate, LocalDateTime, LocalTime, ZonedDateTime} -import scala.collection.immutable.NumericRange -import scala.reflect.runtime.universe.{typeOf, TypeTag} - -class WindowSpec extends AnyWordSpec { - - class BoundedWindowTests[A : Arbitrary : Order](buildWindow: (A, A) => Window[A]) { - import cats.syntax.order._ - - private def assertWithBoundedWindow( - testLowerBound: (Window[A], A) => Unit = (_, _) => (), - testUpperBound: (Window[A], A) => Unit = (_, _) => (), - ): Unit = { - forAll { (a1: A, a2: A) => - whenever(a1 != a2) { - val lowerBound = a1 min a2 - val upperBound = a1 max a2 - val window = buildWindow(lowerBound, upperBound) - testLowerBound(window, lowerBound) - testUpperBound(window, upperBound) - } - } - } - - def itIsABoundedWindow( - includeMin: Boolean, - includeMax: Boolean, - ): Unit = { - "return true for contains when the value is inside the bounds" in { - forAll { (a1: A, a2: A, v: A) => - whenever(a1 != a2 && v != a1 && v != a2) { - val lowerBound = a1 min a2 - val upperBound = a1 max a2 - val window = buildWindow(lowerBound, upperBound) - if (v < upperBound && v > lowerBound) { - assert(window.contains(v)) - } else { - assert(!window.contains(v)) - } - } - } - } - - if (includeMin) { - itIsAStartInclusiveWindow() - } else { - itIsAStartExclusiveWindow() - } - if (includeMax) { - itIsAnEndInclusiveWindow() - } else { - itIsAnEndExclusiveWindow() - } - } - - def itIsAStartExclusiveWindow(): Unit = { - "be exclusive in the lower bound" in { - assertWithBoundedWindow( - testLowerBound = (window, lowerBound) => assert(!window.contains(lowerBound)), - ) - } - } - - def itIsAStartInclusiveWindow(): Unit = { - "be inclusive in the lower bound" in { - assertWithBoundedWindow( - testLowerBound = (window, lowerBound) => assert(window.contains(lowerBound)), - ) - } - } - - def itIsAnEndExclusiveWindow(): Unit = { - "be exclusive in the upper bound" in { - assertWithBoundedWindow( - testUpperBound = (window, upperBound) => assert(!window.contains(upperBound)), - ) - } - } - - def itIsAnEndInclusiveWindow(): Unit = { - "be inclusive in the upper bound" in { - assertWithBoundedWindow( - testUpperBound = (window, upperBound) => assert(window.contains(upperBound)), - ) - } - } - } - - "Window" when { - - "fromRange() is given a standard Int Range" when { - - "exclusive (X until Y)" should { - val tests = new BoundedWindowTests[Int]((min, max) => Window.fromRange(min until max)) - behave like tests.itIsABoundedWindow( - includeMin = true, - includeMax = false, - ) - } - - "inclusive (X to Y)" should { - val tests = new BoundedWindowTests[Int]((min, max) => Window.fromRange(min to max)) - tests.itIsABoundedWindow( - includeMin = true, - includeMax = true, - ) - } - } - - def aNumericRangeIgnoringStepSize[A : Integral : Arbitrary : Equivalence](): Unit = { - val I = Integral[A] - implicit val A: Order[A] = Order.fromOrdering - val step2 = I.plus(I.one, I.one) - - "exclusive" should { - val exclusiveRange = new BoundedWindowTests[A]( - (min, max) => Window.fromRange(NumericRange(min, max, step2)), - ) - exclusiveRange.itIsABoundedWindow(includeMin = true, includeMax = false) - } - - "inclusive" should { - val inclusiveRange = new BoundedWindowTests[A]( - (min, max) => Window.fromRange(NumericRange.inclusive(min, max, step2)), - ) - inclusiveRange.itIsABoundedWindow(includeMin = true, includeMax = true) - } - } - - "fromRange() is given an Int NumericRange" when { - behave like aNumericRangeIgnoringStepSize[Int]() - } - - "fromRange() is given a Long NumericRange" when { - behave like aNumericRangeIgnoringStepSize[Long]() - } - - def allWindowTests[A : Arbitrary : Ordering : TypeTag](): Unit = { - implicit val order: Order[A] = Order.fromOrdering - val typeName = typeOf[A].toString - - s"given values of type $typeName" when { - - "between()" should { - val tests = new BoundedWindowTests[A]( - (min, max) => Window.between(min, max), - ) - behave like tests.itIsABoundedWindow( - includeMin = true, - includeMax = false, - ) - } - - "betweenInclusive()" should { - val tests = new BoundedWindowTests[A]( - (min, max) => Window.betweenInclusive(min, max), - ) - behave like tests.itIsABoundedWindow( - includeMin = true, - includeMax = true, - ) - } - - "between() is called given includeMin and includeMax set to false" should { - val tests = new BoundedWindowTests[A]( - (min, max) => Window.between(min, includeMin = false, max, includeMax = false), - ) - behave like tests.itIsABoundedWindow( - includeMin = false, - includeMax = false, - ) - } - - "between() is called given includeMin set to false and includeMax set to true" should { - val tests = new BoundedWindowTests[A]( - (min, max) => Window.between(min, includeMin = false, max, includeMax = true), - ) - behave like tests.itIsABoundedWindow( - includeMin = false, - includeMax = true, - ) - } - - def assertContainsValue( - createWindow: A => Window[A], - chooseBoundary: (A, A) => A, - inclusive: Boolean, - ): Unit = { - forAll { (a1: A, a2: A) => - whenever(inclusive || a1 != a2) { - val boundary = chooseBoundary(a1, a2) - val window = createWindow(boundary) - val value = if (a1 === boundary) a2 else a1 - assert(window.contains(value)) - if (inclusive) { - assert(window.contains(boundary)) - } - } - } - } - - import cats.syntax.order._ - - "greaterThan() contains a value greaterThan a given window boundary" in { - assertContainsValue(Window.greaterThan(_), _ min _, inclusive = false) - } - - "greaterThanOrEqual() contains a value greaterThanOrEqualTo a given window boundary" in { - assertContainsValue(Window.greaterThanOrEqual(_), _ min _, inclusive = true) - } - - "lessThan() contains a value lessThan a given window boundary" in { - assertContainsValue(Window.lessThan(_), _ max _, inclusive = false) - } - - "lessThanOrEqual() contains a value lessThanOrEqualTo a given window boundary" in { - assertContainsValue(Window.lessThanOrEqual(_), _ max _, inclusive = true) - } - } - } - - locally { - allWindowTests[Int]() - allWindowTests[Long]() - allWindowTests[LocalDate]() - allWindowTests[LocalTime]() - allWindowTests[LocalDateTime]() - allWindowTests[ZonedDateTime]() - } - } -} diff --git a/core/src/test/scala/vapors/dsl/ExprBuilderSpec.scala b/core/src/test/scala/vapors/dsl/ExprBuilderSpec.scala deleted file mode 100644 index 40c90ff5e..000000000 --- a/core/src/test/scala/vapors/dsl/ExprBuilderSpec.scala +++ /dev/null @@ -1,58 +0,0 @@ -package com.rallyhealth - -package vapors.dsl - -import vapors.algebra.Expr -import vapors.example.{FactTypes, GenericMeasurement, Probs} -import vapors.lens.DataPath - -import org.scalatest.Inside.inside -import org.scalatest.matchers.should.Matchers._ -import org.scalatest.wordspec.AnyWordSpec - -import java.time.Instant - -class ExprBuilderSpec extends AnyWordSpec { - - "ValExprBuilder" should { - - "combine lenses from chained .get() methods" in { - val q = factsOfType(FactTypes.GenericMeasurement).exists { - _.get(_.select(_.value)).get(_.select(_.value)) > 0.0 - }.returnOutput - inside(q) { - case Expr.ExistsInOutput(_, condExpr, _) => - inside(condExpr) { - case Expr.OutputWithinWindow(Expr.SelectFromOutput(_, valueLens, _), _, _) => - assertResult(DataPath.empty.atField("value").atField("value")) { - valueLens.path - } - val fact = FactTypes.GenericMeasurement(GenericMeasurement("core/exampleample", 1.0, "m", Instant.now())) - assertResult(fact.value.value) { - valueLens.get(fact) - } - } - } - } - - "combine lenses from .get() and .getFoldable() methods" in { - val q = factsOfType(FactTypes.ProbabilityToUse).exists { - _.get(_.select(_.value)).getFoldable(_.select(_.scores)).isEmpty - }.returnOutput - inside(q) { - case Expr.ExistsInOutput(_, condExpr, _) => - inside(condExpr) { - case Expr.OutputIsEmpty(Expr.SelectFromOutput(_, valueLens, _), _) => - assertResult(DataPath.empty.atField("value").atField("scores")) { - valueLens.path - } - val fact = FactTypes.ProbabilityToUse(Probs(Map())) - assertResult(fact.value.scores) { - valueLens.get(fact) - } - } - } - } - } - -} diff --git a/core/src/test/scala/vapors/example/CaptureTimeRange.scala b/core/src/test/scala/vapors/example/CaptureTimeRange.scala deleted file mode 100644 index fdf5f25f3..000000000 --- a/core/src/test/scala/vapors/example/CaptureTimeRange.scala +++ /dev/null @@ -1,81 +0,0 @@ -package com.rallyhealth - -package vapors.example - -import vapors.algebra.{CaptureP, Expr} -import vapors.data.{ExtractInstant, TypedFact} -import vapors.interpreter.{ExprInput, ExprOutput} - -import cats.{Eval, Monoid} - -import java.time.Instant - -object CaptureTimeRange extends CaptureP.AsMonoidCompanion[TimeRange] { - - implicit def captureTimeRangeFromFacts[T : ExtractInstant, R]: CaptureP.AsMonoidFromFactsOfType[T, R, TimeRange] = { - new CaptureP.AsMonoidFromFactsOfType[T, R, TimeRange] { - - override protected def foldWithParentParam( - expr: Expr[Seq[TypedFact[T]], R, TimeRange], - input: ExprInput[Seq[TypedFact[T]]], - output: ExprOutput[R], - processedChildren: TimeRange, - ): Eval[TimeRange] = { - val timestamps = input.value.map(fact => ExtractInstant[T].extractValue(fact.value)) - Eval.now(TimeRange.fromIterable(timestamps)) - } - } - } -} - -/** - * A range of time that tracks the start and end of the range, without any concern for the values inbetween. - * - * @param min the earliest timestamp in the evaluated expression - * @param max the oldest timestamp in the evaluated expression - */ -final case class TimeRange private ( - min: Option[Instant], - max: Option[Instant], -) { - - /** - * Build a new [[TimeRange]] from the earliest start and latest end time of both the given range and this. - */ - def expand(that: TimeRange): TimeRange = - if (this eq TimeRange.empty) that - else if (that eq TimeRange.empty) this - else TimeRange.fromIterable(Array(this.min, that.min, this.max, that.max).flatten[Instant]) -} - -object TimeRange { - - final val empty = TimeRange(None, None) - - @inline final def apply(): TimeRange = empty - - def apply(one: Instant): TimeRange = TimeRange(Some(one), Some(one)) - - def apply( - min: Instant, - max: Instant, - ): TimeRange = TimeRange(Some(min), Some(max)) - - def fromIterable(many: Iterable[Instant]): TimeRange = { - if (many.isEmpty) empty - else { - val sorted = Array.from(many).sortInPlace() - TimeRange(sorted.headOption, sorted.lastOption) - } - } - - implicit val monoid: Monoid[TimeRange] = { - new Monoid[TimeRange] { - override def empty: TimeRange = TimeRange.empty - override def combine( - x: TimeRange, - y: TimeRange, - ): TimeRange = x.expand(y) - } - } -} diff --git a/core/src/test/scala/vapors/example/FactTypes.scala b/core/src/test/scala/vapors/example/FactTypes.scala deleted file mode 100644 index dd1c3cdfd..000000000 --- a/core/src/test/scala/vapors/example/FactTypes.scala +++ /dev/null @@ -1,34 +0,0 @@ -package com.rallyhealth - -package vapors.example - -import vapors.data.{FactType, FactTypeSet} - -import java.time.LocalDate - -object FactTypes { - - // Order all time-based facts by latest first - import vapors.data.TimeOrder.LatestFirst._ - - val Name = FactType[String]("name") - val Age = FactType[Int]("age") - val Role = FactType[Role]("role") - val BirthYear = FactType[Int]("year_of_birth") - val DateOfBirth = FactType[LocalDate]("date_of_birth") - val AddressUpdate = FactType[AddressUpdate]("address_update") - val GenericMeasurement = FactType[GenericMeasurement]("generic_measurement") - val WeightMeasurement = FactType[WeightMeasurementLbs]("weight_measurement") - val WeightSelfReported = FactType[WeightMeasurementLbs]("weight_self_reported") - val BloodPressureMeasurement = FactType[BloodPressure]("blood_pressure") - val Tag = FactType[String]("tag") - val TagsUpdate = FactType[TagsUpdate]("tags_update") - val ProbabilityToUse = FactType[Probs]("probability_to_use") - val TempFahrenheit = FactType[Double]("temp_fahrenheit") - val TempCelcius = FactType[Double]("temp_celcius") -} - -object FactTypeSets { - import FactTypes._ - val Weight = FactTypeSet.of(WeightMeasurement, WeightSelfReported) -} diff --git a/core/src/test/scala/vapors/example/GeneratedUser.scala b/core/src/test/scala/vapors/example/GeneratedUser.scala deleted file mode 100644 index b3d5d3fb2..000000000 --- a/core/src/test/scala/vapors/example/GeneratedUser.scala +++ /dev/null @@ -1,25 +0,0 @@ -package com.rallyhealth - -package vapors.example - -import vapors.data.FactTable - -import org.scalacheck.Gen -import org.scalacheck.rng.Seed - -abstract class GeneratedUser { - - val name: String = getClass.getSimpleName.filterNot(Set('$')) - - val seed: Seed = Seed(name.##) - - lazy val factTable: FactTable = - FactTable(Generators.genFact.pureApply(GeneratedUser.scalaCheckParams, seed)) -} - -object GeneratedUser { - - val scalaCheckParams: Gen.Parameters = Gen.Parameters.default - .withInitialSeed(7) - .withSize(7) -} diff --git a/core/src/test/scala/vapors/example/Generators.scala b/core/src/test/scala/vapors/example/Generators.scala deleted file mode 100644 index 4b650fd80..000000000 --- a/core/src/test/scala/vapors/example/Generators.scala +++ /dev/null @@ -1,40 +0,0 @@ -package com.rallyhealth - -package vapors.example - -import vapors.data.Fact - -import org.scalacheck.Gen -import org.scalacheck.ops._ - -import java.time.Instant - -object Generators { - - lazy val genSmallId: Gen[String] = for { - chars <- Gen.stringOfN(1, Gen.alphaUpperChar) - nums <- Gen.stringOfN(2, Gen.numChar) - } yield chars + nums - - lazy val genMusicalNote: Gen[Char] = Gen.choose('A', 'G') - - lazy val genMusicalNoteOrEmpty: Gen[String] = Gen.option(Gen.stringOfN(1, genMusicalNote)).map(_.getOrElse("")) - - lazy val genTimestampWithin90Days: Gen[Instant] = Gen.javaInstant.beforeNowWithin(java.time.Duration.ofDays(90)) - - def genTagsSource: Gen[String] = genMusicalNoteOrEmpty - - lazy val genTags: Gen[Set[String]] = Gen.setOf(genSmallId) - - lazy val genTagsUpdate: Gen[TagsUpdate] = for { - source <- genTagsSource - tags <- genTags - timestamp <- genTimestampWithin90Days - } yield TagsUpdate( - source, - tags, - timestamp, - ) - - lazy val genFact: Gen[Fact] = genTagsUpdate.map(FactTypes.TagsUpdate) -} diff --git a/core/src/test/scala/vapors/example/JoeSchmoe.scala b/core/src/test/scala/vapors/example/JoeSchmoe.scala deleted file mode 100644 index 0ccacfd40..000000000 --- a/core/src/test/scala/vapors/example/JoeSchmoe.scala +++ /dev/null @@ -1,51 +0,0 @@ -package com.rallyhealth - -package vapors.example - -import vapors.data.{FactSet, FactTable} - -import java.time.{Instant, LocalDate, ZoneOffset} - -object JoeSchmoe { - val name = FactTypes.Name("Joe Schmoe") - val age = FactTypes.Age(32) - val userRole = FactTypes.Role(Role.User) - val adminRole = FactTypes.Role(Role.Admin) - val dateOfBirth = FactTypes.DateOfBirth(LocalDate.of(1988, 8, 8)) - - val lastAddressUpdate = FactTypes.AddressUpdate( - AddressUpdate( - Address("123 elm", "", "Springfield", "CO", "81073"), - Instant.from(LocalDate.of(2018, 3, 12).atStartOfDay(ZoneOffset.UTC)), - ), - ) - - val weight = FactTypes.WeightMeasurement( - WeightMeasurementLbs(250.0, LocalDate.of(2020, 5, 1).atStartOfDay().toInstant(ZoneOffset.UTC)), - ) - - val weightSelfReported = FactTypes.WeightSelfReported( - WeightMeasurementLbs(240.0, LocalDate.of(2019, 1, 30).atStartOfDay().toInstant(ZoneOffset.UTC)), - ) - - val bloodPressure = FactTypes.BloodPressureMeasurement( - BloodPressure(120, 80, LocalDate.of(2020, 5, 1).atStartOfDay().toInstant(ZoneOffset.UTC)), - ) - - val probs = FactTypes.ProbabilityToUse(Probs(Map("weightloss" -> .8))) - val asthmaTag = FactTypes.Tag("asthma") - - val facts = FactSet( - name, - age, - adminRole, - userRole, - weight, - weightSelfReported, - bloodPressure, - probs, - asthmaTag, - ) - - val factTable = FactTable(facts.toList) -} diff --git a/core/src/test/scala/vapors/example/SimpleTagUpdates.scala b/core/src/test/scala/vapors/example/SimpleTagUpdates.scala deleted file mode 100644 index 853f900c0..000000000 --- a/core/src/test/scala/vapors/example/SimpleTagUpdates.scala +++ /dev/null @@ -1,13 +0,0 @@ -package com.rallyhealth - -package vapors.example - -import java.time.Instant - -object SimpleTagUpdates { - - private val now = Instant.now() - final val tagsNow = TagsUpdate("xSource", Set("X1", "X2"), now) - final val tags5MinAgo = TagsUpdate("ySource", Set("Y1"), now.minusSeconds(5 * 60)) - final val tags15MinAgo = TagsUpdate("zSource", Set("Z1"), now.minusSeconds(15 * 60)) -} diff --git a/core/src/test/scala/vapors/example/Snippets.scala b/core/src/test/scala/vapors/example/Snippets.scala deleted file mode 100644 index 5d14e102a..000000000 --- a/core/src/test/scala/vapors/example/Snippets.scala +++ /dev/null @@ -1,58 +0,0 @@ -package com.rallyhealth - -package vapors.example - -import vapors.algebra.Expr -import vapors.data.{FactTable, FactType} -import vapors.dsl._ - -import cats.Id - -import java.time._ -import java.time.temporal.ChronoUnit - -class Snippets(val clock: Clock) { - - def this(fixedInstant: Instant) = this(Clock.fixed(fixedInstant, ZoneOffset.UTC)) - - def this(fixedLocalDate: LocalDate) = - this(Clock.fixed(fixedLocalDate.atStartOfDay(ZoneId.systemDefault()).toInstant, ZoneId.systemDefault())) - - val ageFromDateOfBirth: Expr[FactTable, Seq[Int], Unit] = { - valuesOfType(FactTypes.DateOfBirth).map { dob => - val ageInYears = dateDiff( - dob, - dob.embedResult(today(clock)), - dob.embedConst(ChronoUnit.YEARS), - ) - ageInYears.withOutputValue.get(_.select(_.toInt)) - } - } - - val ageFromDateOfBirthDef: Expr.Definition[Unit] = { - define(FactTypes.Age).fromEvery { - ageFromDateOfBirth - } - } - - val isOver18: RootExpr[Boolean, Unit] = { - usingDefinitions(ageFromDateOfBirthDef) { - factsOfType(FactTypes.Age).exists { - _.value >= 18 - } - } - } - - lazy val isUser: RootExpr[Boolean, Unit] = { - factsOfType(FactTypes.Role).exists { - _.value >= Role.User - } - } - - val isEligible: RootExpr[Boolean, Unit] = and(isOver18, isUser) - - val isEligibleDef: Expr.Define[Id, Boolean, Unit] = - define(FactType[Boolean]("is_eligible")).from(isEligible) -} - -object Snippets extends Snippets(Clock.systemDefaultZone()) diff --git a/core/src/test/scala/vapors/example/Tags.scala b/core/src/test/scala/vapors/example/Tags.scala deleted file mode 100644 index 07914bce2..000000000 --- a/core/src/test/scala/vapors/example/Tags.scala +++ /dev/null @@ -1,11 +0,0 @@ -package com.rallyhealth - -package vapors.example - -object Tags { - val asthma = FactTypes.Tag("asthma") - val obeseBmi = FactTypes.Tag("obese_bmi") - val normalBmi = FactTypes.Tag("normal_bmi") - val smoker = FactTypes.Tag("smoker") - val nonSmoker = FactTypes.Tag("non_smoker") -} diff --git a/core/src/test/scala/vapors/example/models.scala b/core/src/test/scala/vapors/example/models.scala deleted file mode 100644 index b08940656..000000000 --- a/core/src/test/scala/vapors/example/models.scala +++ /dev/null @@ -1,101 +0,0 @@ -package com.rallyhealth - -package vapors.example - -import vapors.data.TimeOrder.LatestFirst._ -import vapors.data._ - -import cats.Order - -import java.time.Instant - -final case class Probs(scores: Map[String, Double]) - -object Probs { - implicit val order: Order[Probs] = Order.whenEqual( - Order.reverse(Order.by(_.scores.size)), - Order.by(_.scores.toSeq), - ) -} - -sealed trait Role - -object Role { - case object Admin extends Role - case object User extends Role - - implicit val order: Order[Role] = Order.reverse { - Order.by[Role, Int] { - case Admin => 1 - case User => 2 - } - } -} - -sealed trait ColorCoding - -object ColorCoding { - final case object Red extends ColorCoding - final case object Green extends ColorCoding - final case object Blue extends ColorCoding -} - -trait HasTimestamp { - def timestamp: Instant -} - -object HasTimestamp { - implicit val extractTimestamp: ExtractInstant[HasTimestamp] = _.timestamp - implicit def orderByTimestampLatestFirst[T <: HasTimestamp]: Order[T] = Order.by(_.timestamp) -} - -sealed trait Measurement extends HasTimestamp { - def name: String -} - -sealed trait NumericMeasurement extends Measurement { - def value: Double - def unit: String -} - -final case class BloodPressure( - diastolic: Double, - systolic: Double, - timestamp: Instant, -) extends Measurement { - override def name: String = "blood_pressure" -} - -final case class WeightMeasurementLbs( - value: Double, - timestamp: Instant, -) extends NumericMeasurement { - override def name: String = "weight" - override def unit: String = "lbs" -} - -final case class GenericMeasurement( - name: String, - value: Double, - unit: String, - timestamp: Instant, -) extends Measurement - -final case class TagsUpdate( - source: String, - tags: Set[String], - timestamp: Instant, -) extends HasTimestamp - -final case class Address( - street1: String, - street2: String, - city: String, - state: String, - zip: String, -) - -final case class AddressUpdate( - address: Address, - timestamp: Instant, -) extends HasTimestamp diff --git a/core/src/test/scala/vapors/example/users.scala b/core/src/test/scala/vapors/example/users.scala deleted file mode 100644 index 3c4fae4ba..000000000 --- a/core/src/test/scala/vapors/example/users.scala +++ /dev/null @@ -1,13 +0,0 @@ -package com.rallyhealth - -package vapors.example - -// Some sample users to compare for testing that generate the same facts every time -// this should hopefully make it easier to spot when something is awry with the sample user. -// Unlike JoeSchmoe, you cannot make any assumptions about the presence of facts so you should -// verify that your test user has the necessary pre-conditions. If not, you should pick a -// different generated user. - -case object User1 extends GeneratedUser -case object User2 extends GeneratedUser -case object User3 extends GeneratedUser diff --git a/core/src/test/scala/vapors/interpreter/AddOutputsSpec.scala b/core/src/test/scala/vapors/interpreter/AddOutputsSpec.scala deleted file mode 100644 index 07d471472..000000000 --- a/core/src/test/scala/vapors/interpreter/AddOutputsSpec.scala +++ /dev/null @@ -1,61 +0,0 @@ -package com.rallyhealth - -package vapors.interpreter - -import vapors.dsl._ - -import org.scalatest.freespec.AnyFreeSpec - -class AddOutputsSpec extends AnyFreeSpec { - - "Expr.AddOutputs" - { - - "Int" - { - - "expression added to an expression" in { - VaporsEvalTestHelpers.producesTheSameResultOrException[Int, Int, Int, ArithmeticException]( - _ + _, - const(_) + const(_), - ) - } - - "value added to an expression" in { - VaporsEvalTestHelpers.producesTheSameResultOrException[Int, Int, Int, ArithmeticException]( - _ + _, - const(_) + _, - ) - } - - "expression added to a value" in { - VaporsEvalTestHelpers.producesTheSameResultOrException[Int, Int, Int, ArithmeticException]( - _ + _, - const(_).addTo(_), - ) - } - } - - "Double" - { - - "expression added to an expression" in { - VaporsEvalTestHelpers.producesTheSameResultOrException[Double, Double, Double, ArithmeticException]( - _ + _, - const(_) + const(_), - ) - } - - "value added to an expression" in { - VaporsEvalTestHelpers.producesTheSameResultOrException[Double, Double, Double, ArithmeticException]( - _ + _, - const(_) + _, - ) - } - - "expression added to a value" in { - VaporsEvalTestHelpers.producesTheSameResultOrException[Double, Double, Double, ArithmeticException]( - _ + _, - const(_).addTo(_), - ) - } - } - } -} diff --git a/core/src/test/scala/vapors/interpreter/ConcatOutputSpec.scala b/core/src/test/scala/vapors/interpreter/ConcatOutputSpec.scala deleted file mode 100644 index 241ad389c..000000000 --- a/core/src/test/scala/vapors/interpreter/ConcatOutputSpec.scala +++ /dev/null @@ -1,90 +0,0 @@ -package com.rallyhealth - -package vapors.interpreter - -import vapors.data.{Evidence, FactTable} -import vapors.dsl._ -import vapors.example.{FactTypes, JoeSchmoe} - -import org.scalatest.Inside.inside -import org.scalatest.freespec.AnyFreeSpec - -class ConcatOutputSpec extends AnyFreeSpec { - - "concat should" - { - - "combine all elements into the given traversable type" in { - val factTypes = List(FactTypes.TagsUpdate, FactTypes.WeightMeasurement, FactTypes.WeightSelfReported) - val expectedFacts = factTypes.flatMap(JoeSchmoe.factTable.getSortedSeq(_)) - val instantQueries = factTypes.map { factType => - valuesOfType(factType).map(_.get(_.select(_.timestamp))).returnOutput - } - val query = concat(instantQueries: _*).toOutputMonoid - val result = eval(JoeSchmoe.factTable)(query) - assertResult(expectedFacts.map(_.value.timestamp))(result.output.value) - assertResult(Evidence(expectedFacts)) { - result.output.evidence - } - } - - "combine an empty list of expressions into an empty list" in { - val query = concat[FactTable, List, Nothing, Unit]().toOutputMonoid - val result = eval(JoeSchmoe.factTable)(query) - assertResult(Nil)(result.output.value) - assertResult(Evidence.none) { - result.output.evidence - } - } - - "concat mixed elements types into a common supertype" in { - val factTypes = List(FactTypes.TagsUpdate, FactTypes.WeightMeasurement, FactTypes.WeightSelfReported) - val expectedFacts = factTypes.flatMap(JoeSchmoe.factTable.getSortedSeq(_)) - val instantQueries = factTypes.map(factsOfType(_).returnOutput) - val query = concat(instantQueries: _*).toOutputMonoid.withOutputFoldable.map(_.value.get(_.select(_.timestamp))) - val result = eval(JoeSchmoe.factTable)(query) - assertResult(expectedFacts.map(_.value.timestamp))(result.output.value) - assertResult(Evidence(expectedFacts)) { - result.output.evidence - } - } - - "combine all elements into a LazyList" in { - val factTypes = List(FactTypes.TagsUpdate, FactTypes.WeightMeasurement, FactTypes.WeightSelfReported) - val expectedFacts = factTypes.flatMap(JoeSchmoe.factTable.getSortedSeq(_)) - val instantQueries = factTypes.map { factType => - valuesOfType(factType).map(_.get(_.select(_.timestamp))).returnOutput - } - val query = concat(instantQueries: _*).toLazyList - val result = eval(JoeSchmoe.factTable)(query) - assertResult(expectedFacts.map(_.value.timestamp))(result.output.value) - assertResult(Evidence(expectedFacts)) { - result.output.evidence - } - } - - "combined all elements into a LazyList without forcing the values" in { - val factTypes = List(FactTypes.TagsUpdate, FactTypes.WeightMeasurement, FactTypes.WeightSelfReported) - val instantQueries = factTypes.map { factType => - valuesOfType(factType).map(_.get(_.select(_.timestamp))).returnOutput - } - val query = concat(instantQueries: _*).toLazyList - val result = eval(JoeSchmoe.factTable)(query) - inside(result.output.value) { - case values: LazyList[_] => - // confirm that the collection does not start with a definite size because it was unforced - assert(!values.hasDefiniteSize) - // confirm that forcing the collection causes it to have a definite size - assert(values.force.hasDefiniteSize) - } - } - - "combine an empty list of expressions into an empty lazy list" in { - val query = concat[FactTable, List, Nothing, Unit]().toLazyList - val result = eval(JoeSchmoe.factTable)(query) - assertResult(LazyList.empty)(result.output.value) - assertResult(Evidence.none) { - result.output.evidence - } - } - } -} diff --git a/core/src/test/scala/vapors/interpreter/DivideOutputsSpec.scala b/core/src/test/scala/vapors/interpreter/DivideOutputsSpec.scala deleted file mode 100644 index 434d92a72..000000000 --- a/core/src/test/scala/vapors/interpreter/DivideOutputsSpec.scala +++ /dev/null @@ -1,128 +0,0 @@ -package com.rallyhealth - -package vapors.interpreter - -import vapors.algebra.Expr -import vapors.data.FactTable -import vapors.dsl -import vapors.dsl._ -import vapors.example.FactTypes - -import org.scalatest.freespec.AnyFreeSpec - -class DivideOutputsSpec extends AnyFreeSpec { - - "Expr.DivideOutputs" - { - - "Int" - { - - "expression divided by an expression" in { - VaporsEvalTestHelpers.producesTheSameResultOrException[Int, Int, Int, ArithmeticException]( - _ / _, - const(_) / const(_), - ) - } - - "expression divided by a value" in { - VaporsEvalTestHelpers.producesTheSameResultOrException[Int, Int, Int, ArithmeticException]( - _ / _, - const(_) / _, - ) - } - - "value divided from an expression" in { - VaporsEvalTestHelpers.producesTheSameResultOrException[Int, Int, Int, ArithmeticException]( - (a, b) => b / a, - const(_).divideFrom(_), - ) - } - - lazy val humanAgeFromDogYears: Expr[FactTable, Seq[Int], Unit] = { - valuesOfType(FactTypes.Age).map { - _ / 7 - } - } - - "48 dog years is 6 human years" in { - val result = eval(FactTable(FactTypes.Age(48))) { - humanAgeFromDogYears.withOutputFoldable.exists { - _ === 6 - } - } - assert(result.output.value) - } - - "49 dog years is 7 human years" in { - val result = eval(FactTable(FactTypes.Age(49))) { - humanAgeFromDogYears.withOutputFoldable.exists { - _ === 7 - } - } - assert(result.output.value) - } - - "50 dog years is 7 human years" in { - val result = eval(FactTable(FactTypes.Age(50))) { - humanAgeFromDogYears.withOutputFoldable.exists { - _ === 7 - } - } - assert(result.output.value) - } - } - - "Double" - { - - "expression divided by an expression" in { - VaporsEvalTestHelpers.producesTheSameResultOrException[Double, Double, Double, ArithmeticException]( - _ / _, - const(_) / const(_), - ) - } - - "expression divided by a value" in { - VaporsEvalTestHelpers.producesTheSameResultOrException[Double, Double, Double, ArithmeticException]( - _ / _, - const(_) / _, - ) - } - - "value divided from an expression" in { - VaporsEvalTestHelpers.producesTheSameResultOrException[Double, Double, Double, ArithmeticException]( - (a, b) => b / a, - const(_).divideFrom(_), - ) - } - - lazy val celciusFromFahrenheit: Expr[FactTable, Seq[Double], Unit] = { - valuesOfType(FactTypes.TempFahrenheit).map { tempF => - (tempF - 32.0) / 1.8 - } - } - - "is 32F === 0C (shorter form)" in { - val result = eval(FactTable(FactTypes.TempFahrenheit(32.0))) { - celciusFromFahrenheit.withOutputFoldable.exists { v => - // TODO Support tolerance here - // === 0.0 works here, too - val matchVal = 0.0 - dsl.not(and(v > matchVal, v < matchVal)) - } - } - assert(result.output.value) - } - - "is 50F === 10C (shorter form)" in { - val result = eval(FactTable(FactTypes.TempFahrenheit(50.0))) { - celciusFromFahrenheit.withOutputFoldable.exists { v => - // TODO support tolerance here - // === 10.0 works here, too - val matchVal = 10.0 - dsl.not(and(v > matchVal, v < matchVal)) - } - } - assert(result.output.value) - } - } - } -} diff --git a/core/src/test/scala/vapors/interpreter/EmbedExprSpec.scala b/core/src/test/scala/vapors/interpreter/EmbedExprSpec.scala deleted file mode 100644 index 7a5a403f2..000000000 --- a/core/src/test/scala/vapors/interpreter/EmbedExprSpec.scala +++ /dev/null @@ -1,186 +0,0 @@ -package com.rallyhealth - -package vapors.interpreter - -import vapors.data._ -import vapors.dsl._ -import vapors.example.{FactTypes, JoeSchmoe} - -import org.scalatest.matchers.should.Matchers._ -import org.scalatest.wordspec.AnyWordSpec - -class EmbedExprSpec extends AnyWordSpec { - - "embedding an expression inside a logical operator inside a 'withFactsOfType'" when { - - def insideProbOfWeightloss(cond: ValCondExpr[Double, Unit]): RootExpr[Boolean, Unit] = { - valuesOfType(FactTypes.ProbabilityToUse) - .flatMap { - _.getFoldable { - _.select(_.scores).at("weightloss").to(Seq) - } - } - .exists { _ => - cond - } - } - - def weightMeasuredWithin(window: Window[Int]): RootExpr[Boolean, Unit] = { - import cats.syntax.invariant._ - val doubleWindow = window.imap(_.toDouble)(_.toInt) - valuesOfType(FactTypes.WeightMeasurement).exists { - _.get(_.select(_.value)).within(doubleWindow) - } - } - - val trueEmbedded = weightMeasuredWithin(Window.greaterThan(200)) - val falseEmbedded = weightMeasuredWithin(Window.greaterThan(300)) - val embeddedFacts = FactSet(JoeSchmoe.weight) - - def trueLiteral: ValCondExpr[Double, Unit] = { - within(input, const(Window.greaterThan(0.7))) - } - - def falseLiteral: ValCondExpr[Double, Unit] = { - within(input, const(Window.greaterThan(0.9))) - } - - val literalFacts = FactSet(JoeSchmoe.probs) - - "inside an 'or'" should { - - "return 'true' when embedding a 'true' expression BEFORE a 'true' literal" in { - val q = insideProbOfWeightloss(or(trueEmbedded, trueLiteral)) - val result = eval(JoeSchmoe.factTable)(q) - assert(result.output.value) - assertResult(Evidence(embeddedFacts | literalFacts))(result.output.evidence) - } - - "return 'true' when embedding a 'true' expression BEFORE a 'false' literal" in { - val q = insideProbOfWeightloss(or(trueEmbedded, falseLiteral)) - val result = eval(JoeSchmoe.factTable)(q) - assert(result.output.value) - assertResult(Evidence(embeddedFacts))(result.output.evidence) - } - - "return 'true' when embedding a 'false' expression BEFORE a 'true' literal" in { - val q = insideProbOfWeightloss(or(falseEmbedded, trueLiteral)) - val result = eval(JoeSchmoe.factTable)(q) - assert(result.output.value) - assertResult(Evidence(literalFacts))(result.output.evidence) - } - - "return 'false' when embedding a 'false' expression BEFORE a 'false' literal" in { - val q = insideProbOfWeightloss(or(falseEmbedded, falseLiteral)) - val result = eval(JoeSchmoe.factTable)(q) - assert(!result.output.value) - assertResult(Evidence.none)(result.output.evidence) - } - - "return 'true' when embedding a 'true' expression AFTER a 'true' literal" in { - val q = insideProbOfWeightloss(or(trueLiteral, trueEmbedded)) - val result = eval(JoeSchmoe.factTable)(q) - assert(result.output.value) - assertResult(Evidence(literalFacts | embeddedFacts))(result.output.evidence) - } - - "return 'true' when embedding a 'true' expression AFTER a 'false' literal" in { - val q = insideProbOfWeightloss(or(falseLiteral, trueEmbedded)) - val result = eval(JoeSchmoe.factTable)(q) - assert(result.output.value) - assertResult(Evidence(embeddedFacts))(result.output.evidence) - } - - "return 'true' when embedding a 'false' expression AFTER a 'true' literal" in { - val q = insideProbOfWeightloss(or(trueLiteral, falseEmbedded)) - val result = eval(JoeSchmoe.factTable)(q) - assert(result.output.value) - assertResult(Evidence(literalFacts))(result.output.evidence) - } - - "return 'false' when embedding a 'false' expression AFTER a 'false' literal" in { - val q = insideProbOfWeightloss(or(falseLiteral, falseEmbedded)) - val result = eval(JoeSchmoe.factTable)(q) - assert(!result.output.value) - assertResult(Evidence.none)(result.output.evidence) - } - - "disallows embedding an invalid return type" in { - val listOfNumberExpr = valuesOfType(FactTypes.ProbabilityToUse).flatMap { - _.getFoldable(_.select(_.scores).at("weightloss").to(Seq)) - } - assertDoesNotCompile { - """insideProbOfWeightloss(or(trueLiteral, listOfNumberExpr))""" - } - } - } - - "inside an 'and'" should { - - "return 'true' when embedding a 'true' expression BEFORE a 'true' literal" in { - val q = insideProbOfWeightloss(and(trueEmbedded, trueLiteral)) - val result = eval(JoeSchmoe.factTable)(q) - assert(result.output.value) - assertResult(Evidence(embeddedFacts | literalFacts))(result.output.evidence) - } - - "return 'false' when embedding a 'true' expression BEFORE a 'false' literal" in { - val q = insideProbOfWeightloss(and(trueEmbedded, falseLiteral)) - val result = eval(JoeSchmoe.factTable)(q) - assert(!result.output.value) - assertResult(Evidence.none)(result.output.evidence) - } - - "return 'false' when embedding a 'false' expression BEFORE a 'true' literal" in { - val q = insideProbOfWeightloss(and(falseEmbedded, trueLiteral)) - val result = eval(JoeSchmoe.factTable)(q) - assert(!result.output.value) - assertResult(Evidence.none)(result.output.evidence) - } - - "return 'false' when embedding a 'false' expression BEFORE a 'false' literal" in { - val q = insideProbOfWeightloss(and(falseEmbedded, falseLiteral)) - val result = eval(JoeSchmoe.factTable)(q) - assert(!result.output.value) - assertResult(Evidence.none)(result.output.evidence) - } - - "return 'true' when embedding a 'true' expression AFTER a 'true' literal" in { - val q = insideProbOfWeightloss(and(trueLiteral, trueEmbedded)) - val result = eval(JoeSchmoe.factTable)(q) - assert(result.output.value) - assertResult(Evidence(literalFacts | embeddedFacts))(result.output.evidence) - } - - "return 'false' when embedding a 'true' expression AFTER a 'false' literal" in { - val q = insideProbOfWeightloss(and(falseLiteral, trueEmbedded)) - val result = eval(JoeSchmoe.factTable)(q) - assert(!result.output.value) - assertResult(Evidence.none)(result.output.evidence) - } - - "return 'false' when embedding a 'false' expression AFTER a 'true' literal" in { - val q = insideProbOfWeightloss(and(trueLiteral, falseEmbedded)) - val result = eval(JoeSchmoe.factTable)(q) - assert(!result.output.value) - assertResult(Evidence.none)(result.output.evidence) - } - - "return 'false' when embedding a 'false' expression AFTER a 'false' literal" in { - val q = insideProbOfWeightloss(and(falseLiteral, falseEmbedded)) - val result = eval(JoeSchmoe.factTable)(q) - assert(!result.output.value) - assertResult(Evidence.none)(result.output.evidence) - } - - "disallows embedding an invalid return type" in { - val listOfNumberExpr = valuesOfType(FactTypes.ProbabilityToUse).flatMap { - _.getFoldable(_.select(_.scores).at("weightloss").asIterable.to(Seq)) - } - assertDoesNotCompile { - """insideProbOfWeightloss(and(trueLiteral, listOfNumberExpr))""" - } - } - } - } -} diff --git a/core/src/test/scala/vapors/interpreter/ExponentiateOutputsSpec.scala b/core/src/test/scala/vapors/interpreter/ExponentiateOutputsSpec.scala deleted file mode 100644 index a94a107c6..000000000 --- a/core/src/test/scala/vapors/interpreter/ExponentiateOutputsSpec.scala +++ /dev/null @@ -1,31 +0,0 @@ -package com.rallyhealth - -package vapors.interpreter - -import vapors.dsl -import vapors.dsl._ - -import org.scalacheck.{Arbitrary, Gen} -import org.scalatest.freespec.AnyFreeSpec - -import scala.reflect.classTag - -class ExponentiateOutputsSpec extends AnyFreeSpec { - - "Expr.ExponentiateOutputs" - { - - "reasonable positive number raised to a reasonable positive exponent" in { - VaporsEvalTestHelpers.producesTheSameResultOrException[Double, Double, Double, ArithmeticException]( - Math.pow, - (b, e) => dsl.pow(const(b), const(e)), - )(Arbitrary(Gen.choose(0d, 100d)), Arbitrary(Gen.choose(1d, 10d)), classTag[ArithmeticException]) - } - - "arbitrary number raised to a arbitrary exponent" in { - VaporsEvalTestHelpers.producesTheSameResultOrException[Double, Double, Double, ArithmeticException]( - Math.pow, - (b, e) => dsl.pow(const(b), const(e)), - ) - } - } -} diff --git a/core/src/test/scala/vapors/interpreter/FilterOutputSpec.scala b/core/src/test/scala/vapors/interpreter/FilterOutputSpec.scala deleted file mode 100644 index 30e124f14..000000000 --- a/core/src/test/scala/vapors/interpreter/FilterOutputSpec.scala +++ /dev/null @@ -1,203 +0,0 @@ -package com.rallyhealth - -package vapors.interpreter - -import vapors.data.{Evidence, FactTable} -import vapors.dsl._ -import vapors.example.{FactTypes, Tags} - -import org.scalatest.matchers.should.Matchers._ -import org.scalatest.wordspec.AnyWordSpec - -class FilterOutputSpec extends AnyWordSpec { - - "Expr.FilterOutput" when { - - "using with an 'OutputWithinSet' op" when { - val tagFacts = FactTable(Tags.smoker, Tags.asthma, Tags.normalBmi) - - "comparing facts" should { - - "return all matching facts from a given subset" in { - val q = factsOfType(FactTypes.Tag).filter { - _ in Set(Tags.asthma) - } - val res = eval(tagFacts)(q) - res.output.value should contain theSameElementsAs Seq(Tags.asthma) - assertResult(Evidence(Tags.asthma))(res.output.evidence) - } - - "return all matching facts from a given superset" in { - val q = factsOfType(FactTypes.Tag).filter { - _ in Set(Tags.asthma, Tags.obeseBmi) - } - val res = eval(tagFacts)(q) - res.output.value should contain theSameElementsAs Seq(Tags.asthma) - assertResult(Evidence(Tags.asthma))(res.output.evidence) - } - - "return an empty list of facts when given a set that contains no common elements" in { - val q = factsOfType(FactTypes.Tag).filter { - _ in Set(Tags.obeseBmi) - } - val res = eval(tagFacts)(q) - assert(res.output.value.isEmpty) - assert(res.output.evidence.isEmpty) - } - } - - "comparing values" should { - - "return all matching values from a given subset" in { - val q = factsOfType(FactTypes.Tag).map(_.value).filter { - _ in Set(Tags.asthma).map(_.value) - } - val res = eval(tagFacts)(q) - res.output.value should contain theSameElementsAs Seq(Tags.asthma).map(_.value) - } - - "return the correct evidence for the matching values from a given subset" in { - val q = factsOfType(FactTypes.Tag).map(_.value).filter { - _ in Set(Tags.asthma).map(_.value) - } - val res = eval(tagFacts)(q) - pendingUntilFixed { - // TODO: Merge this assertion the above unit test when it passes - assertResult(Evidence(Tags.asthma))(res.output.evidence) - } - } - - "return the matching values from a given superset" in { - val q = factsOfType(FactTypes.Tag).map(_.value).filter { - _ in Set(Tags.asthma, Tags.obeseBmi).map(_.value) - } - val res = eval(tagFacts)(q) - res.output.value should contain theSameElementsAs Seq(Tags.asthma).map(_.value) - } - - "return the correct evidence for the matching values from a given superset" in { - val q = factsOfType(FactTypes.Tag).map(_.value).filter { - _ in Set(Tags.asthma, Tags.obeseBmi).map(_.value) - } - val res = eval(tagFacts)(q) - pendingUntilFixed { - // TODO: Merge this assertion the above unit test when it passes - assertResult(Evidence(Tags.asthma))(res.output.evidence) - } - } - - "return an empty list of values when given a set that contains no common elements" in { - val q = factsOfType(FactTypes.Tag).map(_.value).filter { - _ in Set(Tags.obeseBmi).map(_.value) - } - val res = eval(tagFacts)(q) - assert(res.output.value.isEmpty) - assert(res.output.evidence.isEmpty) - } - } - - "using the 'containsAny' op" should { - - "return 'true' when the fact table contains a superset of the given set" in { - val q = factsOfType(FactTypes.Tag).map(_.value).containsAny { - Set(Tags.asthma).map(_.value) - } - val res = eval(tagFacts)(q) - assert(res.output.value) - } - - "return the correct evidence for the facts that contain a superset of the given set" in { - val q = factsOfType(FactTypes.Tag).map(_.value).containsAny { - Set(Tags.asthma).map(_.value) - } - val res = eval(tagFacts)(q) - pendingUntilFixed { - // TODO: Merge this assertion the above unit test when it passes - assertResult(Evidence(Tags.asthma))(res.output.evidence) - } - } - - "return 'true' when the fact table contains a subset of the given set" in { - val q = factsOfType(FactTypes.Tag).map(_.value).containsAny { - Set(Tags.asthma, Tags.obeseBmi).map(_.value) - } - val res = eval(tagFacts)(q) - assert(res.output.value) - } - - "return the correct evidence for the facts that contain a subset of the given set" in { - val q = factsOfType(FactTypes.Tag).map(_.value).containsAny { - Set(Tags.asthma, Tags.obeseBmi).map(_.value) - } - val res = eval(tagFacts)(q) - pendingUntilFixed { - // TODO: Merge this assertion the above unit test when it passes - assertResult(Evidence(Tags.asthma))(res.output.evidence) - } - } - - "return 'false' with no Evidence when the facts do not contain anything in the given set" in { - val q = factsOfType(FactTypes.Tag).map(_.value).containsAny { - Set(Tags.obeseBmi).map(_.value) - } - val res = eval(tagFacts)(q) - assert(!res.output.value) - assert(res.output.evidence.isEmpty) - } - } - } - - "using with an 'OutputWithinRange' operator" should { - val low = FactTypes.Age(10) - val middle = FactTypes.Age(18) - val high = FactTypes.Age(85) - val numericFacts = FactTable(low, middle, high) - - "return all values that match the condition" in { - val q = factsOfType(FactTypes.Age).map(_.value).filter { - _ >= middle.value - } - val res = eval(numericFacts)(q) - res.output.value should contain theSameElementsAs Seq(middle, high).map(_.value) - } - - "return the correct evidence for the matching values from a given subset" in { - val q = factsOfType(FactTypes.Age).map(_.value).filter { - _ >= middle.value - } - val res = eval(numericFacts)(q) - pendingUntilFixed { - // TODO: Merge this assertion the above unit test when it passes - assertResult(Evidence(middle, high))(res.output.evidence) - } - } - - "return all facts that match the condition" in { - val q = factsOfType(FactTypes.Age).filter { - _.value >= middle.value - } - val res = eval(numericFacts)(q) - res.output.value should contain theSameElementsAs Seq(middle, high) - assertResult(Evidence(middle, high))(res.output.evidence) - } - - "return an empty list of values when none of the elements meet the condition" in { - val q = factsOfType(FactTypes.Age).map(_.value).filter { - _ > high.value - } - val res = eval(numericFacts)(q) - assert(res.output.value.isEmpty) - assert(res.output.evidence.isEmpty) - } - - "return an empty list of facts when none meet the condition" in { - val q = factsOfType(FactTypes.Age).filter { - _.value > high.value - } - val res = eval(numericFacts)(q) - assert(res.output.value.isEmpty) - assert(res.output.evidence.isEmpty) - } - } - } -} diff --git a/core/src/test/scala/vapors/interpreter/FoldOutputSpec.scala b/core/src/test/scala/vapors/interpreter/FoldOutputSpec.scala deleted file mode 100644 index 81252395b..000000000 --- a/core/src/test/scala/vapors/interpreter/FoldOutputSpec.scala +++ /dev/null @@ -1,32 +0,0 @@ -package com.rallyhealth - -package vapors.interpreter - -import vapors.data.FactTable -import vapors.dsl._ - -import org.scalatest.freespec.AnyFreeSpec - -class FoldOutputSpec extends AnyFreeSpec { - - "Expr.FoldOutput" - { - - "fold a list of ints into its sum" in { - val query = const(List(1, 2, 3)).withOutputFoldable.fold - val result = eval(FactTable.empty)(query) - assertResult(6)(result.output.value) - } - - "fold a list of options of int into an option containing the sum" in { - val query = const(List(Some(1), None, Some(3))).withOutputFoldable.fold - val result = eval(FactTable.empty)(query) - assertResult(Some(4))(result.output.value) - } - - "fold a list of options of int into a None" in { - val query = const(List[Option[Int]](None, None, None)).withOutputFoldable.fold - val result = eval(FactTable.empty)(query) - assertResult(None)(result.output.value) - } - } -} diff --git a/core/src/test/scala/vapors/interpreter/GroupOutputSpec.scala b/core/src/test/scala/vapors/interpreter/GroupOutputSpec.scala deleted file mode 100644 index 96bcf824e..000000000 --- a/core/src/test/scala/vapors/interpreter/GroupOutputSpec.scala +++ /dev/null @@ -1,23 +0,0 @@ -package com.rallyhealth - -package vapors.interpreter - -import vapors.dsl._ -import vapors.example.{FactTypes, User1} - -import org.scalatest.freespec.AnyFreeSpec - -class GroupOutputSpec extends AnyFreeSpec { - - "groupBy should" - { - - "create a map from a list using groupBy and mapValues" in { - val query = valuesOfType(FactTypes.TagsUpdate).groupBy(_.select(_.source)) - val expected = User1.factTable.getSortedSeq(FactTypes.TagsUpdate).groupMap(_.value.source)(_.value) - val result = eval(User1.factTable)(query) - assertResult(expected) { - result.output.value.toMap - } - } - } -} diff --git a/core/src/test/scala/vapors/interpreter/InterpretExprAsResultFnSpec.scala b/core/src/test/scala/vapors/interpreter/InterpretExprAsResultFnSpec.scala deleted file mode 100644 index b2b40b0a4..000000000 --- a/core/src/test/scala/vapors/interpreter/InterpretExprAsResultFnSpec.scala +++ /dev/null @@ -1,49 +0,0 @@ -package com.rallyhealth - -package vapors.interpreter - -import vapors.data.Evidence -import vapors.dsl._ -import vapors.example.{FactTypes, JoeSchmoe} - -import org.scalatest.wordspec.AnyWordSpec - -class InterpretExprAsResultFnSpec extends AnyWordSpec { - - "InterpretExprAsFunction" when { - - "using no post processing" should { - - "find a single fact from a query" in { - val q = valuesOfType(FactTypes.Age).exists { - _ >= 18 - } - val result = eval(JoeSchmoe.factTable)(q) - assert(result.param.value === ()) - assert(result.output.value) - assert(result.output.evidence.nonEmpty) - assertResult(Evidence(JoeSchmoe.age))(result.output.evidence) - } - - "find a complex fact from a query" in { - val q = valuesOfType(FactTypes.ProbabilityToUse).exists { - _.getFoldable(_.select(_.scores).at("weightloss")).exists { - _ > 0.5 - } - } - val result = eval(JoeSchmoe.factTable)(q) - assertResult(Evidence(JoeSchmoe.probs))(result.output.evidence) - } - - "define a fact expression" in { - val likelyToJoinWeightloss = valuesOfType(FactTypes.ProbabilityToUse).exists { - _.getFoldable(_.select(_.scores).at("weightloss")).exists { - _ > 0.5 - } - } - val result = eval(JoeSchmoe.factTable)(likelyToJoinWeightloss) - assertResult(Evidence(JoeSchmoe.probs))(result.output.evidence) - } - } - } -} diff --git a/core/src/test/scala/vapors/interpreter/LogicalExprSpec.scala b/core/src/test/scala/vapors/interpreter/LogicalExprSpec.scala deleted file mode 100644 index 3243e20ce..000000000 --- a/core/src/test/scala/vapors/interpreter/LogicalExprSpec.scala +++ /dev/null @@ -1,332 +0,0 @@ -package com.rallyhealth - -package vapors.interpreter - -import vapors.algebra.Expr -import vapors.data.{Evidence, ExtractBoolean, FactSet, FactTable} -import vapors.dsl._ -import vapors.example.JoeSchmoe -import vapors.logic._ - -import org.scalactic.source.Position -import org.scalatest.wordspec.AnyWordSpec - -class LogicalExprSpec extends AnyWordSpec { - - private type LogicExpr[R] = Expr[Unit, R, Unit] - - private type LogicOpBuilder[R] = - (LogicExpr[R], LogicExpr[R], Seq[LogicExpr[R]]) => LogicExpr[R] - - private type UnaryLogicOpBuilder[R] = LogicExpr[R] => LogicExpr[R] - - private def evalUnit[R](facts: FactSet)(expr: LogicExpr[R]): ExprOutput[R] = { - InterpretExprAsResultFn(expr)(ExprInput((), Evidence(facts), FactTable(facts))).output - } - - private def validLogicalOperators[R]( - andBuilder: LogicOpBuilder[R], - orBuilder: LogicOpBuilder[R], - notBuilder: UnaryLogicOpBuilder[R], - trueBuilder: LogicExpr[R], - falseBuilder: LogicExpr[R], - facts: FactSet, - assertTrue: Position => ExprOutput[R] => Unit, - assertFalse: Position => ExprOutput[R] => Unit, - ): Unit = { - - val T = trueBuilder - val F = falseBuilder - val not = notBuilder - - def and( - one: LogicExpr[R], - two: LogicExpr[R], - tail: LogicExpr[R]*, - ): LogicExpr[R] = andBuilder(one, two, tail) - - def or( - one: LogicExpr[R], - two: LogicExpr[R], - tail: LogicExpr[R]*, - ): LogicExpr[R] = orBuilder(one, two, tail) - - val evalOutput = evalUnit[R](facts)(_) - - def shouldBeTrue(output: ExprOutput[R])(implicit pos: Position): Unit = { - assertTrue(pos)(output) - } - - def shouldBeFalse(output: ExprOutput[R])(implicit pos: Position): Unit = { - assertFalse(pos)(output) - } - - "return 'false' for 'not true'" in { - val q = { - not(T) - } - shouldBeFalse(evalOutput(q)) - } - - "return 'true' for 'not false'" in { - val q = { - not(F) - } - shouldBeTrue(evalOutput(q)) - } - - "return 'true' for double negation of 'true'" in { - val q = { - not(not(T)) - } - shouldBeTrue(evalOutput(q)) - } - - "return 'false' for double negation of 'false'" in { - val q = { - not(not(F)) - } - shouldBeFalse(evalOutput(q)) - } - - "return 'true' for one 'true' and one 'false' in an 'or'" in { - val q = { - or(T, F) - } - shouldBeTrue(evalOutput(q)) - } - - "return 'true' for one 'false' and one 'not false' in an 'or'" in { - val q = { - or(F, not(F)) - } - shouldBeTrue(evalOutput(q)) - } - - "return 'false' for one 'false' and one 'not true' in an 'or'" in { - val q = { - or(F, not(T)) - } - shouldBeFalse(evalOutput(q)) - } - - "return 'true' for a complex 'or'" in { - val q = { - or(F, F, T, F) - } - shouldBeTrue(evalOutput(q)) - } - - "return 'false' for negating a complex 'or'" in { - val q = { - not(or(F, not(T), T, not(not(F)))) - } - shouldBeFalse(evalOutput(q)) - } - - "return 'false' for a long 'or'" in { - val q = { - or(F, F, F, F) - } - shouldBeFalse(evalOutput(q)) - } - - "return 'true' for negating a long 'or'" in { - val q = { - not(or(F, F, not(T), not(not(F)))) - } - shouldBeTrue(evalOutput(q)) - } - - "return 'false' for a 'true' and 'false' in an 'and'" in { - val q = { - and(T, F) - } - shouldBeFalse(evalOutput(q)) - } - - "return 'false' for a 'true' and 'not true' in an 'and'" in { - val q = { - and(T, not(T)) - } - shouldBeFalse(evalOutput(q)) - } - - "return 'true' for two trues in an and" in { - val q = { - and(T, T) - } - shouldBeTrue(evalOutput(q)) - } - - "return 'true' for two 'not false's in an 'and'" in { - val q = { - and(not(F), not(F)) - } - shouldBeTrue(evalOutput(q)) - } - - "return 'false' for a complex 'and'" in { - val q = { - and(T, T, F, T) - } - shouldBeFalse(evalOutput(q)) - } - - "return 'true' for the negation of a complex 'and'" in { - val q = { - not(and(T, not(F), F, not(not(T)))) - } - shouldBeTrue(evalOutput(q)) - } - - "return 'true' for a long 'and'" in { - val q = { - and(T, T, T, T) - } - shouldBeTrue(evalOutput(q)) - } - - "return 'false' for the negation of a long 'and'" in { - val q = { - not(and(T, not(F), not(not(T)), not(not(not(F))))) - } - shouldBeFalse(evalOutput(q)) - } - - "return true for nested true 'or's in an 'and'" in { - val q = { - and(or(T, T), or(T, T)) - } - shouldBeTrue(evalOutput(q)) - } - - "return 'true' for two nested 'not false or's in an 'and'" in { - val q = { - and(not(or(F, F)), not(or(F, F))) - } - shouldBeTrue(evalOutput(q)) - } - - "return 'false' for a nested 'false or' before some 'true' expressions" in { - val q = { - and(or(F, F), T, T) - } - shouldBeFalse(evalOutput(q)) - } - - "return 'true' for a nested 'not true or' before some 'true' expressions" in { - val q = { - and(not(or(F, T)), T, not(F)) - } - shouldBeFalse(evalOutput(q)) - } - - "return 'false' for a nested 'false or' after some 'true' expressions" in { - val q = { - and(T, or(T, F), or(F, F)) - } - shouldBeFalse(evalOutput(q)) - } - - "return 'false' for a nested 'not true or' after some 'true' expressions" in { - val q = { - and(T, not(F), or(not(T), T), or(F, not(T))) - } - shouldBeFalse(evalOutput(q)) - } - - "return 'true' for a nested 'false and' in an 'or'" in { - val q = { - or(F, and(F, F), and(T, T), and(T, F)) - } - shouldBeTrue(evalOutput(q)) - } - - "return 'false' for the negation of a nested 'false and' in an 'or'" in { - val q = { - not(or(F, and(F, F), not(and(T, T)), and(T, not(F), not(T)))) - } - shouldBeTrue(evalOutput(q)) - } - - "return 'true' for a complex structure" in { - val q = { - or(F, and(F, T), or(F, F), and(or(T, F), F), or(and(T, F), T)) - } - shouldBeTrue(evalOutput(q)) - } - - "return 'false' for a complex structure" in { - val q = { - not( - or( - F, - not(T), - and(F, T), - or(F, F), - not(and(T, T)), - not(or(T, F)), - not(not(F)), - and(or(T, F), F, not(T)), - or(F, not(T), and(T, F), not(and(T, not(F)))), - ), - ) - } - shouldBeTrue(evalOutput(q)) - } - } - - private abstract class DslLogicOpBuilder { - - def andBuilder[R : Conjunction : ExtractBoolean]: LogicOpBuilder[R] - - def orBuilder[R : Disjunction : ExtractBoolean]: LogicOpBuilder[R] - - def notBuilder[R : Negation]: UnaryLogicOpBuilder[R] - } - - def validDslLogicalOperators(builder: DslLogicOpBuilder): Unit = { - - "operating on boolean results" should { - - behave like validLogicalOperators[Boolean]( - builder.andBuilder, - builder.orBuilder, - builder.notBuilder, - trueBuilder = const(true), - falseBuilder = const(false), - facts = JoeSchmoe.facts, - assertTrue = { implicit pos => o => - assert(o.value) - // TODO: Is is sufficient to only test all the facts or no facts? - assertResult(Evidence(JoeSchmoe.facts.toList))(o.evidence) - }, - assertFalse = { implicit pos => o => - assert(!o.value) - assertResult(Evidence(JoeSchmoe.facts.toList))(o.evidence) - }, - ) - } - } - - "and / or" should { - - behave like validDslLogicalOperators { - new DslLogicOpBuilder { - - override def andBuilder[R : Conjunction : ExtractBoolean]: LogicOpBuilder[R] = { (one, two, tail) => - and(one, two, tail: _*) - } - - override def orBuilder[R : Disjunction : ExtractBoolean]: LogicOpBuilder[R] = { (one, two, tail) => - or(one, two, tail: _*) - } - - override def notBuilder[R : Negation]: UnaryLogicOpBuilder[R] = { - not(_) - } - } - } - } -} diff --git a/core/src/test/scala/vapors/interpreter/MultiplyOutputsSpec.scala b/core/src/test/scala/vapors/interpreter/MultiplyOutputsSpec.scala deleted file mode 100644 index 3975e4910..000000000 --- a/core/src/test/scala/vapors/interpreter/MultiplyOutputsSpec.scala +++ /dev/null @@ -1,61 +0,0 @@ -package com.rallyhealth - -package vapors.interpreter - -import vapors.dsl._ - -import org.scalatest.freespec.AnyFreeSpec - -class MultiplyOutputsSpec extends AnyFreeSpec { - - "Expr.MultiplyOutputs" - { - - "Int" - { - - "expression multiplied by an expression" in { - VaporsEvalTestHelpers.producesTheSameResultOrException[Int, Int, Int, ArithmeticException]( - _ * _, - const(_) * const(_), - ) - } - - "expression multiplied by a value" in { - VaporsEvalTestHelpers.producesTheSameResultOrException[Int, Int, Int, ArithmeticException]( - _ * _, - const(_) * _, - ) - } - - "value multiplied to an expression" in { - VaporsEvalTestHelpers.producesTheSameResultOrException[Int, Int, Int, ArithmeticException]( - _ * _, - const(_).multiplyTo(_), - ) - } - } - - "Double" - { - - "expression multiplied by an expression" in { - VaporsEvalTestHelpers.producesTheSameResultOrException[Double, Double, Double, ArithmeticException]( - _ * _, - const(_) * const(_), - ) - } - - "expression multiplied by a value" in { - VaporsEvalTestHelpers.producesTheSameResultOrException[Int, Int, Int, ArithmeticException]( - _ * _, - const(_) * _, - ) - } - - "value multiplied to an expression" in { - VaporsEvalTestHelpers.producesTheSameResultOrException[Int, Int, Int, ArithmeticException]( - _ * _, - const(_).multiplyTo(_), - ) - } - } - } -} diff --git a/core/src/test/scala/vapors/interpreter/OutputWithinSetExprSpec.scala b/core/src/test/scala/vapors/interpreter/OutputWithinSetExprSpec.scala deleted file mode 100644 index 0084cbab3..000000000 --- a/core/src/test/scala/vapors/interpreter/OutputWithinSetExprSpec.scala +++ /dev/null @@ -1,33 +0,0 @@ -package com.rallyhealth - -package vapors.interpreter - -import vapors.data.Evidence -import vapors.dsl._ -import vapors.example.{FactTypes, JoeSchmoe} - -import org.scalatest.wordspec.AnyWordSpec - -class OutputWithinSetExprSpec extends AnyWordSpec { - - "Expr.OutputWithinSet" should { - - "find an asthma tag in a set that contains it" in { - val q = valuesOfType(FactTypes.Tag).exists { - _ in Set("asthma", "diabetes") - } - val result = eval(JoeSchmoe.factTable)(q) - assert(result.output.value) - assertResult(Evidence(JoeSchmoe.asthmaTag))(result.output.evidence) - } - - "not find an asthma tag in a set that does not contain it" in { - val q = valuesOfType(FactTypes.Tag).exists { - _ in Set("diabetes") - } - val result = eval(JoeSchmoe.factTable)(q) - assert(!result.output.value) - assertResult(Evidence.none)(result.output.evidence) - } - } -} diff --git a/core/src/test/scala/vapors/interpreter/OutputWithinWindowSpec.scala b/core/src/test/scala/vapors/interpreter/OutputWithinWindowSpec.scala deleted file mode 100644 index 704bb7068..000000000 --- a/core/src/test/scala/vapors/interpreter/OutputWithinWindowSpec.scala +++ /dev/null @@ -1,44 +0,0 @@ -package com.rallyhealth - -package vapors.interpreter - -import vapors.data.FactTable -import vapors.dsl._ - -import org.scalatest.wordspec.AnyWordSpec - -class OutputWithinWindowSpec extends AnyWordSpec { - - "Expr.OutputWithinWindow" when { - - "using the === operator" should { - - "return 'true' when the values are equal" in { - val q = const(2 + 2) === 4 - val result = eval(FactTable.empty)(q) - assert(result.output.value) - } - - "return 'false' when the values are not equal" in { - val q = const(2 + 2) === 5 - val result = eval(FactTable.empty)(q) - assert(!result.output.value) - } - } - - "using the !== operator" should { - - "return 'false' when the values are equal" in { - val q = const(2 + 2) !== 4 - val result = eval(FactTable.empty)(q) - assert(!result.output.value) - } - - "return 'true' when the values are not equal" in { - val q = const(2 + 2) !== 5 - val result = eval(FactTable.empty)(q) - assert(result.output.value) - } - } - } -} diff --git a/core/src/test/scala/vapors/interpreter/SelectFromOutputSpec.scala b/core/src/test/scala/vapors/interpreter/SelectFromOutputSpec.scala deleted file mode 100644 index 967d02248..000000000 --- a/core/src/test/scala/vapors/interpreter/SelectFromOutputSpec.scala +++ /dev/null @@ -1,52 +0,0 @@ -package com.rallyhealth - -package vapors.interpreter - -import vapors.dsl._ -import vapors.example._ - -import cats.instances.order._ -import org.scalatest.freespec.AnyFreeSpec -import shapeless.Nat - -import scala.collection.View - -class SelectFromOutputSpec extends AnyFreeSpec { - - "create a map from a converting a list of facts to tuples" in { - val query = valuesOfType(FactTypes.TagsUpdate).map { update => - wrap( - update.get(_.select(_.source)).returnOutput, - update.get(_.select(_.tags)).returnOutput, - ).asTuple.withOutputValue - }.toMap - val expected = User1.factTable - .getSortedSeq(FactTypes.TagsUpdate) - .map { fact => - (fact.value.source, fact.value.tags) - } - .toMap - val result = eval(User1.factTable)(query) - assertResult(expected) { - result.output.value.toMap - } - } - - "create a set from a list using groupBy, flatMap, sorted, and headOption" in { - val query = factsOfType(FactTypes.TagsUpdate) - .groupBy(_.select(_.value.source)) - .flatMap { sourceAndFacts => - val facts = sourceAndFacts.getFoldable(_.at(Nat._1)) - val latestFactTags = facts.sorted.headOption.toSet.flatMap(_.getFoldable(_.select(_.value.tags))) - latestFactTags.to(View) - } - val expected = User1.factTable.getSortedSeq(FactTypes.TagsUpdate).groupBy(_.value.source).view.flatMap { - case (_, facts) => - facts.map(_.value).sorted.headOption.toList.flatMap(_.tags) - } - val result = eval(User1.factTable)(query) - assertResult(expected.toVector) { - result.output.value.toVector - } - } -} diff --git a/core/src/test/scala/vapors/interpreter/SortOutputSpec.scala b/core/src/test/scala/vapors/interpreter/SortOutputSpec.scala deleted file mode 100644 index 195a2634e..000000000 --- a/core/src/test/scala/vapors/interpreter/SortOutputSpec.scala +++ /dev/null @@ -1,54 +0,0 @@ -package com.rallyhealth - -package vapors.interpreter - -import vapors.data.FactTable -import vapors.dsl._ -import vapors.example.{BloodPressure, FactTypes} - -import org.scalatest.freespec.AnyFreeSpec - -import java.time.Instant - -class SortOutputSpec extends AnyFreeSpec { - - "Expr.SortOutput" - { - - val bpNow = Instant.now() - val bp5MinAgo = bpNow.minusSeconds(5 * 60) - val bp15MinAgo = bpNow.minusSeconds(15 * 60) - val lowDiastolic = BloodPressure(70, 100, bp15MinAgo) - val medDiastolic = BloodPressure(80, 100, bp5MinAgo) - val highDiastolic = BloodPressure(90, 100, bpNow) - - val bpFacts = FactTable( - Seq( - lowDiastolic, - medDiastolic, - highDiastolic, - ).map(FactTypes.BloodPressureMeasurement(_)), - ) - - "sorted using natural ordering" in { - val query = valuesOfType(FactTypes.BloodPressureMeasurement).map(_.get(_.select(_.diastolic))).sorted - assertResult(Some(highDiastolic)) { - bpFacts.getSortedSeq(FactTypes.BloodPressureMeasurement).headOption.map(_.value) - } - val result = eval(bpFacts)(query) - assertResult(lowDiastolic.diastolic) { - result.output.value.head - } - } - - "sortBy ordered field" in { - val query = valuesOfType(FactTypes.BloodPressureMeasurement).sortBy(_.select(_.diastolic)) - assertResult(Some(highDiastolic)) { - bpFacts.getSortedSeq(FactTypes.BloodPressureMeasurement).headOption.map(_.value) - } - val result = eval(bpFacts)(query) - assertResult(lowDiastolic) { - result.output.value.head - } - } - } -} diff --git a/core/src/test/scala/vapors/interpreter/SubtractOutputsSpec.scala b/core/src/test/scala/vapors/interpreter/SubtractOutputsSpec.scala deleted file mode 100644 index 1e11a1977..000000000 --- a/core/src/test/scala/vapors/interpreter/SubtractOutputsSpec.scala +++ /dev/null @@ -1,61 +0,0 @@ -package com.rallyhealth - -package vapors.interpreter - -import vapors.dsl._ - -import org.scalatest.freespec.AnyFreeSpec - -class SubtractOutputsSpec extends AnyFreeSpec { - - "Expr.SubtractOutputs" - { - - "Int" - { - - "expression subtracted by an expression" in { - VaporsEvalTestHelpers.producesTheSameResultOrException[Int, Int, Int, ArithmeticException]( - _ - _, - const(_) - const(_), - ) - } - - "expression subtracted by a value" in { - VaporsEvalTestHelpers.producesTheSameResultOrException[Int, Int, Int, ArithmeticException]( - _ - _, - const(_) - _, - ) - } - - "value subtracted by an expression" in { - VaporsEvalTestHelpers.producesTheSameResultOrException[Int, Int, Int, ArithmeticException]( - (a, b) => b - a, - const(_).subtractFrom(_), - ) - } - } - - "Double" - { - - "expression subtracted by an expression" in { - VaporsEvalTestHelpers.producesTheSameResultOrException[Double, Double, Double, ArithmeticException]( - _ - _, - const(_) - const(_), - ) - } - - "expression subtracted by a value" in { - VaporsEvalTestHelpers.producesTheSameResultOrException[Double, Double, Double, ArithmeticException]( - _ - _, - const(_) - _, - ) - } - - "value subtracted by an expression" in { - VaporsEvalTestHelpers.producesTheSameResultOrException[Double, Double, Double, ArithmeticException]( - (a, b) => b - a, - const(_).subtractFrom(_), - ) - } - } - } -} diff --git a/core/src/test/scala/vapors/interpreter/TakeFromOutputSpec.scala b/core/src/test/scala/vapors/interpreter/TakeFromOutputSpec.scala deleted file mode 100644 index 49f4c6360..000000000 --- a/core/src/test/scala/vapors/interpreter/TakeFromOutputSpec.scala +++ /dev/null @@ -1,87 +0,0 @@ -package com.rallyhealth - -package vapors.interpreter - -import vapors.data.FactTable -import vapors.dsl._ -import vapors.example.{FactTypes, TagsUpdate} - -import org.scalatest.wordspec.AnyWordSpec - -import java.time.Instant - -class TakeFromOutputSpec extends AnyWordSpec { - - "Expr.TakeFromOutput" when { - val now = Instant.now() - val updateABC = - FactTypes.TagsUpdate(TagsUpdate("ABC", Set("A", "B", "C"), now.minusSeconds(60 * 60 * 24))) - val updateDEF = FactTypes.TagsUpdate(TagsUpdate("DEF", Set("D", "E", "F"), now)) - - "using .take(n) with a positive number" should { - - "return an empty list if the collection is empty" in { - val q = factsOfType(FactTypes.TagsUpdate).take(1) - val res = eval(FactTable.empty)(q) - assert(res.output.value.isEmpty) - } - - "return the number of elements selected from the start of the list by fact ordering" in { - val q = factsOfType(FactTypes.TagsUpdate).take(1) - val res = eval(FactTable(updateABC, updateDEF))(q) - assertResult(Seq(updateDEF))(res.output.value) - } - - "return all the elements of the list by fact ordering when the number requested is greater than the size" in { - val q = factsOfType(FactTypes.TagsUpdate).take(3) - val res = eval(FactTable(updateABC, updateDEF))(q) - assertResult(Seq(updateDEF, updateABC))(res.output.value) - } - } - - "using .take(n) with a negative number" should { - - "return an empty list if the collection is empty" in { - val q = factsOfType(FactTypes.TagsUpdate).take(-1) - val res = eval(FactTable.empty)(q) - assert(res.output.value.isEmpty) - } - - "return the number of elements selected from the end of the list by fact ordering" in { - val q = factsOfType(FactTypes.TagsUpdate).take(-1) - val res = eval(FactTable(updateABC, updateDEF))(q) - assertResult(Seq(updateABC))(res.output.value) - } - - "return all the elements of the list by fact ordering when the number requested is greater than the size" in { - val q = factsOfType(FactTypes.TagsUpdate).take(-3) - val res = eval(FactTable(updateABC, updateDEF))(q) - assertResult(Seq(updateDEF, updateABC))(res.output.value) - } - } - - "using .take(n) with 0" should { - - "return an empty collection" in { - val q = factsOfType(FactTypes.TagsUpdate).take(0) - val res = eval(FactTable(updateABC, updateDEF))(q) - assertResult(Seq.empty)(res.output.value) - } - } - - "using .headOption" should { - - "return None if the collection is empty" in { - val q = factsOfType(FactTypes.TagsUpdate).headOption - val res = eval(FactTable.empty)(q) - assertResult(None)(res.output.value) - } - - "return the head of the collection by fact ordering" in { - val q = factsOfType(FactTypes.TagsUpdate).headOption - val res = eval(FactTable(updateABC, updateDEF))(q) - assertResult(Some(updateDEF))(res.output.value) - } - } - } -} diff --git a/core/src/test/scala/vapors/interpreter/TimeFunctionsSpec.scala b/core/src/test/scala/vapors/interpreter/TimeFunctionsSpec.scala deleted file mode 100644 index 3082080e2..000000000 --- a/core/src/test/scala/vapors/interpreter/TimeFunctionsSpec.scala +++ /dev/null @@ -1,230 +0,0 @@ -package com.rallyhealth - -package vapors.interpreter - -import vapors.data.FactTable -import vapors.dsl._ -import vapors.example.{FactTypes, Snippets} - -import org.scalacheck.Arbitrary.arbitrary -import org.scalacheck.ops._ -import org.scalacheck.{Arbitrary, Gen} -import org.scalatest.freespec.AnyFreeSpec - -import java.time._ -import java.time.temporal.ChronoUnit - -class TimeFunctionsSpec extends AnyFreeSpec { - import TimeFunctionsSpec._ - - "dateAdd" - { - - "Instant with" - { - - "Duration works the same as .plus" in { - VaporsEvalTestHelpers.producesTheSameResultOrException[Instant, Duration, Instant, DateTimeException]( - _.plus(_), - (t, d) => dateAdd(const(t), const(d)), - ) - } - - "Period fails to compile" in { - val now = Instant.now() - val period = Period.ofYears(1) - assertDoesNotCompile { - "dateAdd(const(now), const(period))" - } - assertThrows[DateTimeException] { - now.plus(period) - } - } - } - - "LocalDate with" - { - - "Duration fails to compile" in { - val today = LocalDate.now() - val duration = Duration.ofSeconds(10) - assertDoesNotCompile { - "dateAdd(const(today), const(duration))" - } - assertThrows[DateTimeException] { - today.plus(duration) - } - } - - "Period works the same as .plus" in { - VaporsEvalTestHelpers.producesTheSameResultOrException[LocalDate, Period, LocalDate, DateTimeException]( - _.plus(_), - (t, d) => dateAdd(const(t), const(d)), - ) - } - } - - "LocalDateTime with" - { - - "Duration works the same as .plus" in { - VaporsEvalTestHelpers - .producesTheSameResultOrException[LocalDateTime, Duration, LocalDateTime, DateTimeException]( - _.plus(_), - (t, d) => dateAdd(const(t), const(d)), - ) - } - - "Period works the same as .plus" in { - VaporsEvalTestHelpers - .producesTheSameResultOrException[LocalDateTime, Period, LocalDateTime, DateTimeException]( - _.plus(_), - (t, d) => dateAdd(const(t), const(d)), - ) - } - } - - "ZonedDateTime with" - { - - "Duration works the same as .plus" in { - VaporsEvalTestHelpers - .producesTheSameResultOrException[ZonedDateTime, Duration, ZonedDateTime, DateTimeException]( - _.plus(_), - (t, d) => dateAdd(const(t), const(d)), - ) - } - - "Period works the same as .plus" in { - VaporsEvalTestHelpers - .producesTheSameResultOrException[ZonedDateTime, Period, ZonedDateTime, DateTimeException]( - _.plus(_), - (t, d) => dateAdd(const(t), const(d)), - ) - } - } - } - - "dateDiff" - { - - "computing Age from DateOfBirth" - { - - val feb28Year2021 = LocalDate.of(2021, 2, 28) - val onFeb28Year2021 = new Snippets(feb28Year2021) - - // leap year - val feb29Year2020 = LocalDate.of(2020, 2, 29) - val onFeb29Year2020 = new Snippets(feb29Year2020) - - "rounds the year down" in { - val feb1Minus20Years = LocalDate.of(feb28Year2021.getYear - 20, 2, 1) - // validate that it works outside of vapors - assertResult(20) { - feb1Minus20Years.until(feb28Year2021, ChronoUnit.YEARS) - } - // validate that it works inside of vapors - val result = eval(FactTable(FactTypes.DateOfBirth(feb1Minus20Years))) { - onFeb28Year2021.ageFromDateOfBirth - } - assertResult(Seq(20)) { // Just turned 20 this month - result.output.value - } - } - - "rounds the year down, even when close" in { - val feb29Year2000 = feb28Year2021.minusYears(21).plusDays(1) - // validate that it works outside of vapors - assertResult(20) { - feb29Year2000.until(feb28Year2021, ChronoUnit.YEARS) - } - // validate that it works inside of vapors - val result = eval(FactTable(FactTypes.DateOfBirth(feb29Year2000))) { - onFeb28Year2021.ageFromDateOfBirth - } - assertResult(Seq(20)) { // Sorry, birthday is tomorrow - result.output.value - } - } - - "counts the current year on their birthday" in { - val feb28Year2000 = feb28Year2021.minusYears(21) - // validate that it works outside of vapors - assertResult(21) { - feb28Year2000.until(feb28Year2021, ChronoUnit.YEARS) - } - // validate that it works inside of vapors - val result = eval(FactTable(FactTypes.DateOfBirth(feb28Year2000))) { - onFeb28Year2021.ageFromDateOfBirth - } - assertResult(Seq(21)) { // 🎉 happy birthday! 🍻 - result.output.value - } - } - - "returns the correct number of years between leap years" in { - val feb29Year2000 = LocalDate.of(2000, 2, 29) - // validate that it works outside of vapors - assertResult(20) { - feb29Year2000.until(feb29Year2020, ChronoUnit.YEARS) - } - // validate that it works inside of vapors - val result = eval(FactTable(FactTypes.DateOfBirth(feb29Year2000))) { - new Snippets(feb29Year2020).ageFromDateOfBirth - } - assertResult(Seq(20)) { // leap years work as expected - result.output.value - } - } - - "returns the correct number of years on a leap year" in { - val mar1Year1999 = LocalDate.of(1999, 3, 1) - // validate that it works outside of vapors - assertResult(20) { - mar1Year1999.until(feb29Year2020, ChronoUnit.YEARS) - } - // validate that it works inside of vapors - val result = eval(FactTable(FactTypes.DateOfBirth(mar1Year1999))) { - onFeb29Year2020.ageFromDateOfBirth - } - assertResult(Seq(20)) { - result.output.value - } - } - - "returns the correct number of years from a leap year birthday" in { - val feb29Year2000 = LocalDate.of(2000, 2, 29) - // validate that it works outside of vapors - assertResult(20) { - feb29Year2000.until(feb28Year2021, ChronoUnit.YEARS) - } - // validate that it works inside of vapors - val result = eval(FactTable(FactTypes.DateOfBirth(feb29Year2000))) { - onFeb28Year2021.ageFromDateOfBirth - } - assertResult(Seq(20)) { - result.output.value - } - } - } - } -} - -object TimeFunctionsSpec { - - // TODO: Add to scalacheck-ops - - // $COVERAGE-OFF$ - implicit val arbChronoUnit: Arbitrary[ChronoUnit] = Arbitrary { - Gen.oneOf(ChronoUnit.values()) - } - - implicit val arbJDuration: Arbitrary[Duration] = Arbitrary { - for { - a <- arbitrary[Instant] - b <- arbitrary[Instant] - } yield Duration.between(a, b) - } - - implicit val arbJPeriod: Arbitrary[Period] = Arbitrary { - for { - a <- arbitrary[LocalDate] - b <- arbitrary[LocalDate] - } yield Period.between(a, b) - } - // $COVERAGE-ON$ -} diff --git a/core/src/test/scala/vapors/interpreter/UsingDefinitionsExprSpec.scala b/core/src/test/scala/vapors/interpreter/UsingDefinitionsExprSpec.scala deleted file mode 100644 index 66f727c4b..000000000 --- a/core/src/test/scala/vapors/interpreter/UsingDefinitionsExprSpec.scala +++ /dev/null @@ -1,65 +0,0 @@ -package com.rallyhealth - -package vapors.interpreter - -import vapors.data.{DerivedFactOfType, Evidence, FactTable} -import vapors.dsl._ -import vapors.example.{FactTypes, JoeSchmoe, Role, Snippets} - -import org.scalatest.matchers.should.Matchers._ -import org.scalatest.wordspec.AnyWordSpec - -import java.time.LocalDate - -class UsingDefinitionsExprSpec extends AnyWordSpec { - - "Expr.UsingDefinitions" should { - - "add the defined facts to the fact table" in { - val result = eval(JoeSchmoe.factTable) { - usingDefinitions(Snippets.ageFromDateOfBirthDef) { - valuesOfType(FactTypes.Age) - } - } - val ages = result.output.value - ages should contain only JoeSchmoe.age.value - } - - "return the result of an expression with the Definition-produced facts as DerivedFacts" in { - val yearWhen19 = LocalDate.now().getYear - 19 - val expiredAge = FactTypes.Age(17) - val dob = FactTypes.DateOfBirth(LocalDate.of(yearWhen19, 1, 1)) - val facts = FactTable(expiredAge, dob) - val result = eval(facts)(Snippets.isOver18) - assert(result.output.value) - val ageFromYear = LocalDate.now().getYear - dob.value.getYear - val expectedDerivedFact = DerivedFactOfType(FactTypes.Age, ageFromYear, Evidence(dob)) - assertResult(Evidence(expectedDerivedFact))(result.output.evidence) - assertResult(Evidence(dob))(result.output.evidence.derivedFromSources) - } - - "support nested fact definitions" in { - val yearWhen19 = LocalDate.now().getYear - 19 - val userRole = FactTypes.Role(Role.User) - val expiredAge = FactTypes.Age(17) - val dob = FactTypes.DateOfBirth(LocalDate.of(yearWhen19, 1, 1)) - val facts = FactTable(expiredAge, dob, userRole) - val definition = Snippets.isEligibleDef - val result = eval(facts) { - usingDefinitions(definition) { - factsOfType(definition.factType).exists { - _.get(_.select(_.value)) - } - } - } - assert(result.output.value) - val correctAge = DerivedFactOfType(FactTypes.Age, 19, Evidence(dob)) - val evidenceOfEligibility = Evidence( - DerivedFactOfType(definition.factType, true, Evidence(correctAge, userRole)), - ) - assertResult(evidenceOfEligibility)(result.output.evidence) - assertResult(Evidence(dob, userRole))(result.output.evidence.derivedFromSources) - } - - } -} diff --git a/core/src/test/scala/vapors/interpreter/VaporsEvalTestHelpers.scala b/core/src/test/scala/vapors/interpreter/VaporsEvalTestHelpers.scala deleted file mode 100644 index b2a9d067d..000000000 --- a/core/src/test/scala/vapors/interpreter/VaporsEvalTestHelpers.scala +++ /dev/null @@ -1,117 +0,0 @@ -package com.rallyhealth - -package vapors.interpreter - -import vapors.data.FactTable -import vapors.dsl.{eval, RootExpr} - -import org.scalacheck.Arbitrary -import org.scalactic.Tolerance._ -import org.scalactic.TripleEqualsSupport._ -import org.scalactic.source.Position -import org.scalatest.Assertion -import org.scalatest.Assertions._ -import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks._ - -import scala.Fractional.Implicits._ -import scala.reflect.ClassTag -import scala.util.Try - -object VaporsEvalTestHelpers { - - trait CompareEquality[E, A] { - - def assertEqual( - expected: E, - actual: A, - )(implicit - pos: Position, - ): Assertion - } - - object CompareEquality extends LowPriorityDefaultTolerance { - - def apply[E, A](build: Position => (E, A) => Assertion): CompareEquality[E, A] = new CompareEquality[E, A] { - override def assertEqual( - expected: E, - actual: A, - )(implicit - pos: Position, - ): Assertion = build(pos)(expected, actual) - } - - implicit def tolerantFractional[A : Fractional : DefaultTolerance]: CompareEquality[A, A] = CompareEquality { - implicit pos => (expected, actual) => - assert(DefaultTolerance[A].toSpread(expected) === actual) - } - - implicit def tolerantIntegral[A : Integral : DefaultTolerance]: CompareEquality[A, A] = CompareEquality { - implicit pos => (expected, actual) => - assert(DefaultTolerance[A].toSpread(expected) === actual) - } - } - - trait LowPriorityDefaultTolerance { - - implicit def zeroToleranceIntegral[A : Integral]: CompareEquality[A, A] = CompareEquality { - implicit pos => (expected, actual) => - assert(expected == actual) - } - } - - trait DefaultTolerance[A] { - def toSpread(value: A): Spread[A] - } - - object DefaultTolerance { - - @inline def apply[A : DefaultTolerance]: DefaultTolerance[A] = implicitly - - implicit def onePercentFractionalTolerance[A : Fractional]: DefaultTolerance[A] = { - val F = Fractional[A] - val onePercent = F.one / F.fromInt(100) - value => { - val onePercentOfValue = value * onePercent - value +- onePercentOfValue - } - } - } - - def producesTheSameResultOrException[A : Arbitrary, B : Arbitrary, R, E <: Throwable : ClassTag]( - evaluate: (A, B) => R, - buildExpr: (A, B) => RootExpr[R, Unit], - ): Assertion = { - forAll { (a: A, b: B) => - val query = buildExpr(a, b) - Try(evaluate(a, b)).fold( - { - case expectedExc: E => - val exc = intercept[E] { - eval(FactTable.empty)(query) - } - assertResult(expectedExc.getMessage) { - exc.getMessage - } - case unexpected => - fail("Unexpected exception", unexpected) - }, - expectedValue => { - val result = eval(FactTable.empty)(query) - expectedValue match { - case num: Double if num.isNaN => - result.output.value match { - case obs: Double => - assert(obs.isNaN, "Expected output to be NaN") - case _ => - fail("Expected output to be a Double") - } - case _ => - assertResult(expectedValue) { - result.output.value - } - } - }, - ) - } - } -} diff --git a/core/src/test/scala/vapors/interpreter/WhenExprSpec.scala b/core/src/test/scala/vapors/interpreter/WhenExprSpec.scala deleted file mode 100644 index 191fdf3cb..000000000 --- a/core/src/test/scala/vapors/interpreter/WhenExprSpec.scala +++ /dev/null @@ -1,87 +0,0 @@ -package com.rallyhealth - -package vapors.interpreter - -import vapors.data.FactTable -import vapors.dsl._ - -import org.scalatest.wordspec.AnyWordSpec - -class WhenExprSpec extends AnyWordSpec { - - // a set of ordered constants for comparison - private final val a = "A" - private final val b = "B" - private final val c = "C" - private final val d = "D" - - "Expr.When" when { - - "operating on constants" should { - - "return the first branch of an if / else when the first condition is 'true'" in { - val q = when(const(true)).thenReturn(const(a)).elseReturn(const(b)) - val res = eval(FactTable.empty)(q) - assertResult(a)(res.output.value) - } - - "return the second branch of an if / else when the first condition is 'false'" in { - val q = when(const(false)).thenReturn(const(a)).elseReturn(const(b)) - val res = eval(FactTable.empty)(q) - assertResult(b)(res.output.value) - } - - "return the first branch of a if / elif / else when the first condition is 'true' and the second condition is 'false'" in { - val q = when(const(true)) - .thenReturn(const(a)) - .elif(const(false)) - .thenReturn(const(b)) - .elseReturn(const(c)) - val res = eval(FactTable.empty)(q) - assertResult(a)(res.output.value) - } - - "return the first branch of a if / elif / else when the first condition is 'true' and the second condition is 'true'" in { - val q = when(const(true)) - .thenReturn(const(a)) - .elif(const(true)) - .thenReturn(const(b)) - .elseReturn(const(c)) - val res = eval(FactTable.empty)(q) - assertResult(a)(res.output.value) - } - - "return the second branch of a if / elif / else when the first condition is 'false' and the second condition is 'true'" in { - val q = when(const(false)) - .thenReturn(const(a)) - .elif(const(true)) - .thenReturn(const(b)) - .elseReturn(const(c)) - val res = eval(FactTable.empty)(q) - assertResult(b)(res.output.value) - } - - "return the last branch of a if / elif / else when all conditions are 'false'" in { - val q = when(const(false)) - .thenReturn(const(a)) - .elif(const(false)) - .thenReturn(const(b)) - .elseReturn(const(c)) - val res = eval(FactTable.empty)(q) - assertResult(c)(res.output.value) - } - - "return the third branch of a if / elif / elif / else when the first two conditions are 'false' and the third condition is 'true'" in { - val q = when(const(false)) - .thenReturn(const(a)) - .elif(const(false)) - .thenReturn(const(b)) - .elif(const(true)) - .thenReturn(const(c)) - .elseReturn(const(d)) - val res = eval(FactTable.empty)(q) - assertResult(c)(res.output.value) - } - } - } -} diff --git a/core/src/test/scala/vapors/interpreter/WrapOutputHListSpec.scala b/core/src/test/scala/vapors/interpreter/WrapOutputHListSpec.scala deleted file mode 100644 index 95dbde927..000000000 --- a/core/src/test/scala/vapors/interpreter/WrapOutputHListSpec.scala +++ /dev/null @@ -1,84 +0,0 @@ -package com.rallyhealth - -package vapors.interpreter - -import vapors.data.{Evidence, FactTable} -import vapors.dsl._ -import vapors.example.{ColorCoding, FactTypes, JoeSchmoe, TagsUpdate} - -import org.scalatest.freespec.AnyFreeSpec -import shapeless.HNil - -class WrapOutputHListSpec extends AnyFreeSpec { - - import vapors.example.SimpleTagUpdates._ - - "wrap, when given const nodes, should" - { - - "convert to a case class with the correct number of inputs" in { - val query = { - wrap(const(tagsNow.source), const(tagsNow.tags), const(tagsNow.timestamp)).as[TagsUpdate] - } - val result = eval(FactTable.empty)(query) - assertResult(tagsNow)(result.output.value) - } - - "NOT compile when attempting to convert fewer inputs than required into a case class" in { - assertDoesNotCompile { - "wrap(const(tagsNow.tags)).as[TagsUpdate]" - } - } - - "NOT compile when attempting to convert inputs into a case class in the wrong order" in { - assertDoesNotCompile { - "wrap(const(tagsNow.tags), const(tagsNow.timestamp), const(tagsNow.source)).as[TagsUpdate]" - } - } - - "convert into a tuple-3" in { - val t = (1, "two", ColorCoding.Blue) - val query = { - wrap(const(t._1), const(t._2), const(t._3)).asTuple - } - val result = eval(FactTable.empty)(query) - assertResult(t)(result.output.value) - } - - "convert into an hlist of size 3" in { - val hlist = 1 :: "two" :: ColorCoding.Blue :: HNil - val t = hlist.tupled - val query = { - wrap(const(t._1), const(t._2), const(t._3)).asHList - } - val result = eval(FactTable.empty)(query) - assertResult(hlist)(result.output.value) - } - } - - "wrap, when comparing evidence, should" - { - - "combine all non-empty evidence" in { - val query = { - wrap( - factsOfType(FactTypes.Name).headOption, - factsOfType(FactTypes.Age).headOption, - ).asHList - } - val facts = List(JoeSchmoe.name, JoeSchmoe.age) - val result = eval(FactTable(facts))(query) - assertResult(Evidence(facts))(result.output.evidence) - } - - "return no evidence if any branch has no evidence" in { - val query = { - wrap( - factsOfType(FactTypes.Name).headOption, - factsOfType(FactTypes.Age).headOption, - ).asHList - } - val result = eval(FactTable(JoeSchmoe.name))(query) - assertResult(Evidence.none)(result.output.evidence) - } - } - -} diff --git a/core/src/test/scala/vapors/interpreter/WrapOutputSeqSpec.scala b/core/src/test/scala/vapors/interpreter/WrapOutputSeqSpec.scala deleted file mode 100644 index 2ce3cc0d9..000000000 --- a/core/src/test/scala/vapors/interpreter/WrapOutputSeqSpec.scala +++ /dev/null @@ -1,64 +0,0 @@ -package com.rallyhealth - -package vapors.interpreter - -import vapors.data.{Evidence, FactTable, FactType} -import vapors.dsl._ -import vapors.example.{FactTypes, HasTimestamp, JoeSchmoe} - -import org.scalatest.Inside.inside -import org.scalatest.freespec.AnyFreeSpec - -class WrapOutputSeqSpec extends AnyFreeSpec { - - "wrapSeq / sequence should" - { - - "wrap a list of constant expressions into an expression of a list of the values" in { - val query = wrapSeq( - const(1), - const(2), - const(3), - ) - val result = eval(FactTable.empty)(query) - assertResult(Seq(1, 2, 3)) { - result.output.value - } - } - - "not force the resulting lazy list" in { - val query = wrapSeq( - const(1), - const(2), - const(3), - ) - val result = eval(FactTable.empty)(query) - inside(result.output.value) { - case values: LazyList[_] => - // confirm that the collection does not start with a definite size because it was unforced - assert(!values.hasDefiniteSize) - // confirm that forcing the collection causes it to have a definite size - assert(values.force.hasDefiniteSize) - } - } - - "wrap a list of expressions and combine the evidence from all of them" in { - val factTypes = List[FactType[_ <: HasTimestamp]]( - FactTypes.BloodPressureMeasurement, - FactTypes.WeightSelfReported, - FactTypes.TagsUpdate, - ) - val subExpressions = - factTypes.map(t => valuesOfType(t).map(_.get(_.select(_.timestamp))).returnOutput) - val query = sequence(subExpressions) - val factsPerType = factTypes.map(JoeSchmoe.factTable.getSortedSeq(_)) - val expectedValues = factsPerType.map(_.map(_.value.timestamp)) - val result = eval(JoeSchmoe.factTable)(query) - assertResult(expectedValues) { - result.output.value - } - assertResult(Evidence(factsPerType.flatten)) { - result.output.evidence - } - } - } -} diff --git a/core/src/test/scala/vapors/interpreter/ZipOutputSpec.scala b/core/src/test/scala/vapors/interpreter/ZipOutputSpec.scala deleted file mode 100644 index 329fc5212..000000000 --- a/core/src/test/scala/vapors/interpreter/ZipOutputSpec.scala +++ /dev/null @@ -1,148 +0,0 @@ -package com.rallyhealth - -package vapors.interpreter - -import vapors.data.FactTable -import vapors.dsl._ -import vapors.example.TagsUpdate - -import org.scalatest.freespec.AnyFreeSpec -import shapeless.HNil - -import java.time.Instant - -class ZipOutputSpec extends AnyFreeSpec { - - import vapors.example.SimpleTagUpdates._ - - "zippedToShortest should" - { - - "return a list as long as the shortest input list of" - { - - "tuple values" in { - val query = wrapEach( - const(List("A", "B", "C")), - const(List(1, 2, 3, 4, 5)), - ).zippedToShortest.asTuple - val result = eval(FactTable.empty)(query) - assertResult(List(("A", 1), ("B", 2), ("C", 3))) { - result.output.value - } - } - - "HList values" in { - val query = wrapEach( - const(List("A", "B", "C")), - const(List(1, 2, 3, 4, 5)), - ).zippedToShortest.asHList - val result = eval(FactTable.empty)(query) - assertResult(List("A" :: 1 :: HNil, "B" :: 2 :: HNil, "C" :: 3 :: HNil)) { - result.output.value - } - } - - "case class values" in { - val query = wrapEach( - const(List(tagsNow, tags5MinAgo, tags15MinAgo).map(_.source)), - const(List(tagsNow, tags5MinAgo).map(_.tags)), - const(List(tagsNow, tags5MinAgo, tags15MinAgo).map(_.timestamp)), - ).zippedToShortest.as[TagsUpdate] - val result = eval(FactTable.empty)(query) - assertResult(List(tagsNow, tags5MinAgo)) { - result.output.value - } - } - } - - "return nil when any input list is nil for" - { - - "tuple values" in { - val query = wrapEach( - const(List("A", "B", "C")), - const(List.empty[Int]), - ).zippedToShortest.asTuple - val result = eval(FactTable.empty)(query) - assertResult(Nil) { - result.output.value - } - } - - "HList values" in { - val query = wrapEach( - const(List.empty[String]), - const(List(1, 2, 3, 4, 5)), - ).zippedToShortest.asHList - val result = eval(FactTable.empty)(query) - assertResult(Nil) { - result.output.value - } - } - - "case class values" in { - val query = wrapEach( - const(List(tagsNow).map(_.source)), - const(List(tagsNow, tags5MinAgo).map(_.tags)), - const(List.empty[Instant]), - ).zippedToShortest.asHList - val result = eval(FactTable.empty)(query) - assertResult(Nil) { - result.output.value - } - } - } - - "not force a LazyList when" - { - - "taking the headOption" in { - val query = wrapEach( - const((tagsNow #:: fail("forced source") #:: LazyList.empty).map(_.source)), - const((tagsNow #:: fail("forced tags") #:: LazyList.empty).map(_.tags)), - const((tagsNow #:: fail("forced timestamp") #:: LazyList.empty).map(_.timestamp)), - ).zippedToShortest.as[TagsUpdate].withOutputFoldable.headOption - val result = eval(FactTable.empty)(query) - assertResult(Some(tagsNow)) { - result.output.value - } - } - } - - "not compile when" - { - - "given too many arguments for a case class" in { - assertDoesNotCompile { - """ - wrapEach( - const(List.empty[String]), - const(List.empty[Set[String]]), - const(List.empty[Instant]), - const(List.empty[Int]), // too many - ).zippedToShortest.as[TagsUpdate] - """ - } - } - - "given too few arguments for a case class" in { - assertDoesNotCompile { - """ - wrapEach( - const(List.empty[String]), - const(List.empty[Set[String]]), - ).zippedToShortest.as[TagsUpdate] - """ - } - } - - "given arguments out of order for a case class" in { - assertDoesNotCompile { - """ - wrapEach( - const(List.empty[Set[String]]), - const(List.empty[Instant]), - const(List.empty[String]), - ).zippedToShortest.as[TagsUpdate] - """ - } - } - } - } -} diff --git a/core/src/test/scala/vapors/lens/NamedLensSpec.scala b/core/src/test/scala/vapors/lens/NamedLensSpec.scala deleted file mode 100644 index 6bf2db69f..000000000 --- a/core/src/test/scala/vapors/lens/NamedLensSpec.scala +++ /dev/null @@ -1,85 +0,0 @@ -package com.rallyhealth - -package vapors.lens - -import vapors.data.TypedFact -import vapors.example.{AddressUpdate, BloodPressure, JoeSchmoe} - -import cats.data.Chain -import org.scalatest.wordspec.AnyWordSpec -import shapeless.{::, HNil, Nat} - -import java.time.LocalDate - -class NamedLensSpec extends AnyWordSpec { - - "NamedLens.at" should { - - "select a value from an HList using a Nat literal" in { - val lens = NamedLens.id[Double :: Int :: HNil].at(Nat._1) - val expected = 1 - assertResult(expected) { - lens.get(2.0 :: expected :: HNil) - } - } - - "fail to compile when using an index out of range" in { - assertDoesNotCompile { - "NamedLens.id[Double :: Int :: HNil].at(Nat._2)" - } - } - } - - "NamedLens.select" should { - - "select a field of a case class" in { - val lens = NamedLens.id[BloodPressure].select(_.diastolic) - assertResult(DataPath(Chain(DataPath.Field("diastolic"))))(lens.path) - assertResult(JoeSchmoe.bloodPressure.value.diastolic) { - lens.get(JoeSchmoe.bloodPressure.value) - } - } - - "select a field of a case class within another case class" in { - val lens = NamedLens.id[AddressUpdate].select(_.address.zip) - assertResult(DataPath(Chain(DataPath.Field("address"), DataPath.Field("zip"))))(lens.path) - assertResult(JoeSchmoe.lastAddressUpdate.value.address.zip) { - lens.get(JoeSchmoe.lastAddressUpdate.value) - } - } - - "select a field of a case class inside of a Fact" in { - val lens = NamedLens.id[TypedFact[BloodPressure]].select(_.value.diastolic) - assertResult(DataPath(Chain(DataPath.Field("value"), DataPath.Field("diastolic"))))(lens.path) - assertResult(JoeSchmoe.bloodPressure.value.diastolic) { - lens.get(JoeSchmoe.bloodPressure) - } - } - - "select a field of a case class within another case class inside of a fact" in { - val lens = NamedLens.id[TypedFact[AddressUpdate]].select(_.value.address.zip) - assertResult(DataPath(Chain(DataPath.Field("value"), DataPath.Field("address"), DataPath.Field("zip"))))( - lens.path, - ) - assertResult(JoeSchmoe.lastAddressUpdate.value.address.zip) { - lens.get(JoeSchmoe.lastAddressUpdate) - } - } - - "select a Java bean style 'get' method with the prefix removed" in { - val lens = NamedLens.id[LocalDate].select(_.getYear()) - assertResult(DataPath(Chain(DataPath.Field("year"))))(lens.path) - assertResult(JoeSchmoe.dateOfBirth.value.getYear) { - lens.get(JoeSchmoe.dateOfBirth.value) - } - } - - "select a Java bean style 'is' method without removing the prefix" in { - val lens = NamedLens.id[LocalDate].select(_.isLeapYear()) - assertResult(DataPath(Chain(DataPath.Field("isLeapYear"))))(lens.path) - assertResult(JoeSchmoe.dateOfBirth.value.isLeapYear) { - lens.get(JoeSchmoe.dateOfBirth.value) - } - } - } -} diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 899d38144..e39995822 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -16,7 +16,6 @@ object Dependencies { private final val shapelessVersion = "2.3.7" private final val sourcecodeVersion = "0.2.7" - private val alleyCatsCore = "org.typelevel" %% "alleycats-core" % catsVersion private val catsCore = "org.typelevel" %% "cats-core" % catsVersion private val circeCore = "io.circe" %% "circe-core" % circeVersion private val circeLiteral = "io.circe" %% "circe-literal" % circeVersion @@ -30,7 +29,6 @@ object Dependencies { private val scalactic = "org.scalactic" %% "scalactic" % scalacticVersion private val scalaTest = "org.scalatest" %% "scalatest" % scalaTestVersion private val scalaTestPlusScalaCheck = "org.scalatestplus" %% "scalacheck-1-15" % scalaTestPlusScalaCheckVersion - private def scalaReflect(scalacVersion: String): ModuleID = "org.scala-lang" % "scala-reflect" % scalacVersion private val shapeless = "com.chuusai" %% "shapeless" % shapelessVersion private val sourcecode = "com.lihaoyi" %% "sourcecode" % sourcecodeVersion @@ -41,26 +39,6 @@ object Dependencies { ).map(_ % Test) } - final object CoreProject { - - def all(scalaVersion: String): Seq[ModuleID] = - Seq( - alleyCatsCore, - catsCore, - scalactic, - scalaReflect(scalaVersion), - shapeless, - ) ++ Seq( - // Test-only dependencies - munit, - munitScalaCheck, - scalaCheck, - scalaCheckOps, - scalaTest, - scalaTestPlusScalaCheck, - ).map(_ % Test) - } - final object CoreV1Project { val all: Seq[ModuleID] =