Cierres en Scala

En Scala, un cierre (closure) es una función que puede capturar e incluir variables libres de su entorno de definición.

Un cierre es una función cuyo valor de retorno depende de una o más variables declaradas fuera de la función.

Un cierre puede acceder a variables externas a la función y usarlas dentro de la función, incluso cuando el ámbito de las variables ha sobrepasado su entorno de definición, el cierre aún puede acceder a ellas.

La clave del cierre es que puede «cerrar» variables externas a la función, haciendo que estas variables sigan disponibles dentro de la función.

Como en la siguiente función anónima:

val multiplier = (i:Int) => i * 10  

Dentro del cuerpo de la función hay una variable i, que actúa como parámetro de la función. Como en el siguiente fragmento de código:

val multiplier = (i:Int) => i * factor

En multiplier hay dos variables: i y factor. Una de ellas, i, es un parámetro formal de la función; cuando se invoca la función multiplier, a i se le asigna un nuevo valor. Sin embargo, factor no es un parámetro formal, sino una variable libre. Considera el siguiente código:

var factor = 3  
val multiplier = (i:Int) => i * factor  

Aquí introducimos una variable libre factor, que está definida fuera de la función.

La variable de función multiplier así definida se convierte en un «cierre», porque hace referencia a una variable definida fuera de la función. El proceso de definir esta función consiste en capturar esta variable libre para formar una función cerrada.

Ejemplo completo

Ejemplo

object Test {  
   def main(args: Array[String]) {  
      println( "muliplier(1) value = " +  multiplier(1) )  
      println( "muliplier(2) value = " +  multiplier(2) )  
   }  
   var factor = 3  
   val multiplier = (i:Int) => i * factor  
}

Al ejecutar el código anterior, el resultado es:

$ scalac Test.scala  
$  scala Test  
muliplier(1) value = 3  
muliplier(2) value = 6  

En el siguiente ejemplo, la función closure captura la variable externa externalVar. Cuando el valor de externalVar cambia, el resultado del cálculo dentro del cierre también cambia en consecuencia.

Ejemplo

object ClosureExample1 {
  def main(args: Array[String]): Unit = {
    var externalVar = 10

    val closure = (x: Int) => x + externalVar

    println(closure(5))  // Salida 15

    externalVar = 20

    println(closure(5))  // Salida 25
  }
}

Al ejecutar el código anterior, el resultado es:

15
25

El cierre como valor de retorno

En el siguiente ejemplo, la función makeAdder devuelve un cierre. Diferentes instancias de cierres capturan diferentes valores de adder.

Ejemplo

object ClosureExample2 {
  def main(args: Array[String]): Unit = {
    def makeAdder(adder: Int): Int => Int = {
      (x: Int) => x + adder
    }

    val addFive = makeAdder(5)
    val addTen = makeAdder(10)

    println(addFive(3))  // Salida 8
    println(addTen(3))   // Salida 13
  }
}

Al ejecutar el código anterior, el resultado es:

8
13

El tipo de variables capturadas por el cierre

Los cierres pueden capturar variables de diferentes tipos, incluidos tipos por valor y tipos por referencia.

En el siguiente ejemplo, el cierre addMessage captura la variable messages y puede modificarla.

Ejemplo

object ClosureExample3 {
  def main(args: Array[String]): Unit = {
    var messages = List("Hello", "World")

    val addMessage = (msg: String) => messages = messages :+ msg

    addMessage("Scala")
    println(messages)  // Salida List(Hello, World, Scala)
  }
}

Al ejecutar el código anterior, el resultado es:

List(Hello, World, Scala)

Cierres y programación funcional

El cierre es un concepto importante en la programación funcional y se utiliza ampliamente en funciones de orden superior, currificación y otras técnicas.

En el siguiente ejemplo, la función highOrderFunction acepta una función como parámetro. El cierre pasado captura la variable externa externalVar.

Ejemplo

object ClosureExample4 {
  def main(args: Array[String]): Unit = {
    def highOrderFunction(f: Int => Int, x: Int): Int = f(x)

    val externalVar = 5
    val closure = (x: Int) => x + externalVar

    println(highOrderFunction(closure, 10))  // Salida 15
  }
}

Al ejecutar el código anterior, el resultado es:

15
Otras extensiones