spark pattern generic companion classes scala functional-programming case-class

pattern - ¿Cuál es la diferencia entre la clase de caso de Scala y la clase?



scala extractor (14)

Busqué en Google para encontrar las diferencias entre una case class y una class . Todo el mundo menciona que cuando desee hacer una comparación de patrones en la clase, utilice la clase de caso. De lo contrario, use clases y también mencione algunas ventajas adicionales, como iguales y sobreescritura de código hash. Pero, ¿son estas las únicas razones por las que uno debería usar una clase de caso en lugar de una clase?

Supongo que debería haber una razón muy importante para esta función en Scala. ¿Cuál es la explicación o hay un recurso para aprender más sobre las clases de casos de Scala?


A diferencia de las clases, las clases de casos solo se usan para guardar datos.

Las clases de casos son flexibles para las aplicaciones centradas en los datos, lo que significa que puede definir campos de datos en la clase de casos y definir la lógica de negocios en un objeto complementario. De esta manera, está separando los datos de la lógica de negocios.

Con el método de copia, puede heredar cualquiera o todas las propiedades requeridas de la fuente y puede cambiarlas a su gusto.


Aparte de lo que las personas ya han dicho, hay algunas diferencias más básicas entre class y case class

1. Case Class no necesita new explícitos, mientras que la clase debe llamarse con new

val classInst = new MyClass(...) // For classes val classInst = MyClass(..) // For case class

2. Por parámetros de constructores predeterminados son privados en class , mientras que son públicos en case class

// For class class MyClass(x:Int) { } val classInst = new MyClass(10) classInst.x // FAILURE : can''t access // For caseClass case class MyClass(x:Int) { } val classInst = MyClass(10) classInst.x // SUCCESS

3. case class compararse por valor

// case Class class MyClass(x:Int) { } val classInst = new MyClass(10) val classInst2 = new MyClass(10) classInst == classInst2 // FALSE // For Case Class case class MyClass(x:Int) { } val classInst = MyClass(10) val classInst2 = MyClass(10) classInst == classInst2 // TRUE


Clase:

scala> class Animal(name:String) defined class Animal scala> val an1 = new Animal("Padddington") an1: Animal = Animal@748860cc scala> an1.name <console>:14: error: value name is not a member of Animal an1.name ^

Pero si usamos el mismo código pero usamos la clase de caso:

scala> case class Animal(name:String) defined class Animal scala> val an2 = new Animal("Paddington") an2: Animal = Animal(Paddington) scala> an2.name res12: String = Paddington scala> an2 == Animal("fred") res14: Boolean = false scala> an2 == Animal("Paddington") res15: Boolean = true

Clase de persona

scala> case class Person(first:String,last:String,age:Int) defined class Person scala> val harry = new Person("Harry","Potter",30) harry: Person = Person(Harry,Potter,30) scala> harry res16: Person = Person(Harry,Potter,30) scala> harry.first = "Saily" <console>:14: error: reassignment to val harry.first = "Saily" ^ scala>val saily = harry.copy(first="Saily") res17: Person = Person(Saily,Potter,30) scala> harry.copy(age = harry.age+1) res18: Person = Person(Harry,Potter,31)

La coincidencia de patrones:

scala> harry match { | case Person("Harry",_,age) => println(age) | case _ => println("no match") | } 30 scala> res17 match { | case Person("Harry",_,age) => println(age) | case _ => println("no match") | } no match

objeto: singleton:

scala> case class Person(first :String,last:String,age:Int) defined class Person scala> object Fred extends Person("Fred","Jones",22) defined object Fred


Creo que, en general, todas las respuestas han dado una explicación semántica sobre las clases y las clases de casos. Esto podría ser muy relevante, pero cada novato en scala debería saber qué sucede cuando crea una clase de caso. He escrito this respuesta, que explica la clase de casos en pocas palabras.

Todos los programadores deben saber que si están utilizando funciones precompiladas, están escribiendo un código comparativamente menor, lo que les permite otorgar el poder de escribir el código más optimizado, pero el poder conlleva grandes responsabilidades. Por lo tanto, utilice funciones precompiladas con precauciones.

Algunos desarrolladores evitan escribir clases de casos debido a 20 métodos adicionales, que puede ver al desensamblar el archivo de clase.

Consulte this .


La construcción de la clase de caso en Scala también se puede ver como una conveniencia para quitar un poco de repetitivo.

Al construir una clase de caso, Scala te da lo siguiente.

  • Crea una clase así como su objeto compañero.
  • Su objeto complementario implementa el método de apply que puede utilizar como método de fábrica. Usted obtiene la ventaja de azúcar sintáctica de no tener que usar la nueva palabra clave.

Debido a que la clase es inmutable, obtienes accesores, que son solo las variables (o propiedades) de la clase pero no los mutadores (por lo tanto, no hay capacidad para cambiar las variables). Los parámetros del constructor están disponibles automáticamente como campos de solo lectura públicos. Mucho más agradable de usar que la construcción Java Bean.

  • También obtienes los hashCode , equals y toString de forma predeterminada y el método equals compara un objeto estructuralmente. Se genera un método de copy para poder clonar un objeto.

La mayor ventaja, como se ha mencionado anteriormente, es el hecho de que puede hacer un patrón de coincidencia en las clases de casos. La razón de esto es porque obtienes el método de no aplicación que te permite deconstruir una clase de caso para extraer sus campos.

