(patch) add byte utilities to rng #2

Merged
pfm merged 1 commit from rng-enhancements into main 2026-05-07 03:28:44 +00:00
2 changed files with 90 additions and 0 deletions
Showing only changes of commit fb34707c4a - Show all commits

View file

@ -24,6 +24,19 @@ object ByteCount:
*/
final val OneGigabyte: ByteCount = 1000000000
/** Provide integer representations for common sizes in terms of number of
* bytes. These values are useful for things like array sizing that depend on
* the `Int` type.
*/
object IntSizes:
final val Zero: Int = 0
final val OneKilobyte: Int = 1000
final val OneMegabyte: Int = 1000000
final val OneGigabyte: Int = 1000000000
end IntSizes
/** Express the given number as a byte count. All values are normalized to the
* absolute value -- negative values are coerced to positive.
*

View file

@ -3,7 +3,10 @@ package gs.std.v0.effect
import cats.Applicative
import cats.effect.Sync
import cats.syntax.all.*
import fs2.Chunk
import fs2.Stream
import gs.std.v0.core.Blob
import gs.std.v0.core.ByteCount
import java.security.SecureRandom
import java.util.Random
@ -154,6 +157,29 @@ trait Rng[F[_]]:
bound: Double
): Stream[F, Double]
/** Produce an array of random bytes.
*
* @param count
* The number of bytes to produce.
* @return
* The array of randomly generated bytes.
*/
def nextByteArray(count: Int): F[Array[Byte]]
/** @return
* Infinite stream of random bytes.
*/
def nextBytes(): Stream[F, Byte]
/** Produce a blob of random bytes.
*
* @param size
* The number of bytes to produce.
* @return
* The blob of randomly generated bytes.
*/
def nextBlob(size: Int): F[Blob]
object Rng:
/** Instantiate [[Rng]] using a new `java.util.Random` instance.
@ -212,6 +238,42 @@ object Rng:
*/
final class JavaRandom[F[_]: Sync](random: Random) extends Rng[F]:
/** @inheritDocs
*/
override def nextBytes(): Stream[F, Byte] =
Stream
.evalUnChunk[F, Byte](Sync[F].delay {
// Allocate space for the random data. Generate 1kb at once.
val bytes = new Array[Byte](ByteCount.IntSizes.OneKilobyte)
val _ = random.nextBytes(bytes)
// This chunk is _backed by_ the array -- the data isn't copied, so we
// allocate a new array each time and cede it to the chunk.
Chunk.array(bytes)
})
.repeat
/** @inheritDocs
*/
override def nextByteArray(count: Int): F[Array[Byte]] =
if count < 0 then
Sync[F].raiseError(
new IllegalArgumentException(
"Cannot generate a negative number of bytes."
)
)
else if count == 0 then Sync[F].pure(Array.empty)
else
Sync[F].delay {
val bytes = new Array[Byte](count)
val _ = random.nextBytes(bytes)
bytes
}
/** @inheritDocs
*/
override def nextBlob(size: Int): F[Blob] =
nextByteArray(size).map(new Blob(_))
/** @inheritDocs
*/
override def updateSeed(seed: Long): F[Rng[F]] =
@ -306,6 +368,21 @@ object Rng:
*/
final class Zero[F[_]: Applicative] extends Rng[F]:
/** @inheritDocs
*/
override def nextByteArray(count: Int): F[Array[Byte]] =
Applicative[F].pure(Array.fill[Byte](count)(0))
/** @inheritDocs
*/
override def nextBytes(): Stream[F, Byte] =
Stream[F, Byte](0).repeat
/** @inheritDocs
*/
override def nextBlob(size: Int): F[Blob] =
nextByteArray(size).map(new Blob(_))
/** @inheritDocs
*/
override def updateSeed(seed: Long): F[Rng[F]] = Applicative[F].pure(this)