En el desarrollo de software, la inmutabilidad es un concepto clave en el paradigma de programación funcional y juega un papel fundamental en la construcción de código robusto y confiable. En Scala la inmutabilidad se promueve como una práctica recomendada.
¿Qué es la inmutabilidad?
En programación, la inmutabilidad se refiere a la propiedad de un objeto o valor que no puede cambiar después de ser creado. En Scala, los objetos inmutables no pueden ser modificados una vez que se han creado, lo que implica que cualquier intento de cambiar su estado resultará en la creación de una nueva instancia con los cambios deseados. Esta característica contrasta con la mutabilidad, donde los objetos pueden cambiar su estado a lo largo del tiempo.
Beneficios de la inmutabilidad:
La inmutabilidad ofrece una serie de beneficios significativos en el desarrollo de software:
- Evita efectos secundarios inesperados: Al utilizar objetos inmutables, se evitan los efectos secundarios no deseados que pueden surgir cuando varios componentes comparten y modifican el mismo estado mutable. Esto mejora la predictibilidad y facilita el razonamiento sobre el comportamiento del programa.
- Simplifica la concurrencia: Los objetos inmutables son intrínsecamente seguros para su uso en entornos concurrentes. Como no se pueden modificar, no hay problemas de concurrencia relacionados con el acceso simultáneo y la modificación de estado. Esto facilita la creación de programas concurrentes y ayuda a evitar errores comunes asociados con la concurrencia.
- Facilita el razonamiento y el testing: Al utilizar objetos inmutables, el comportamiento de un objeto se puede razonar de manera aislada sin tener que considerar cambios en su estado. Esto hace que el código sea más fácil de entender, depurar y testear, ya que no se requiere realizar un seguimiento de cambios y mutaciones de estado en múltiples lugares.
- Fomenta la reutilización de código: Los objetos inmutables son más fáciles de reutilizar en diferentes contextos y componentes, ya que su estado no cambia. Esto promueve la modularidad y el diseño de componentes independientes y cohesivos.
Ejemplos de inmutabilidad en Scala:
Case class inmutable:
case class Persona(nombre: String, edad: Int)
val persona = Persona("Juan", 30)
val personaActualizada = persona.copy(edad = 31)
En este ejemplo, la case class Persona
es inmutable. Una vez que se crea una instancia de Persona
, sus campos no se pueden modificar. Para realizar cambios, se crea una nueva instancia utilizando el método copy
que devuelve una nueva instancia de Persona
con los campos modificados.
Colecciones inmutables:
val lista = List(1, 2, 3)
val listaActualizada = lista :+ 4
En este caso, la lista lista
es inmutable. No se pueden agregar ni eliminar elementos de la lista existente. En cambio, se crea una nueva lista listaActualizada
utilizando el operador :+
que agrega un elemento al final de la lista original.
Valores inmutables:
val pi = 3.14159
En este ejemplo, el valor pi
es inmutable. No se puede cambiar una vez que se ha asignado. Cualquier intento de modificar su valor generaría un error de compilación.
Ejemplos en Java y Scala
En los siguientes ejemplos extraidos del libro Grokking Functional Programming se puede notar como el metodo replan
esta implementado de tal manera que hay mutaciones de valores:
public class PlannerMutableJava {
public static void main(String[] args) {
List<String> plan = new ArrayList<>();
plan.add("Paris");
plan.add("Berlin");
plan.add("Krakow");
System.out.println(plan);
replan(plan, "Vienna", "Krakow");
System.out.println(plan);
}
static void replan(List<String> plan,
String newCity,
String beforeCity) {
int newCityIndex = plan.indexOf(beforeCity);
plan.add(newCityIndex, newCity);
}
}
Esto puede ser re escrito en Java de tal manera que el metodo replan
solo use valores inmutables:
public class PlannerImmutableJava {
public static void main(String[] args) {
List<String> plan = new ArrayList<>();
plan.add("Paris");
plan.add("Berlin");
plan.add("Krakow");
System.out.println(plan);
List<String> plan2 = replan(plan, "Vienna", "Krakow");
System.out.println(plan2);
}
static List<String> replan(List<String> plan,
String newCity,
String beforeCity) {
int newCityIndex = plan.indexOf(beforeCity);
List<String> replanned = new ArrayList<>(plan);
replanned.add(newCityIndex, newCity);
return replanned;
}
}
Del mismo modo se puede tener en Scala:
object PlannerImmutableScala extends App {
def replan(plan: List[String],
newCity: String,
beforeCity: String): List[String] = {
val beforeCityIndex = plan.indexOf(beforeCity)
val citiesBefore = plan.slice(0, beforeCityIndex)
val citiesAfter = plan.slice(beforeCityIndex, plan.size)
citiesBefore.appended(newCity).appendedAll(citiesAfter)
}
val plan: List[String] = List("Paris", "Berlin", "Krakow")
println(plan)
val plan2: List[String] = replan(plan, "Vienna", "Krakow")
println(plan2)
}
Conclusión:
La inmutabilidad es una práctica esencial en Scala y en la programación funcional en general. Al utilizar objetos inmutables, evitamos efectos secundarios inesperados, simplificamos la concurrencia, facilitamos el razonamiento y el testing, y fomentamos la reutilización de código. Los ejemplos proporcionados ilustran cómo podemos aplicar la inmutabilidad en diferentes contextos en Scala. Al adoptar la inmutabilidad como principio de diseño, podemos construir código más robusto, confiable y fácilmente mantenible.