En esencia, lo que está obteniendo de Scala al crear una clase de caso (o un objeto de caso si su clase no acepta argumentos) es un objeto singleton que sirve como propósito de fábrica y como extractor .


Las clases de casos se pueden ver como objetos de almacenamiento de datos simples e inmutables que deben depender exclusivamente de sus argumentos de constructor .

Este concepto funcional nos permite

  • use una sintaxis de inicialización compacta ( Node(1, Leaf(2), None)) )
  • descomponerlos mediante la 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 el interior o muestra otros tipos de comportamiento complejo, debe ser una clase ordinaria.


Nadie mencionó que el objeto compañero de la clase de caso tiene una tupled tupleada, que tiene un tipo:

case class Person(name: String, age: Int) //Person.tupled is def tupled: ((String, Int)) => Person

El único caso de uso que puedo encontrar es cuando necesita construir una clase de caso a partir de una tupla, por ejemplo:

val bobAsTuple = ("bob", 14) val bob = (Person.apply _).tupled(bobAsTuple) //bob: Person = Person(bob,14)

Puede hacer lo mismo, sin tuplas, creando un objeto directamente, pero si sus conjuntos de datos expresados ​​como lista de tuplas con aridad 20 (tupla con 20 elementos), pueden usar tuplas es su elección.


Nadie mencionó que las clases de casos también son instancias de Product y, por lo tanto, heredan estos métodos:

def productElement(n: Int): Any def productArity: Int def productIterator: Iterator[Any]

donde productArity devuelve el número de parámetros de clase, productElement(i) devuelve el parámetro i th , y productIterator permite iterar a través de ellos.


Nadie mencionó que las clases de casos tienen parámetros de constructor val , pero este también es el valor predeterminado para las clases regulares (lo que creo que es una inconsistencia en el diseño de Scala). Darío insinuó que cuando notó que son " inmutables ".

Tenga en cuenta que puede anular el valor predeterminado al anteponer cada argumento de constructor con var para clases de casos. Sin embargo, hacer que las clases de casos sean mutables hace que sus métodos equals y hashCode en el tiempo. [1]

sepp2k ya mencionó que las clases de casos generan automáticamente métodos equals y hashCode .

Además, nadie mencionó que las clases de casos crean automáticamente un object complementario con el mismo nombre que la clase, que contiene los métodos de apply y no apply . El método de apply permite construir instancias sin anteponer con new . El método de unapply extractor permite la coincidencia de patrones que otros mencionaron.

Además, el compilador optimiza la velocidad de match patrones de casos para las clases de casos [2].

[1] Clases de casos son geniales

[2] Clases de casos y extractores, pág . 15 .


Según la documentation de Scala:

Las clases de casos son solo clases regulares que son:

  • Inmutable por defecto
  • Descompone a través de la coincidencia de patrones
  • Comparado por igualdad estructural en lugar de por referencia
  • Sucinto para instanciar y operar en

Otra característica de la palabra clave del caso es que el compilador genera automáticamente varios métodos para nosotros, incluidos los métodos familiares de toString, equals y hashCode en Java.


Técnicamente, no hay diferencia entre una clase y una clase de caso, incluso si el compilador optimiza algunas cosas al usar clases de caso. Sin embargo, una clase de caso se utiliza para eliminar la placa de caldera para un patrón específico, que es la implementación de tipos de datos algebraicos .

Un ejemplo muy simple de este tipo 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 (a través de la coincidencia de patrones) con la misma sintaxis, que también es exactamente la forma en que se imprimen (menos espacios).

Y también se pueden utilizar con mapas o conjuntos hash, ya que tienen un código hash válido y estable.


Una clase de caso es una clase que puede usarse con la declaración de match/case .

def isIdentityFun(term: Term): Boolean = term match { case Fun(x, Var(y)) if x == y => true case _ => false }

Verá que a ese case le sigue una instancia de la clase Fun cuyo segundo parámetro es un Var. Esta es una sintaxis muy agradable y potente, pero no puede funcionar con instancias de ninguna clase, por lo tanto, existen algunas restricciones para las clases de casos. Y si estas restricciones se cumplen, es posible definir automáticamente el código hash y los iguales.

La frase vaga "un mecanismo de descomposición recursiva a través de la coincidencia de patrones" significa simplemente "funciona con el case ". (De hecho, la instancia seguida de match se compara con (comparada con) la instancia que sigue al case , Scala tiene que descomponerlos a ambos, y tiene que descomponer recursivamente de qué están hechos).

¿Para qué clases de casos son útiles? El artículo de Wikipedia sobre tipos de datos algebraicos da dos buenos ejemplos clásicos, listas y árboles. El soporte para tipos de datos algebraicos (incluido saber cómo compararlos) es una necesidad para cualquier lenguaje funcional moderno.

¿Para qué clases de casos no son útiles? Algunos objetos tienen estado, el código como connection.setConnectTimeout(connectTimeout) no es para clases de casos.

Y ahora puedes leer A Tour of Scala: Case Classes


  • Clases de casos pueden ser de patrón ajustado
  • Las clases de casos definen automáticamente el código hash y los iguales.
  • Las clases de casos definen automáticamente los métodos de obtención para los argumentos del constructor.

(Ya mencionaste todo menos el último).

Esas son las únicas diferencias con las clases regulares.


  • Las clases de casos definen un objeto compagnon con métodos de aplicar y no aplicar
  • Clases de caso se extiende Serializable
  • Las clases de casos definen métodos hashCode y copy iguales.
  • Todos los atributos del constructor son val (azúcar sintáctica).