¿Cuál es la diferencia entre la clase de caso y la clase de Scala?
Busqué en Google para encontrar las diferencias entre a case class
y a class
. Todo el mundo menciona que cuando quieras hacer una coincidencia de patrones en una clase, utiliza la clase de caso. De lo contrario, use clases y también mencione algunas ventajas adicionales como iguales y anulación de código hash. Pero, ¿son estas las únicas razones por las que se debería utilizar una clase de caso en lugar de una clase?
Supongo que debería haber alguna razón muy importante para esta característica en Scala. ¿Cuál es la explicación o existe algún recurso para obtener más información sobre las clases de casos de Scala?
Las clases de casos pueden verse como objetos simples e inmutables que contienen datos y que deberían depender exclusivamente de los argumentos de su constructor .
Este concepto funcional nos permite
- utilizar una sintaxis de inicialización compacta (
Node(1, Leaf(2), None))
) - descomponerlos usando coincidencia de patrones
- tener comparaciones de igualdad implícitamente definidas
En combinación con la herencia, las clases de casos se utilizan para imitar tipos de datos algebraicos .
Si un objeto realiza cálculos con estado en su interior o exhibe otros tipos de comportamiento complejo, debería ser una clase ordinaria.
Técnicamente, no hay diferencia entre una clase y una clase de caso, incluso si el compilador optimiza algunas cosas cuando usa clases de caso. Sin embargo, se utiliza una clase de caso para eliminar la placa estándar para un patrón específico, que implementa tipos de datos algebraicos .
Un ejemplo muy sencillo de este tipo de tipos son los árboles. Un árbol binario, por ejemplo, se puede implementar así:
sealed abstract class Tree
case class Node(left: Tree, right: Tree) extends Tree
case class Leaf[A](value: A) extends Tree
case object EmptyLeaf extends Tree
Eso nos permite hacer lo siguiente:
// DSL-like assignment:
val treeA = Node(EmptyLeaf, Leaf(5))
val treeB = Node(Node(Leaf(2), Leaf(3)), Leaf(5))
// On Scala 2.8, modification through cloning:
val treeC = treeA.copy(left = treeB.left)
// Pretty printing:
println("Tree A: "+treeA)
println("Tree B: "+treeB)
println("Tree C: "+treeC)
// Comparison:
println("Tree A == Tree B: %s" format (treeA == treeB).toString)
println("Tree B == Tree C: %s" format (treeB == treeC).toString)
// Pattern matching:
treeA match {
case Node(EmptyLeaf, right) => println("Can be reduced to "+right)
case Node(left, EmptyLeaf) => println("Can be reduced to "+left)
case _ => println(treeA+" cannot be reduced")
}
// Pattern matches can be safely done, because the compiler warns about
// non-exaustive matches:
def checkTree(t: Tree) = t match {
case Node(EmptyLeaf, Node(left, right)) =>
// case Node(EmptyLeaf, Leaf(el)) =>
case Node(Node(left, right), EmptyLeaf) =>
case Node(Leaf(el), EmptyLeaf) =>
case Node(Node(l1, r1), Node(l2, r2)) =>
case Node(Leaf(e1), Leaf(e2)) =>
case Node(Node(left, right), Leaf(el)) =>
case Node(Leaf(el), Node(left, right)) =>
// case Node(EmptyLeaf, EmptyLeaf) =>
case Leaf(el) =>
case EmptyLeaf =>
}
Tenga en cuenta que los árboles construyen y deconstruyen (mediante coincidencia de patrones) con la misma sintaxis, que también es exactamente como se imprimen (menos espacios).
Y también se pueden usar con mapas o conjuntos hash, ya que tienen un código hash válido y estable.