Adding and improving effect implementations.
All checks were successful
/ Build and Release Library (push) Successful in 1m31s

This commit is contained in:
Pat Garrity 2026-05-01 23:28:16 -05:00
parent eedf6e8f04
commit 479a9f6753
Signed by: pfm
GPG key ID: 5CA5D21BAB7F3A76
4 changed files with 519 additions and 168 deletions

View file

@ -0,0 +1,87 @@
package gs.std.v0.effect
import cats.effect.Sync
import cats.syntax.all.*
import java.time.Clock
import java.time.Instant
import java.time.LocalDate
import java.time.OffsetDateTime
import java.time.ZonedDateTime
import java.time.ZoneId
/** Implementation of [[DateTimeProvider]] based on a specific
* `java.time.Clock`. All dates and times are calculated using this clock.
*
* @param clock
* The clock used to calculate dates and times.
*/
final class ClockDateTimeProvider[F[_]: Sync](
val clock: Clock
) extends DateTimeProvider[F]:
/** @inheritDocs
*/
override def timestamp(): F[Instant] =
Sync[F].delay(Instant.now(clock))
/** @inheritDocs
*/
override def nowInstant(): F[Instant] =
Sync[F].delay(Instant.now(clock))
/** @inheritDocs
*/
override def nowZoned(): F[ZonedDateTime] =
Sync[F].delay(ZonedDateTime.now(clock))
/** @inheritDocs
*/
override def nowOffset(): F[OffsetDateTime] =
Sync[F].delay(OffsetDateTime.now(clock))
/** @inheritDocs
*/
override def today(): F[LocalDate] =
Sync[F].delay(LocalDate.now(clock))
/** @inheritDocs
*/
override def yesterday(): F[LocalDate] =
today().map(_.minusDays(1L))
/** @inheritDocs
*/
override def tomorrow(): F[LocalDate] =
today().map(_.plusDays(1L))
object ClockDateTimeProvider:
/** Instantiate a new [[ClockDateTimeProvider]].
*
* @param clock
* The clock to use for all date/time calculations.
* @return
* The new [[DateTimeProvider]] instance.
*/
def apply[F[_]: Sync](clock: Clock): DateTimeProvider[F] =
new ClockDateTimeProvider[F](clock)
/** @return
* A new [[ClockDateTimeProvider]] based on the system's default zone using
* the system's clock.
*/
def system[F[_]: Sync]: DateTimeProvider[F] =
new ClockDateTimeProvider[F](Clock.systemDefaultZone())
/** Use a system clock aligned to the given zone.
*
* @param zoneId
* The identifier of the zone to target.
* @return
* A new [[ClockDateTimeProvider]] based on the specified zone using the
* system's clock.
*/
def forZone[F[_]: Sync](zoneId: ZoneId): DateTimeProvider[F] =
new ClockDateTimeProvider[F](Clock.system(zoneId))
end ClockDateTimeProvider

View file

@ -0,0 +1,44 @@
package gs.std.v0.effect
import java.time.Instant
import java.time.LocalDate
import java.time.OffsetDateTime
import java.time.ZonedDateTime
/** Provides date and time values.
*/
trait DateTimeProvider[F[_]]:
/** @return
* The current timestamp (`java.time.Instant`).
*/
def timestamp(): F[Instant]
/** @return
* The current `java.time.Instant`.
*/
def nowInstant(): F[Instant]
/** @return
* The current date and time in the configured zone.
*/
def nowZoned(): F[ZonedDateTime]
/** @return
* The current date and time in the configured offset.
*/
def nowOffset(): F[OffsetDateTime]
/** @return
* The current date.
*/
def today(): F[LocalDate]
/** @return
* The date before today.
*/
def yesterday(): F[LocalDate]
/** @return
* The date after today.
*/
def tomorrow(): F[LocalDate]

View file

