Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 62 additions & 10 deletions docs/std/random.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,75 @@ on the JVM:
- `java.util.Random`
- `java.security.SecureRandom`

## Creating a `Random` instance
## Creating and using a `Random` instance

Obtaining an instance of `Random` can be as simple as:
The following example shows the usage of `Random` in a way that is *referentially transparent*, meaning that you can know from the 'outside' what the result will be by knowing the inputs:
```scala mdoc:silent
import cats.effect.IO
import cats.effect.std.Random
import cats.syntax.all._
import cats.Monad
import cats.effect.unsafe.implicits.global

object BusinessLogic {

// use the standard implementation of Random backed by java.util.Random()
// (the same implementation as Random.javaUtilRandom(43))
val randomizer: IO[Random[IO]] = Random.scalaUtilRandom[IO]

// other possible implementations you could choose
val sr = SecureRandom.javaSecuritySecureRandom(3) // backed java.security.SecureRandom()
val jr = Random.javaUtilRandom(new java.util.Random()) // pass in the backing randomizer

// calling .unsafeRunSync() in business logic is an anti-patten.
// Doing it here to make the example easy to follow.
def unsafeGetMessage: String =
Magic
.getMagicNumber[IO](5, randomizer)
.unsafeRunSync()
}

Random.scalaUtilRandom[IO]
object Magic {
def getMagicNumber[F[_] : Monad](
mult: Int,
randomizer: F[Random[F]]
): F[String] =
for {
rand <- randomizer.flatMap(random => random.betweenInt(1, 11)) // 11 is excluded
number = rand * mult
msg = s"the magic number is: $number"
} yield msg
}
```

## Using `Random`
```scala mdoc
import cats.Functor
import cats.syntax.functor._
Since `getMagicNumber` is not dependent on a particular implementation (it's referentially transparent), you can give it another instance of the type class as you see fit.

This is particularly useful when testing. In the following example, we need our `Random` implementation to give back a stable value so we can ensure everything else works correctly, and our test assert succeeds. Since `randomizer` is passed into `getMagicNumber`, we can swap it out in our test with a `Random` of which we can make stable. In our test implementation, calls to `betweenInt` will *always* give back `7`. This stability of "randomness" allows us to test that our function `getMagicNumber` does what we intend:

def dieRoll[F[_]: Functor: Random]: F[Int] =
Random[F].betweenInt(0, 6).map(_ + 1) // `6` is excluded from the range
```scala mdoc:silent
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.must.Matchers._

class MagicSpec extends AnyFunSuite {

// for testing, create a Random instance that gives back the same number every time. With
// this version of the Random type class, we can test our business logic works as intended.
implicit val r: IO[Random[IO]] = IO.pure(
new Random[IO] {
def betweenInt(minInclusive: Int, maxExclusive: Int): IO[Int] =
IO.pure(7) // gives back 7 every call

// all other methods not implemented since they won't be called in our test
def betweenDouble(minInclusive: Double, maxExclusive: Double): IO[Double] = ???
def betweenFloat(minInclusive: Float, maxExclusive: Float): IO[Float] = ???
// ... snip: cutting out other method implementations for brevity
}
)

test("getMagicNumber text matches expectations") {
val result = MagicSpec.getMagicNumber[IO](5)
result.mustBe("the magic number is: 35")
}
}
```

## Derivation
Expand Down