package _970_scala_puzzlers

import scala.collection.mutable

/**
  * Puzzler 7 - Caught Up in Closures
  * What is the result of executing the following code?
  *
  * variables data, i, and j are no longer in scope when the functions are invoked.
  * Before examining which differences between i and j result in the observed behavior, it is helpful to look at how
  * Scala enables the function body to access these variables at all.
  * Scala allows the body of a function to reference variables that are not explicit function parameters, but are in
  * scope at the moment the function is constructed. To access these free variables when the function is invoked in
  * a different scope, Scala "closes over" them to create a closure.
  * Closing over a free variable is not taking a "snapshot" of the variable's value when it is used. Instead, a field
  * referencing the captured variable is added to the function object. Crucially for this case, while captured vals are
  * simply represented by the value, capturing a var results in a reference to the var itself.
  *
  * From here, the explanation for the observed behavior is straightforward: when each accessors1 function is created,
  * it captures the current value of i, and so prints the expected results when invoked. The accessors2 functions, on
  * the other hand, each capture a reference to a mutable IntRef object containing the value of j, which can change
  * over time.
  * By the time the first accessors2 function is invoked, the value of j is already 3. Since data only has three
  * elements, invoking data(j) triggers an IndexOutOfBoundsException.
  *
  * Solutions:
  * The most robust way to prevent this problem is to avoid vars, which is also better Scala style.
  * If you can't avoid a var, but you still want a closure to capture its value at the time the closure is created,
  * you can "freeze" the var by assigning its value to a temporary val. look for this 'Solution' below.
  *
  * --> Avoid capturing free variables in your closures that refer to anything mutable—vars or mutable objects. If you
  * need to close over anything mutable, extract a stable value and assign it to a val, then use that val in your function.
  */
object _07_CaughtUpInClosures {
  val accessors1: mutable.Buffer[() => Int] = mutable.Buffer.empty[() => Int]
  val accessors2: mutable.Buffer[() => Int] = mutable.Buffer.empty[() => Int]

  val data = Seq(100, 110, 120)
  var j = 0
  for (i <- data.indices) {
    accessors1 += (() => data(i))
    accessors2 += (() => data(j))
    j += 1
  }

  def main(args: Array[String]): Unit = {
    accessors1.foreach(a1 => println(a1()))
    //    accessors2.foreach(a2 => println(a2())) // throws java.lang.IndexOutOfBoundsException: 3
    println("\n===== Solution =====\n")
    Solution.accessors1.foreach(a1 => println(a1()))
    Solution.accessors2.foreach(a2 => println(a2()))
  }

  object Solution {
    val accessors1: mutable.Buffer[() => Int] = mutable.Buffer.empty[() => Int]
    val accessors2: mutable.Buffer[() => Int] = mutable.Buffer.empty[() => Int]

    val data = Seq(100, 110, 120)
    var j = 0
    for (i <- data.indices) {
      val currentJ = j
      accessors1 += (() => data(i))
      accessors2 += (() => data(currentJ))
      j += 1
    }
  }

}