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
|
Deps.Gs.Uuid
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,42 @@
|
||||||
package gs.predicate.v0.api
|
package gs.predicate.v0.api
|
||||||
|
|
||||||
import cats.syntax.all.*
|
|
||||||
import cats.Applicative
|
import cats.Applicative
|
||||||
|
import cats.syntax.all.*
|
||||||
import gs.predicate.v0.api.Predicate.Result.forall
|
import gs.predicate.v0.api.Predicate.Result.forall
|
||||||
import gs.uuid.v0.UUID
|
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](
|
final class And[F[_]: Applicative, -A](
|
||||||
val id: UUID,
|
val id: UUID,
|
||||||
private val ps: List[Predicate[F, A]]
|
private val ps: List[Predicate[F, A]]
|
||||||
) extends Predicate[F, A]:
|
) extends Predicate[F, A]:
|
||||||
/** @inheritDocs */
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
override def eval(input: A): F[Predicate.Result] =
|
override def eval(input: A): F[Predicate.Result] =
|
||||||
ps.map(_.eval(input)).sequence.map(_.forall())
|
ps.map(_.eval(input)).sequence.map(_.forall())
|
||||||
|
|
||||||
object And:
|
object And:
|
||||||
|
|
||||||
def apply[F[_]: Applicative, A](ps: Predicate[F, A]*): And[F, A] =
|
def empty[F[_]: Applicative]: Predicate[F, Any] = False[F]
|
||||||
new And(UUID.v7(), ps.toList)
|
|
||||||
|
|
||||||
def apply[F[_]: Applicative, A](id: UUID, ps: Predicate[F, A]*): And[F, A] =
|
def apply[F[_]: Applicative, A](ps: Predicate[F, A]*): Predicate[F, A] =
|
||||||
new And(id, ps.toList)
|
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
|
end And
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
package gs.predicate.v0.api
|
package gs.predicate.v0.api
|
||||||
|
|
||||||
import cats.Applicative
|
import cats.Applicative
|
||||||
|
|
||||||
import gs.uuid.v0.UUID
|
import gs.uuid.v0.UUID
|
||||||
|
|
||||||
/**
|
/** Always returns a miss.
|
||||||
* Always returns a miss.
|
|
||||||
*/
|
*/
|
||||||
final class False[F[_]: Applicative](
|
final class False[F[_]: Applicative](
|
||||||
val id: UUID
|
val id: UUID
|
||||||
) extends Predicate[F, Any]:
|
) extends Predicate[F, Any]:
|
||||||
/** @inheritDocs */
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
override def eval(input: Any): F[Predicate.Result] =
|
override def eval(input: Any): F[Predicate.Result] =
|
||||||
Applicative[F].pure(Predicate.Result.missed())
|
Applicative[F].pure(Predicate.Result.missed())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,42 @@
|
||||||
package gs.predicate.v0.api
|
package gs.predicate.v0.api
|
||||||
|
|
||||||
import cats.syntax.all.*
|
|
||||||
import cats.Applicative
|
import cats.Applicative
|
||||||
|
import cats.syntax.all.*
|
||||||
import gs.predicate.v0.api.Predicate.Result.forany
|
import gs.predicate.v0.api.Predicate.Result.forany
|
||||||
import gs.uuid.v0.UUID
|
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](
|
final class Or[F[_]: Applicative, -A](
|
||||||
val id: UUID,
|
val id: UUID,
|
||||||
private val ps: List[Predicate[F, A]]
|
private val ps: List[Predicate[F, A]]
|
||||||
) extends Predicate[F, A]:
|
) extends Predicate[F, A]:
|
||||||
/** @inheritDocs */
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
override def eval(input: A): F[Predicate.Result] =
|
override def eval(input: A): F[Predicate.Result] =
|
||||||
ps.map(_.eval(input)).sequence.map(_.forany())
|
ps.map(_.eval(input)).sequence.map(_.forany())
|
||||||
|
|
||||||
object Or:
|
object Or:
|
||||||
|
|
||||||
def apply[F[_]: Applicative, A](ps: Predicate[F, A]*): Or[F, A] =
|
def empty[F[_]: Applicative]: Predicate[F, Any] = False[F]
|
||||||
new Or(UUID.v7(), ps.toList)
|
|
||||||
|
|
||||||
def apply[F[_]: Applicative, A](id: UUID, ps: Predicate[F, A]*): Or[F, A] =
|
def apply[F[_]: Applicative, A](ps: Predicate[F, A]*): Predicate[F, A] =
|
||||||
new Or(id, ps.toList)
|
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
|
end Or
|
||||||
|
|
|
||||||
|
|
@ -2,119 +2,125 @@ package gs.predicate.v0.api
|
||||||
|
|
||||||
import gs.uuid.v0.UUID
|
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).
|
||||||
* [[Predicate.Result]] (whether the predicate matched).
|
*
|
||||||
*
|
* Predicates evaluate input to see if the predicate matches that input.
|
||||||
* Predicates evaluate input to see if the predicate matches that input.
|
*/
|
||||||
*/
|
|
||||||
trait Predicate[F[_], -A]:
|
trait Predicate[F[_], -A]:
|
||||||
/**
|
/** @return
|
||||||
* @return The unique identifier of this Predicate.
|
* The unique identifier of this Predicate.
|
||||||
*/
|
*/
|
||||||
def id: UUID
|
def id: UUID
|
||||||
|
|
||||||
/**
|
/** Evaluate this predicate against the given input.
|
||||||
* Evaluate this predicate against the given input.
|
*
|
||||||
*
|
* @param input
|
||||||
* @param input The input to evaluate this predicate against.
|
* The input to evaluate this predicate against.
|
||||||
* @return Some [[Predicate.Result]] that describes whether the input matched the predicate.
|
* @return
|
||||||
*/
|
* Some [[Predicate.Result]] that describes whether the input matched the
|
||||||
|
* predicate.
|
||||||
|
*/
|
||||||
def eval(input: A): F[Predicate.Result]
|
def eval(input: A): F[Predicate.Result]
|
||||||
|
|
||||||
/**
|
/** Predicate equality is based on the _unique identifier_. Two predicates
|
||||||
* Predicate equality is based on the _unique identifier_. Two predicates with
|
* with the same ID are considered equal.
|
||||||
* the same ID are considered equal.
|
*
|
||||||
*
|
* @param that
|
||||||
* @param that The other object.
|
* The other object.
|
||||||
* @return True if the other object is a predicate with the same ID, false otherwise.
|
* @return
|
||||||
*/
|
* True if the other object is a predicate with the same ID, false
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
override def equals(that: Any): Boolean =
|
override def equals(that: Any): Boolean =
|
||||||
that match
|
that match
|
||||||
case p: Predicate[?, ?] => p.id == id
|
case p: Predicate[?, ?] => p.id == id
|
||||||
case _ => false
|
case _ => false
|
||||||
|
|
||||||
/**
|
/** @return
|
||||||
* @return The hash code of the unique identifier.
|
* The hash code of the unique identifier.
|
||||||
*/
|
*/
|
||||||
override def hashCode(): Int = id.hashCode()
|
override def hashCode(): Int = id.hashCode()
|
||||||
|
|
||||||
/** @inheritDocs */
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
override def toString(): String = s"predicate:${id.withoutDashes()}"
|
override def toString(): String = s"predicate:${id.withoutDashes()}"
|
||||||
|
|
||||||
object Predicate:
|
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.
|
||||||
* - `true`: The predicate matched the given input.
|
* - `false`: The predicate missed (did not match) the given input.
|
||||||
* - `false`: The predicate missed (did not match) the given input.
|
*/
|
||||||
*/
|
|
||||||
opaque type Result = Boolean
|
opaque type Result = Boolean
|
||||||
|
|
||||||
object Result:
|
object Result:
|
||||||
|
|
||||||
/**
|
/** @return
|
||||||
* @return A result indicating a predicate matched (`true`).
|
* A result indicating a predicate matched (`true`).
|
||||||
*/
|
*/
|
||||||
def matched(): Result = true
|
def matched(): Result = true
|
||||||
|
|
||||||
/**
|
/** @return
|
||||||
* @return A result indicating a predicate missed (`false`).
|
* A result indicating a predicate missed (`false`).
|
||||||
*/
|
*/
|
||||||
def missed(): Result = 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
|
||||||
* @param value The underlying `Boolean` value.
|
* The underlying `Boolean` value.
|
||||||
* @return The new predicate result.
|
* @return
|
||||||
*/
|
* The new predicate result.
|
||||||
|
*/
|
||||||
def apply(value: Boolean): Result = value
|
def apply(value: Boolean): Result = value
|
||||||
|
|
||||||
given CanEqual[Result, Result] = CanEqual.derived
|
given CanEqual[Result, Result] = CanEqual.derived
|
||||||
|
|
||||||
extension (results: List[Result])
|
extension (results: List[Result])
|
||||||
/**
|
/** @return
|
||||||
* @return True if all results are true.
|
* True if all results are true.
|
||||||
*/
|
*/
|
||||||
def forall(): Result = results.forall(x => x)
|
def forall(): Result = results.forall(x => x)
|
||||||
|
|
||||||
/**
|
/** @return
|
||||||
* @return True if any results match.
|
* True if any results match.
|
||||||
*/
|
*/
|
||||||
def forany(): Result = results.find(x => x).isDefined
|
def forany(): Result = results.find(x => x).isDefined
|
||||||
|
|
||||||
extension (result: Result)
|
extension (result: Result)
|
||||||
/**
|
/** @return
|
||||||
* @return The underlying value.
|
* The underlying value.
|
||||||
*/
|
*/
|
||||||
def unwrap(): Boolean = result
|
def unwrap(): Boolean = result
|
||||||
|
|
||||||
/**
|
/** Logical AND operation.
|
||||||
* Logical AND operation.
|
*
|
||||||
*
|
* @param other
|
||||||
* @param other The other result.
|
* The other result.
|
||||||
* @return True if both results match. False otherwise.
|
* @return
|
||||||
*/
|
* True if both results match. False otherwise.
|
||||||
|
*/
|
||||||
def and(other: Result): Result = result && other
|
def and(other: Result): Result = result && other
|
||||||
|
|
||||||
/**
|
/** Logical OR operation.
|
||||||
* Logical OR operation.
|
*
|
||||||
*
|
* @param other
|
||||||
* @param other The other result.
|
* The other result.
|
||||||
* @return True if either result matches. False otherwise.
|
* @return
|
||||||
*/
|
* True if either result matches. False otherwise.
|
||||||
|
*/
|
||||||
def or(other: Result): Result = result || other
|
def or(other: Result): Result = result || other
|
||||||
|
|
||||||
/**
|
/** @return
|
||||||
* @return True if this result is a match. False otherwise.
|
* True if this result is a match. False otherwise.
|
||||||
*/
|
*/
|
||||||
def isMatch: Boolean = result
|
def isMatch: Boolean = result
|
||||||
|
|
||||||
/**
|
/** @return
|
||||||
* @return True if this result is a miss. False otherwise.
|
* True if this result is a miss. False otherwise.
|
||||||
*/
|
*/
|
||||||
def isMiss: Boolean = !result
|
def isMiss: Boolean = !result
|
||||||
|
|
||||||
end Result
|
end Result
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
package gs.predicate.v0.api
|
package gs.predicate.v0.api
|
||||||
|
|
||||||
import cats.Applicative
|
import cats.Applicative
|
||||||
|
|
||||||
import gs.uuid.v0.UUID
|
import gs.uuid.v0.UUID
|
||||||
|
|
||||||
/**
|
/** Always returns a match.
|
||||||
* Always returns a match.
|
|
||||||
*/
|
*/
|
||||||
final class True[F[_]: Applicative](
|
final class True[F[_]: Applicative](
|
||||||
val id: UUID
|
val id: UUID
|
||||||
) extends Predicate[F, Any]:
|
) extends Predicate[F, Any]:
|
||||||
/** @inheritDocs */
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
override def eval(input: Any): F[Predicate.Result] =
|
override def eval(input: Any): F[Predicate.Result] =
|
||||||
Applicative[F].pure(Predicate.Result.matched())
|
Applicative[F].pure(Predicate.Result.matched())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,32 @@
|
||||||
package gs.predicate.v0.api
|
package gs.predicate.v0.api
|
||||||
|
|
||||||
|
import cats.effect.IO
|
||||||
import support.IOSuite
|
import support.IOSuite
|
||||||
|
|
||||||
import cats.effect.IO
|
|
||||||
|
|
||||||
import gs.predicate.v0.api.And
|
|
||||||
|
|
||||||
class AndTests extends IOSuite:
|
class AndTests extends IOSuite:
|
||||||
|
|
||||||
iotest("should return true if all are true") {
|
iotest("should return true if all are true") {
|
||||||
val and: And[IO, Any] = And(
|
val and = And(True[IO], True[IO], True[IO])
|
||||||
True[IO], True[IO], True[IO]
|
|
||||||
)
|
for result <- and.eval(())
|
||||||
and.eval(()).map(_.unwrap())
|
yield assertEquals(result.unwrap(), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
iotest("should return false if any are false") {
|
iotest("should return false if any are false") {
|
||||||
val and: And[IO, Any] = And(
|
val and = And(True[IO], False[IO], True[IO])
|
||||||
True[IO], False[IO], True[IO]
|
|
||||||
)
|
for result <- and.eval(())
|
||||||
and.eval(()).map(x => !x.unwrap())
|
yield assertEquals(result.unwrap(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
iotest("should return true for an empty list") {
|
iotest("should return false for an empty list") {
|
||||||
val and: And[IO, Any] = And()
|
val and = And.empty[IO]
|
||||||
and.eval(()).map(_.unwrap())
|
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