Randomness without Effect

The solution to our problem is to separate describing how we'll use random numbers from the process of actually generating them. This sounds complicated, but it's exactly what we've be doing with Image throughout this book. We

We do the same thing with Doodle's Random type. To access this code we first need to import the doodle.random package.

import doodle.random._

Now we can create values that describe creating a random number

val randomDouble = Random.double
// randomDouble: Free[[A >: Nothing <: Any] => RandomOp[A], Double] = Suspend(
//   a = RDouble
// )

No random numbers are actually created until we call the run method.

// res0: Double = 0.726509678904902

The type Random[Double] indicates we have something that will produce a random Double when we run it. Just like with Image and draw, substitution holds with Random up until the point we call run.

Table Table generative:random shows some of the ways to construct Random values.

Method Description Example -------------------------- ------------------------------ ------------------------------ Random.always(value) Creates a Random that Random.always(10) always produces the given value.

Random.double Creates a Random that Random.double generates a Double uniformly distributed between 0.0 and 1.0.

Random.int Creates a Random that Random.int generates an Int uniformly distributed across the entire range.

Random.natural(limit) Creates a Random that Random.natural(10) generates a Int uniformly distributed in the range greater than or equal to 0 and less than 1.

Random.oneOf(value, ...) Creates a Random that Random.oneOf("A", "B", "C") generates one of the given values with equal chance. ----------------------------------------------------------------------------------------

: Some of the methods to create Random values. Table generative:random

Composing Random

Now we've seen how to create a Random, how do we compose them into more interesting programs? For example, how could we turn a random Double into a random Angle? It might be tempting to call run every time we want to manipulate a random result, but this will break substitution and is exactly what we're trying to avoid.

Remember when we talked about map in the previous chapter we said it transforms the elements but keeps the structure (number of elements) in the List. The same analogy applies to the map method on Random. It lets us transform the element of a Random---the value it produces when it is run---but doesn't let us change the structure. Here the "structure" means introducing more randomness, or making a random choice.

We can create a random value and apply a deterministic transformation to it using map, but we can't create a random value and them use that value as input to a process that creates another random value.

Here's how we can create a random angle.

val randomAngle: Random[Angle] =
  Random.double.map(x => x.turns)

When we run RandomAngle we can generate randomly created Angle

// res1: Angle = Angle(2.111645234336106)
// res2: Angle = Angle(6.216313473473228)

Exercises {-}

Random Colors {-}

Given randomAngle above, create a method that accepts saturation and lightness and generates a random color. Your method should have the signature

def randomColor(s: Normalized, l: Normalized): Random[Color] =

<div class="example"> This is a deterministic transformation of the output of randomAngle, so we can implement it using map.

def randomColor(s: Double, l: Double): Random[Color] =
  randomAngle.map(hue => Color.hsl(hue, s, l))


Random Circles {-}

Write a method that accepts a radius and a Random[Color], and produces a circle of the given radius and filled with the given random color. It should have the signature

def randomCircle(r: Double, color: Random[Color]): Random[Image] =

<div class="example"> Once again this is a deterministic transformation of the random color, so we can use map.

def randomCircle(r: Double, color: Random[Color]): Random[Image] =
  color.map(fill => Image.circle(r).fillColor(fill))