Improved and/or, scalafmt, more tests
This commit is contained in:
parent
24fee4b4be
commit
c114da203e
8 changed files with 183 additions and 115 deletions
|
|
@ -88,4 +88,3 @@ lazy val api = project
|
|||
Deps.Gs.Uuid
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,29 +1,42 @@
|
|||
package gs.predicate.v0.api
|
||||
|
||||
import cats.syntax.all.*
|
||||
import cats.Applicative
|
||||
import cats.syntax.all.*
|
||||
import gs.predicate.v0.api.Predicate.Result.forall
|
||||
import gs.uuid.v0.UUID
|
||||
|
||||
/**
|
||||
* Implements logical AND.
|
||||
/** Implements logical AND.
|
||||
*
|
||||
* @param ps The predicates to evaluate.
|
||||
* @param ps
|
||||
* The predicates to evaluate.
|
||||
*/
|
||||
final class And[F[_]: Applicative, -A](
|
||||
val id: UUID,
|
||||
private val ps: List[Predicate[F, A]]
|
||||
) extends Predicate[F, A]:
|
||||
/** @inheritDocs */
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def eval(input: A): F[Predicate.Result] =
|
||||
ps.map(_.eval(input)).sequence.map(_.forall())
|
||||
|
||||
object And:
|
||||
|
||||
def apply[F[_]: Applicative, A](ps: Predicate[F, A]*): And[F, A] =
|
||||
new And(UUID.v7(), ps.toList)
|
||||
def empty[F[_]: Applicative]: Predicate[F, Any] = False[F]
|
||||
|
||||
def apply[F[_]: Applicative, A](id: UUID, ps: Predicate[F, A]*): And[F, A] =
|
||||
new And(id, ps.toList)
|
||||
def apply[F[_]: Applicative, A](ps: Predicate[F, A]*): Predicate[F, A] =
|
||||
ps.toList match
|
||||
case Nil => False[F]
|
||||
case p :: Nil => p
|
||||
case list => new And(UUID.v7(), list)
|
||||
|
||||
def apply[F[_]: Applicative, A](
|
||||
id: UUID,
|
||||
ps: Predicate[F, A]*
|
||||
): Predicate[F, A] =
|
||||
ps.toList match
|
||||
case Nil => False[F]
|
||||
case p :: Nil => p
|
||||
case list => new And(id, list)
|
||||
|
||||
end And
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
package gs.predicate.v0.api
|
||||
|
||||
import cats.Applicative
|
||||
|
||||
import gs.uuid.v0.UUID
|
||||
|
||||
/**
|
||||
* Always returns a miss.
|
||||
/** Always returns a miss.
|
||||
*/
|
||||
final class False[F[_]: Applicative](
|
||||
val id: UUID
|
||||
) extends Predicate[F, Any]:
|
||||
/** @inheritDocs */
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def eval(input: Any): F[Predicate.Result] =
|
||||
Applicative[F].pure(Predicate.Result.missed())
|
||||
|
||||
|
|
|
|||
|
|
@ -1,29 +1,42 @@
|
|||
package gs.predicate.v0.api
|
||||
|
||||
import cats.syntax.all.*
|
||||
import cats.Applicative
|
||||
import cats.syntax.all.*
|
||||
import gs.predicate.v0.api.Predicate.Result.forany
|
||||
import gs.uuid.v0.UUID
|
||||
|
||||
/**
|
||||
* Implements logical OR.
|
||||
/** Implements logical OR.
|
||||
*
|
||||
* @param ps The predicates to evaluate.
|
||||
* @param ps
|
||||
* The predicates to evaluate.
|
||||
*/
|
||||
final class Or[F[_]: Applicative, -A](
|
||||
val id: UUID,
|
||||
private val ps: List[Predicate[F, A]]
|
||||
) extends Predicate[F, A]:
|
||||
/** @inheritDocs */
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def eval(input: A): F[Predicate.Result] =
|
||||
ps.map(_.eval(input)).sequence.map(_.forany())
|
||||
|
||||
object Or:
|
||||
|
||||
def apply[F[_]: Applicative, A](ps: Predicate[F, A]*): Or[F, A] =
|
||||
new Or(UUID.v7(), ps.toList)
|
||||
def empty[F[_]: Applicative]: Predicate[F, Any] = False[F]
|
||||
|
||||
def apply[F[_]: Applicative, A](id: UUID, ps: Predicate[F, A]*): Or[F, A] =
|
||||
new Or(id, ps.toList)
|
||||
def apply[F[_]: Applicative, A](ps: Predicate[F, A]*): Predicate[F, A] =
|
||||
ps.toList match
|
||||
case Nil => False[F]
|
||||
case p :: Nil => p
|
||||
case list => new Or(UUID.v7(), list)
|
||||
|
||||
def apply[F[_]: Applicative, A](
|
||||
id: UUID,
|
||||
ps: Predicate[F, A]*
|
||||
): Predicate[F, A] =
|
||||
ps.toList match
|
||||
case Nil => False[F]
|
||||
case p :: Nil => p
|
||||
case list => new Or(id, list)
|
||||
|
||||
end Or
|
||||
|
|
|
|||
|
|
@ -2,119 +2,125 @@ package gs.predicate.v0.api
|
|||
|
||||
import gs.uuid.v0.UUID
|
||||
|
||||
/**
|
||||
* A _Predicate_ is some function that accepts any input and emits some
|
||||
* [[Predicate.Result]] (whether the predicate matched).
|
||||
*
|
||||
* Predicates evaluate input to see if the predicate matches that input.
|
||||
*/
|
||||
/** A _Predicate_ is some function that accepts any input and emits some
|
||||
* [[Predicate.Result]] (whether the predicate matched).
|
||||
*
|
||||
* Predicates evaluate input to see if the predicate matches that input.
|
||||
*/
|
||||
trait Predicate[F[_], -A]:
|
||||
/**
|
||||
* @return The unique identifier of this Predicate.
|
||||
*/
|
||||
/** @return
|
||||
* The unique identifier of this Predicate.
|
||||
*/
|
||||
def id: UUID
|
||||
|
||||
/**
|
||||
* Evaluate this predicate against the given input.
|
||||
*
|
||||
* @param input The input to evaluate this predicate against.
|
||||
* @return Some [[Predicate.Result]] that describes whether the input matched the predicate.
|
||||
*/
|
||||
/** Evaluate this predicate against the given input.
|
||||
*
|
||||
* @param input
|
||||
* The input to evaluate this predicate against.
|
||||
* @return
|
||||
* Some [[Predicate.Result]] that describes whether the input matched the
|
||||
* predicate.
|
||||
*/
|
||||
def eval(input: A): F[Predicate.Result]
|
||||
|
||||
/**
|
||||
* Predicate equality is based on the _unique identifier_. Two predicates with
|
||||
* the same ID are considered equal.
|
||||
*
|
||||
* @param that The other object.
|
||||
* @return True if the other object is a predicate with the same ID, false otherwise.
|
||||
*/
|
||||
override def equals(that: Any): Boolean =
|
||||
/** Predicate equality is based on the _unique identifier_. Two predicates
|
||||
* with the same ID are considered equal.
|
||||
*
|
||||
* @param that
|
||||
* The other object.
|
||||
* @return
|
||||
* True if the other object is a predicate with the same ID, false
|
||||
* otherwise.
|
||||
*/
|
||||
override def equals(that: Any): Boolean =
|
||||
that match
|
||||
case p: Predicate[?, ?] => p.id == id
|
||||
case _ => false
|
||||
case _ => false
|
||||
|
||||
/**
|
||||
* @return The hash code of the unique identifier.
|
||||
*/
|
||||
/** @return
|
||||
* The hash code of the unique identifier.
|
||||
*/
|
||||
override def hashCode(): Int = id.hashCode()
|
||||
|
||||
/** @inheritDocs */
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def toString(): String = s"predicate:${id.withoutDashes()}"
|
||||
|
||||
object Predicate:
|
||||
|
||||
/**
|
||||
* The result of evaluating a [[Predicate]] is a Boolean value where:
|
||||
*
|
||||
* - `true`: The predicate matched the given input.
|
||||
* - `false`: The predicate missed (did not match) the given input.
|
||||
*/
|
||||
/** The result of evaluating a [[Predicate]] is a Boolean value where:
|
||||
*
|
||||
* - `true`: The predicate matched the given input.
|
||||
* - `false`: The predicate missed (did not match) the given input.
|
||||
*/
|
||||
opaque type Result = Boolean
|
||||
|
||||
object Result:
|
||||
|
||||
/**
|
||||
* @return A result indicating a predicate matched (`true`).
|
||||
*/
|
||||
/** @return
|
||||
* A result indicating a predicate matched (`true`).
|
||||
*/
|
||||
def matched(): Result = true
|
||||
|
||||
/**
|
||||
* @return A result indicating a predicate missed (`false`).
|
||||
*/
|
||||
/** @return
|
||||
* A result indicating a predicate missed (`false`).
|
||||
*/
|
||||
def missed(): Result = false
|
||||
|
||||
/**
|
||||
* Instantiate a new [[Predicate.Result]] from the given value.
|
||||
*
|
||||
* @param value The underlying `Boolean` value.
|
||||
* @return The new predicate result.
|
||||
*/
|
||||
/** Instantiate a new [[Predicate.Result]] from the given value.
|
||||
*
|
||||
* @param value
|
||||
* The underlying `Boolean` value.
|
||||
* @return
|
||||
* The new predicate result.
|
||||
*/
|
||||
def apply(value: Boolean): Result = value
|
||||
|
||||
given CanEqual[Result, Result] = CanEqual.derived
|
||||
|
||||
extension (results: List[Result])
|
||||
/**
|
||||
* @return True if all results are true.
|
||||
*/
|
||||
/** @return
|
||||
* True if all results are true.
|
||||
*/
|
||||
def forall(): Result = results.forall(x => x)
|
||||
|
||||
/**
|
||||
* @return True if any results match.
|
||||
*/
|
||||
/** @return
|
||||
* True if any results match.
|
||||
*/
|
||||
def forany(): Result = results.find(x => x).isDefined
|
||||
|
||||
extension (result: Result)
|
||||
/**
|
||||
* @return The underlying value.
|
||||
*/
|
||||
/** @return
|
||||
* The underlying value.
|
||||
*/
|
||||
def unwrap(): Boolean = result
|
||||
|
||||
/**
|
||||
* Logical AND operation.
|
||||
*
|
||||
* @param other The other result.
|
||||
* @return True if both results match. False otherwise.
|
||||
*/
|
||||
/** Logical AND operation.
|
||||
*
|
||||
* @param other
|
||||
* The other result.
|
||||
* @return
|
||||
* True if both results match. False otherwise.
|
||||
*/
|
||||
def and(other: Result): Result = result && other
|
||||
|
||||
/**
|
||||
* Logical OR operation.
|
||||
*
|
||||
* @param other The other result.
|
||||
* @return True if either result matches. False otherwise.
|
||||
*/
|
||||
/** Logical OR operation.
|
||||
*
|
||||
* @param other
|
||||
* The other result.
|
||||
* @return
|
||||
* True if either result matches. False otherwise.
|
||||
*/
|
||||
def or(other: Result): Result = result || other
|
||||
|
||||
/**
|
||||
* @return True if this result is a match. False otherwise.
|
||||
*/
|
||||
/** @return
|
||||
* True if this result is a match. False otherwise.
|
||||
*/
|
||||
def isMatch: Boolean = result
|
||||
|
||||
/**
|
||||
* @return True if this result is a miss. False otherwise.
|
||||
*/
|
||||
/** @return
|
||||
* True if this result is a miss. False otherwise.
|
||||
*/
|
||||
def isMiss: Boolean = !result
|
||||
|
||||
end Result
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
package gs.predicate.v0.api
|
||||
|
||||
import cats.Applicative
|
||||
|
||||
import gs.uuid.v0.UUID
|
||||
|
||||
/**
|
||||
* Always returns a match.
|
||||
/** Always returns a match.
|
||||
*/
|
||||
final class True[F[_]: Applicative](
|
||||
val id: UUID
|
||||
) extends Predicate[F, Any]:
|
||||
/** @inheritDocs */
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def eval(input: Any): F[Predicate.Result] =
|
||||
Applicative[F].pure(Predicate.Result.matched())
|
||||
|
||||
|
|
|
|||
|
|
@ -1,27 +1,32 @@
|
|||
package gs.predicate.v0.api
|
||||
|
||||
import cats.effect.IO
|
||||
import support.IOSuite
|
||||
|
||||
import cats.effect.IO
|
||||
|
||||
import gs.predicate.v0.api.And
|
||||
|
||||
class AndTests extends IOSuite:
|
||||
|
||||
iotest("should return true if all are true") {
|
||||
val and: And[IO, Any] = And(
|
||||
True[IO], True[IO], True[IO]
|
||||
)
|
||||
and.eval(()).map(_.unwrap())
|
||||
val and = And(True[IO], True[IO], True[IO])
|
||||
|
||||
for result <- and.eval(())
|
||||
yield assertEquals(result.unwrap(), true)
|
||||
}
|
||||
|
||||
iotest("should return false if any are false") {
|
||||
val and: And[IO, Any] = And(
|
||||
True[IO], False[IO], True[IO]
|
||||
)
|
||||
and.eval(()).map(x => !x.unwrap())
|
||||
val and = And(True[IO], False[IO], True[IO])
|
||||
|
||||
for result <- and.eval(())
|
||||
yield assertEquals(result.unwrap(), false)
|
||||
}
|
||||
|
||||
iotest("should return true for an empty list") {
|
||||
val and: And[IO, Any] = And()
|
||||
and.eval(()).map(_.unwrap())
|
||||
iotest("should return false for an empty list") {
|
||||
val and = And.empty[IO]
|
||||
val and2 = And[IO, Any]()
|
||||
|
||||
for
|
||||
result <- and.eval(())
|
||||
result2 <- and2.eval(())
|
||||
yield
|
||||
assertEquals(result.unwrap(), false)
|
||||
assertEquals(result2.unwrap(), false)
|
||||
}
|
||||
|
|
|
|||
32
modules/api/src/test/scala/gs/predicate/v0/api/OrTests.scala
Normal file
32
modules/api/src/test/scala/gs/predicate/v0/api/OrTests.scala
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package gs.predicate.v0.api
|
||||
|
||||
import cats.effect.IO
|
||||
import support.IOSuite
|
||||
|
||||
class OrTests extends IOSuite:
|
||||
|
||||
iotest("should return true if any are true") {
|
||||
val or = Or(False[IO], True[IO], False[IO])
|
||||
|
||||
for result <- or.eval(())
|
||||
yield assertEquals(result.unwrap(), true)
|
||||
}
|
||||
|
||||
iotest("should return false if all are false") {
|
||||
val or = Or(False[IO], False[IO], False[IO])
|
||||
|
||||
for result <- or.eval(())
|
||||
yield assertEquals(result.unwrap(), false)
|
||||
}
|
||||
|
||||
iotest("should return false for an empty list") {
|
||||
val or = Or.empty[IO]
|
||||
val or2 = Or[IO, Any]()
|
||||
|
||||
for
|
||||
result <- or.eval(())
|
||||
result2 <- or2.eval(())
|
||||
yield
|
||||
assertEquals(result.unwrap(), false)
|
||||
assertEquals(result2.unwrap(), false)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue