En este tema, exploraremos dos conceptos avanzados en Scala: las clases de tipo y el polimorfismo. Estos conceptos son fundamentales para escribir código genérico y reutilizable, y son ampliamente utilizados en la programación funcional y orientada a objetos.

Clases de Tipo

Las clases de tipo en Scala permiten definir comportamientos genéricos que pueden ser aplicados a diferentes tipos de datos. A diferencia de las clases tradicionales, las clases de tipo no definen instancias de objetos, sino que definen cómo los tipos pueden comportarse de manera genérica.

Definición de una Clase de Tipo

Una clase de tipo se define utilizando la palabra clave trait. Aquí hay un ejemplo simple de una clase de tipo que define una operación de suma:

trait Summable[T] {
  def sum(a: T, b: T): T
}

En este ejemplo, Summable es una clase de tipo que tiene un método sum que toma dos parámetros del tipo T y devuelve un valor del mismo tipo.

Implementación de una Clase de Tipo

Para usar una clase de tipo, necesitamos proporcionar implementaciones específicas para los tipos que queremos soportar. Esto se hace mediante la creación de instancias implícitas:

implicit object IntSummable extends Summable[Int] {
  def sum(a: Int, b: Int): Int = a + b
}

implicit object StringSummable extends Summable[String] {
  def sum(a: String, b: String): String = a + b
}

En este ejemplo, hemos creado dos implementaciones de Summable: una para Int y otra para String.

Uso de una Clase de Tipo

Podemos usar una clase de tipo en una función genérica utilizando parámetros implícitos:

def add[T](a: T, b: T)(implicit summable: Summable[T]): T = {
  summable.sum(a, b)
}

println(add(1, 2))         // Output: 3
println(add("Hello, ", "World!")) // Output: Hello, World!

En este ejemplo, la función add toma dos parámetros de tipo T y un parámetro implícito de tipo Summable[T]. Scala resuelve automáticamente la implementación correcta de Summable en tiempo de compilación.

Polimorfismo

El polimorfismo es la capacidad de un objeto para tomar muchas formas. En Scala, el polimorfismo se puede lograr de varias maneras, incluyendo el polimorfismo ad-hoc (sobrecarga de métodos) y el polimorfismo paramétrico (genéricos).

Polimorfismo Ad-hoc

El polimorfismo ad-hoc se refiere a la capacidad de definir múltiples métodos con el mismo nombre pero diferentes firmas. Aquí hay un ejemplo:

def printValue(value: Int): Unit = {
  println(s"Int value: $value")
}

def printValue(value: String): Unit = {
  println(s"String value: $value")
}

printValue(42)         // Output: Int value: 42
printValue("Scala")    // Output: String value: Scala

En este ejemplo, hemos definido dos métodos printValue que aceptan diferentes tipos de parámetros.

Polimorfismo Paramétrico

El polimorfismo paramétrico permite definir funciones y clases que pueden operar sobre cualquier tipo. Aquí hay un ejemplo de una clase genérica:

class Box[T](value: T) {
  def getValue: T = value
}

val intBox = new Box[Int](42)
val stringBox = new Box[String]("Scala")

println(intBox.getValue)    // Output: 42
println(stringBox.getValue) // Output: Scala

En este ejemplo, Box es una clase genérica que puede contener un valor de cualquier tipo T.

Ejercicio Práctico

Ejercicio 1: Definir una Clase de Tipo

Define una clase de tipo Multipliable que tenga un método multiply y proporciona implementaciones para Int y Double.

trait Multipliable[T] {
  def multiply(a: T, b: T): T
}

implicit object IntMultipliable extends Multipliable[Int] {
  def multiply(a: Int, b: Int): Int = a * b
}

implicit object DoubleMultipliable extends Multipliable[Double] {
  def multiply(a: Double, b: Double): Double = a * b
}

def multiplyValues[T](a: T, b: T)(implicit multipliable: Multipliable[T]): T = {
  multipliable.multiply(a, b)
}

println(multiplyValues(3, 4))         // Output: 12
println(multiplyValues(2.5, 4.0))     // Output: 10.0

Ejercicio 2: Polimorfismo Paramétrico

Define una clase genérica Pair que contenga dos valores de cualquier tipo y un método swap que intercambie los valores.

class Pair[T, U](var first: T, var second: U) {
  def swap(): Unit = {
    val temp = first
    first = second.asInstanceOf[T]
    second = temp.asInstanceOf[U]
  }
}

val pair = new Pair[Int, String](1, "Scala")
println(s"Before swap: (${pair.first}, ${pair.second})") // Output: Before swap: (1, Scala)
pair.swap()
println(s"After swap: (${pair.first}, ${pair.second})")  // Output: After swap: (Scala, 1)

Conclusión

En esta sección, hemos explorado las clases de tipo y el polimorfismo en Scala. Las clases de tipo permiten definir comportamientos genéricos para diferentes tipos de datos, mientras que el polimorfismo permite que los objetos tomen múltiples formas. Estos conceptos son fundamentales para escribir código flexible y reutilizable en Scala. En la próxima sección, profundizaremos en los macros y la reflexión en Scala.

© Copyright 2024. Todos los derechos reservados