@ -1,168 +0,0 @@
package gs.std.v0.effect
import cats.Applicative
import cats.effect.Sync
import fs2.Stream
import java.security.SecureRandom
import java.util.Random
trait RNG[F[_]]:
def nextInt(): F[Int]
def nextInt(bound: Int): F[Int]
def nextInt(
origin: Int,
bound: Int
): F[Int]
def nextLong(): F[Long]
def nextLong(bound: Long): F[Long]
def nextLong(
origin: Long,
bound: Long
): F[Long]
def nextDouble(): F[Double]
def nextDouble(bound: Double): F[Double]
def nextDouble(
origin: Double,
bound: Double
): F[Double]
def nextBoolean(): F[Boolean]
def nextInts(
origin: Int,
bound: Int
): Stream[F, Int]
def nextLongs(
origin: Long,
bound: Long
): Stream[F, Long]
def nextDoubles(
origin: Double,
bound: Double
): Stream[F, Double]
object RNG:
def secure[F[_]: Sync]: RNG[F] =
new JavaRandom[F](new SecureRandom())
final class JavaRandom[F[_]: Sync](random: Random) extends RNG[F]:
override def nextInt(): F[Int] =
Sync[F].delay(random.nextInt())
override def nextInt(bound: Int): F[Int] =
Sync[F].delay(random.nextInt(bound))
override def nextInt(
origin: Int,
bound: Int
): F[Int] =
Sync[F].delay(random.nextInt(origin, bound))
override def nextLong(): F[Long] =
Sync[F].delay(random.nextLong())
override def nextLong(bound: Long): F[Long] =
Sync[F].delay(random.nextLong(bound))
override def nextLong(
origin: Long,
bound: Long
): F[Long] =
Sync[F].delay(random.nextLong(origin, bound))
override def nextDouble(): F[Double] =
Sync[F].delay(random.nextDouble())
override def nextDouble(bound: Double): F[Double] =
Sync[F].delay(random.nextDouble(bound))
override def nextDouble(
origin: Double,
bound: Double
): F[Double] =
Sync[F].delay(random.nextDouble(origin, bound))
override def nextBoolean(): F[Boolean] =
Sync[F].delay(random.nextBoolean())
override def nextInts(
origin: Int,
bound: Int
): Stream[F, Int] =
Stream.repeatEval(nextInt(origin, bound))
override def nextLongs(
origin: Long,
bound: Long
): Stream[F, Long] =
Stream.repeatEval(nextLong(origin, bound))
override def nextDoubles(
origin: Double,
bound: Double
): Stream[F, Double] =
Stream.repeatEval(nextDouble(origin, bound))
end JavaRandom
final class Zero[F[_]: Applicative] extends RNG[F]:
override def nextInt(): F[Int] = Applicative[F].pure(0)
override def nextInt(bound: Int): F[Int] = Applicative[F].pure(0)
override def nextInt(
origin: Int,
bound: Int
): F[Int] = Applicative[F].pure(0)
override def nextLong(): F[Long] = Applicative[F].pure(0)
override def nextLong(bound: Long): F[Long] = Applicative[F].pure(0)
override def nextLong(
origin: Long,
bound: Long
): F[Long] = Applicative[F].pure(0)
override def nextDouble(): F[Double] = Applicative[F].pure(0.0)
override def nextDouble(bound: Double): F[Double] = Applicative[F].pure(0.0)
override def nextDouble(
origin: Double,
bound: Double
): F[Double] = Applicative[F].pure(0.0)
override def nextBoolean(): F[Boolean] = Applicative[F].pure(false)
override def nextInts(
origin: Int,
bound: Int
): Stream[F, Int] =
Stream.repeatEval(Applicative[F].pure(0))
override def nextLongs(
origin: Long,
bound: Long
): Stream[F, Long] =
Stream.repeatEval(Applicative[F].pure(0))
override def nextDoubles(
origin: Double,
bound: Double
): Stream[F, Double] =
Stream.repeatEval(Applicative[F].pure(0.0))
end Zero
end RNG

View file

