Generative Art

Generative art means art where some part of the composition is determined by an autonomous process. Concretely, for us this will mean adding an element of randomness.

Let's take a really simple example. We've learned previously how to create concentric circles.

def concentricCircles(n: Int): Image =
  n match {
    case 0 => Image.circle(10)
    case n => concentricCircles(n-1).on(Image.circle(n * 10)) 
  }

(We now know we could write this using a Range and a method like allOn.)

We also learned how we could make coloured circles, using a second parameter.

def concentricCircles(n: Int, color: Color): Image =
  n match {
    case 0 => Image.circle(10).fillColor(color)
    case n => concentricCircles(n-1, color.spin(15.degrees)).on(Image.circle(n * 10).fillColor(color)) 
  }

Pictures constructed in this way are nice, but they are a bit boring in their regularity. What if we wanted to make a random alteration to the hue of the color at each step?

Scala provides some methods that produce random numbers. One such method is math.random. Each time we call it we get a different Double between 0.0 and 1.0Footnote pseudo-random.

[pseudo-random] These numbers are not truly random. The output is determined by a value known as the seed. If we know the seed we can perfectly predict all the result we'll get from calling math.random. However, going the other way---that is, predicting the seed given a sequence of outputs---is very difficult. The numbers so generated are called pseudo-random numbers, because they are not truly random but nonetheless are very difficult to predict.
math.random
// res0: Double = 0.20832799599325624
math.random
// res1: Double = 0.7478937442305806

Given math.random we could produce a method that returns a random Angle like so.

def randomAngle: Angle = 
  math.random.turns
  
randomAngle
// res2: Angle = Angle(1.7999491955145053)
randomAngle
// res3: Angle = Angle(4.298384675448248)

Why might we not want to do this? What principle does this break?

<div class="solution"> Generating random numbers in this way breaks substitution. Remember substitution says wherever we see an expression we should be able to substitute the value it evaluates to without changing the meaning of the program. Concretely, this means

val result1 = randomAngle
// result1: Angle = Angle(0.11680001056257192)
val result2 = randomAngle
// result2: Angle = Angle(1.7115052850116896)

and

val result1 = randomAngle
// result1: Angle = Angle(6.097248152056065)
val result2 = result1
// result2: Angle = Angle(6.097248152056065)

should be the same program and clearly they are not. </div>

What should we do? Suffer the slings and arrows of outrageous computational models, or take arms against a sea of side-effects, and by opposing end them! There's really only one choice.