Improved and/or, scalafmt, more tests

This commit is contained in:
Pat Garrity 2025-11-03 09:14:15 -06:00
parent 24fee4b4be
commit c114da203e
Signed by: pfm
GPG key ID: 5CA5D21BAB7F3A76
8 changed files with 183 additions and 115 deletions

View file

@ -88,4 +88,3 @@ lazy val api = project
Deps.Gs.Uuid
)
)

View file

@ -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

View file

@ -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())

View file

@ -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

View file

@ -2,50 +2,53 @@ package gs.predicate.v0.api
import gs.uuid.v0.UUID
/**
* A _Predicate_ is some function that accepts any input and emits some
/** 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.
/** 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.
* @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.
/** 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.
* @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
/**
* @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:
/** 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.
@ -54,66 +57,69 @@ object Predicate:
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.
/** Instantiate a new [[Predicate.Result]] from the given value.
*
* @param value The underlying `Boolean` value.
* @return The new predicate result.
* @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.
/** Logical AND operation.
*
* @param other The other result.
* @return True if both results match. False otherwise.
* @param other
* The other result.
* @return
* True if both results match. False otherwise.
*/
def and(other: Result): Result = result && other
/**
* Logical OR operation.
/** Logical OR operation.
*
* @param other The other result.
* @return True if either result matches. False otherwise.
* @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

View file

@ -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())

View file

@ -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)
}

View 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)
}