@ -0,0 +1,388 @@
package gs.std.v0.effect
import cats.Applicative
import cats.effect.Sync
import cats.syntax.all.*
import fs2.Stream
import java.security.SecureRandom
import java.util.Random
/** Random number generator.
*/
trait Rng[F[_]]:
/** Explicitly set or update the seed using the given 8 bytes.
*
* @param seed
* The seed value to assign or augment.
* @return
* This [[Rng]] instance.
*/
def updateSeed(seed: Long): F[Rng[F]]
/** @return
* The next (unbounded) random `Int`.
*/
def nextInt(): F[Int]
/** Calculate a number bounded inclusively within 0 and some bound.
*
* @param bound
* The bound.
* @return
* The calculated number.
*/
def nextInt(bound: Int): F[Int]
/** Calculate a number bounded inclusively within some origin and some bound.
*
* @param origin
* The origin.
* @param bound
* The bound.
* @return
* The calculated number.
*/
def nextInt(
origin: Int,
bound: Int
): F[Int]
/** @return
* The next (unbounded) random `Long`.
*/
def nextLong(): F[Long]
/** Calculate a number bounded inclusively within 0 and some bound.
*
* @param bound
* The bound.
* @return
* The calculated number.
*/
def nextLong(bound: Long): F[Long]
/** Calculate a number bounded inclusively within some origin and some bound.
*
* @param origin
* The origin.
* @param bound
* The bound.
* @return
* The calculated number.
*/
def nextLong(
origin: Long,
bound: Long
): F[Long]
/** @return
* The next (unbounded) random `Double`.
*/
def nextDouble(): F[Double]
/** Calculate a number bounded inclusively within 0 and some bound.
*
* @param bound
* The bound.
* @return
* The calculated number.
*/
def nextDouble(bound: Double): F[Double]
/** Calculate a number bounded inclusively within some origin and some bound.
*
* @param origin
* The origin.
* @param bound
* The bound.
* @return
* The calculated number.
*/
def nextDouble(
origin: Double,
bound: Double
): F[Double]
/** @return
* The next randomly selected `true` or `false` value.
*/
def nextBoolean(): F[Boolean]
/** Produce an infinite stream of `Int` bounded inclusively within some origin
* and some bound.
*
* @param origin
* The origin.
* @param bound
* The bound.
* @return
* The infinite stream of values.
*/
def nextInts(
origin: Int,
bound: Int
): Stream[F, Int]
/** Produce an infinite stream of `Long` bounded inclusively within some
* origin and some bound.
*
* @param origin
* The origin.
* @param bound
* The bound.
* @return
* The infinite stream of values.
*/
def nextLongs(
origin: Long,
bound: Long
): Stream[F, Long]
/** Produce an infinite stream of `Double` bounded inclusively within some
* origin and some bound.
*
* @param origin
* The origin.
* @param bound
* The bound.
* @return
* The infinite stream of values.
*/
def nextDoubles(
origin: Double,
bound: Double
): Stream[F, Double]
object Rng:
/** Instantiate [[Rng]] using a new `java.util.Random` instance.
*
* For secure random number generation, please refer to the `secure[F]`
* function.
*
* @param random
* The `java.util.Random` instance.
* @return
* The new [[Rng]].
*/
def default[F[_]: Sync]: Rng[F] =
default[F](new Random())
/** Instantiate [[Rng]] using the given random number generator.
*
* @param random
* The `java.util.Random` instance.
* @return
* The new [[Rng]].
*/
def default[F[_]: Sync](random: Random): Rng[F] =
new JavaRandom[F](random)
/** Instantiate [[Rng]] using a new `java.security.SecureRandom` instance.
*
* @param random
* The `java.security.SecureRandom` instance.
* @return
* The new [[Rng]].
*/
def secure[F[_]: Sync]: Rng[F] =
secure[F](new SecureRandom())
/** Instantiate [[Rng]] using the given secure random number generator.
*
* @param random
* The `java.security.SecureRandom` instance.
* @return
* The new [[Rng]].
*/
def secure[F[_]: Sync](random: SecureRandom): Rng[F] =
new JavaRandom[F](random)
/** @return
* New instance of [[Rng]] that always returns `0` or `false`.
*/
def zero[F[_]: Applicative]: Rng[F] =
new Zero[F]
/** Implementation of [[Rng]] based on any given `java.util.Random` instance.
*
* @param random
* The underlying random number generator.
*/
final class JavaRandom[F[_]: Sync](random: Random) extends Rng[F]:
/** @inheritDocs
*/
override def updateSeed(seed: Long): F[Rng[F]] =
Sync[F].delay(random.setSeed(seed)).as(this)
/** @inheritDocs
*/
override def nextInt(): F[Int] =
Sync[F].delay(random.nextInt())
/** @inheritDocs
*/
override def nextInt(bound: Int): F[Int] =
Sync[F].delay(random.nextInt(bound))
/** @inheritDocs
*/
override def nextInt(
origin: Int,
bound: Int
): F[Int] =
Sync[F].delay(random.nextInt(origin, bound))
/** @inheritDocs
*/
override def nextLong(): F[Long] =
Sync[F].delay(random.nextLong())
/** @inheritDocs
*/
override def nextLong(bound: Long): F[Long] =
Sync[F].delay(random.nextLong(bound))
/** @inheritDocs
*/
override def nextLong(
origin: Long,
bound: Long
): F[Long] =
Sync[F].delay(random.nextLong(origin, bound))
/** @inheritDocs
*/
override def nextDouble(): F[Double] =
Sync[F].delay(random.nextDouble())
/** @inheritDocs
*/
override def nextDouble(bound: Double): F[Double] =
Sync[F].delay(random.nextDouble(bound))
/** @inheritDocs
*/
override def nextDouble(
origin: Double,
bound: Double
): F[Double] =
Sync[F].delay(random.nextDouble(origin, bound))
/** @inheritDocs
*/
override def nextBoolean(): F[Boolean] =
Sync[F].delay(random.nextBoolean())
/** @inheritDocs
*/
override def nextInts(
origin: Int,
bound: Int
): Stream[F, Int] =
Stream.repeatEval(nextInt(origin, bound))
/** @inheritDocs
*/
override def nextLongs(
origin: Long,
bound: Long
): Stream[F, Long] =
Stream.repeatEval(nextLong(origin, bound))
/** @inheritDocs
*/
override def nextDoubles(
origin: Double,
bound: Double
): Stream[F, Double] =
Stream.repeatEval(nextDouble(origin, bound))
end JavaRandom
/** Implementation of [[Rng]] that always returns `0` or `false`.
*/
final class Zero[F[_]: Applicative] extends Rng[F]:
/** @inheritDocs
*/
override def updateSeed(seed: Long): F[Rng[F]] = Applicative[F].pure(this)
/** @inheritDocs
*/
override def nextInt(): F[Int] = Applicative[F].pure(0)
/** @inheritDocs
*/
override def nextInt(bound: Int): F[Int] = Applicative[F].pure(0)
/** @inheritDocs
*/
override def nextInt(
origin: Int,
bound: Int
): F[Int] = Applicative[F].pure(0)
/** @inheritDocs
*/
override def nextLong(): F[Long] = Applicative[F].pure(0)
/** @inheritDocs
*/
override def nextLong(bound: Long): F[Long] = Applicative[F].pure(0)
/** @inheritDocs
*/
override def nextLong(
origin: Long,
bound: Long
): F[Long] = Applicative[F].pure(0)
/** @inheritDocs
*/
override def nextDouble(): F[Double] = Applicative[F].pure(0.0)
/** @inheritDocs
*/
override def nextDouble(bound: Double): F[Double] = Applicative[F].pure(0.0)
/** @inheritDocs
*/
override def nextDouble(
origin: Double,
bound: Double
): F[Double] = Applicative[F].pure(0.0)
/** @inheritDocs
*/
override def nextBoolean(): F[Boolean] = Applicative[F].pure(false)
/** @inheritDocs
*/
override def nextInts(
origin: Int,
bound: Int
): Stream[F, Int] =
Stream.repeatEval(Applicative[F].pure(0))
/** @inheritDocs
*/
override def nextLongs(
origin: Long,
bound: Long
): Stream[F, Long] =
Stream.repeatEval(Applicative[F].pure(0))
/** @inheritDocs
*/
override def nextDoubles(
origin: Double,
bound: Double
): Stream[F, Double] =
Stream.repeatEval(Applicative[F].pure(0.0))
end Zero
end Rng