superclase simple sencillo polimorfismo metodo llamar herencia ejemplos ejemplo como clase abstracta class scala inheritance enumeration

class - simple - super en java



clase de caso copia ''método'' con superclase (7)

Quiero hacer algo como esto:

sealed abstract class Base(val myparam:String) case class Foo(override val myparam:String) extends Base(myparam) case class Bar(override val myparam:String) extends Base(myparam) def getIt( a:Base ) = a.copy(myparam="changed")

No puedo, porque en el contexto de getIt, no le he dicho al compilador que cada base tiene un método de ''copia'', pero copiar tampoco es realmente un método, así que no creo que haya un rasgo o método abstracto Puedo poner en Base para que esto funcione correctamente. O, ¿está ahí?

Si trato de definir Base como abstract class Base{ def copy(myparam:String):Base } , entonces la case class Foo(myparam:String) extends Base resultados case class Foo(myparam:String) extends Base en la class Foo needs to be abstract, since method copy in class Base of type (myparam: String)Base is not defined

¿Hay alguna otra manera de decirle al compilador que todas las clases Base serán clases de casos en su implementación? ¿Algún rasgo que significa "tiene las propiedades de una clase de caso"?

Podría hacer que Base sea una clase de caso, pero luego recibo advertencias del compilador que dicen que la herencia de las clases de casos está en desuso.

Sé que también puedo:

def getIt(f:Base)={ (f.getClass.getConstructors.head).newInstance("yeah").asInstanceOf[Base] }

pero ... eso parece muy feo.

¿Pensamientos? ¿Todo mi enfoque es "incorrecto"?

ACTUALIZACIÓN Cambié la clase base para contener el atributo e hice que las clases de casos utilizaran la palabra clave "anular". Esto refleja mejor el problema real y hace que el problema sea más realista en consideración a la respuesta de Edmondo1984.


Creo que esto es para lo que son los métodos de extensión. Elija entre las estrategias de implementación para el método de copia en sí.

Me gusta que el problema se resuelva en un solo lugar.

Es interesante preguntar por qué no hay un rasgo para la mayúscula: no dice mucho sobre cómo invocar la copia, excepto que siempre se puede invocar sin args, copy() .

sealed trait Base { def p1: String } case class Foo(val p1: String) extends Base case class Bar(val p1: String, p2: String) extends Base case class Rab(val p2: String, p1: String) extends Base case class Baz(val p1: String)(val p3: String = p1.reverse) extends Base object CopyCase extends App { implicit class Copy(val b: Base) extends AnyVal { def copy(p1: String): Base = b match { case foo: Foo => foo.copy(p1 = p1) case bar: Bar => bar.copy(p1 = p1) case rab: Rab => rab.copy(p1 = p1) case baz: Baz => baz.copy(p1 = p1)(p1.reverse) } //def copy(p1: String): Base = reflect invoke //def copy(p1: String): Base = macro xcopy } val f = Foo("param1") val g = f.copy(p1="param2") // normal val h: Base = Bar("A", "B") val j = h.copy("basic") // enhanced println(List(f,g,h,j) mkString ", ") val bs = List(Foo("param1"), Bar("A","B"), Rab("A","B"), Baz("param3")()) val vs = bs map (b => b copy (p1 = b.p1 * 2)) println(vs) }

Solo por diversión, copia reflexiva:

// finger exercise in the api def copy(p1: String): Base = { import scala.reflect.runtime.{ currentMirror => cm } import scala.reflect.runtime.universe._ val im = cm.reflect(b) val ts = im.symbol.typeSignature val copySym = ts.member(newTermName("copy")).asMethod def element(p: Symbol): Any = (im reflectMethod ts.member(p.name).asMethod)() val args = for (ps <- copySym.params; p <- ps) yield { if (p.name.toString == "p1") p1 else element(p) } (im reflectMethod copySym)(args: _*).asInstanceOf[Base] }


Es un viejo problema, con una vieja solución,

https://code.google.com/p/scala-scales/wiki/VirtualConstructorPreSIP

hecho antes de que existiera el método de copia de clase de caso.

Entonces, en referencia a este problema, cada clase de caso DEBE ser un nodo hoja de todos modos, así que defina la copia y un MyType / thisType más la nueva función ThisT y se establece, cada clase de caso corrige el tipo. Si desea ampliar la función tree / newThis y usar parámetros predeterminados, tendrá que cambiar el nombre.

como comentario adicional: he estado esperando que la magia de los complementos de compilación mejore antes de implementar esto, pero las macros de tipo pueden ser el jugo mágico. Busque en las listas de Kevin''s AutoProxy para obtener una explicación más detallada de por qué mi código nunca llegó a ningún lado.


Esto funciona bien para mi:

sealed abstract class Base { def copy(myparam: String): Base } case class Foo(myparam:String) extends Base { override def copy(x: String = myparam) = Foo(x) } def copyBase(x: Base) = x.copy("changed") copyBase(Foo("abc")) //Foo(changed)



Si las dos clases de casos divergieran con el tiempo para que tengan campos diferentes, entonces el enfoque de copy compartida dejaría de funcionar.

Es mejor definir una def withMyParam(newParam: X): Base abstracta con def withMyParam(newParam: X): Base . Aún mejor, puede introducir un tipo abstracto para conservar el tipo de clase de caso a la vuelta:

scala> trait T { | type Sub <: T | def myParam: String | def withMyParam(newParam: String): Sub | } defined trait T scala> case class Foo(myParam: String) extends T { | type Sub = Foo | override def withMyParam(newParam: String) = this.copy(myParam = newParam) | } defined class Foo scala> scala> case class Bar(myParam: String) extends T { | type Sub = Bar | override def withMyParam(newParam: String) = this.copy(myParam = newParam) | } defined class Bar scala> Bar("hello").withMyParam("dolly") res0: Bar = Bar(dolly)


Esta es una respuesta anterior, antes de que se cambiara la pregunta.

Los lenguajes de programación fuertemente tipados evitan lo que estás tratando de hacer. Veamos por qué.

La idea de un método con la siguiente firma:

def getIt( a:Base ) : Unit

Es que el cuerpo del método podrá acceder a las propiedades visibles a través de la clase base o la interfaz, es decir, las propiedades y los métodos definidos únicamente en la clase Base / interfaz o sus padres. Durante la ejecución del código, cada instancia específica pasada al método getIt podría tener una subclase diferente, pero el tipo de compilación de a siempre será Base

Uno puede razonar de esta manera:

Ok, tengo una Base de clase, la heredo en dos clases de casos y agrego una propiedad con el mismo nombre, y luego trato de acceder a la propiedad en la instancia de Base.

Un simple ejemplo muestra por qué esto no es seguro:

sealed abstract class Base case class Foo(myparam:String) extends Base case class Bar(myparam:String) extends Base case class Evil(myEvilParam:String) extends Base def getIt( a:Base ) = a.copy(myparam="changed")

En el siguiente caso, si el compilador no arrojó un error en tiempo de compilación, significa que el código intentaría acceder a una propiedad que no existe en el tiempo de ejecución. Esto no es posible en los lenguajes de programación estrictamente tipados: has cambiado las restricciones en el código que puedes escribir para una verificación mucho más fuerte de tu código por parte del compilador, sabiendo que esto reduce drásticamente la cantidad de errores que tu código puede contener

Esta es la nueva respuesta. Es un poco largo porque se necesitan pocos puntos antes de llegar a la conclusión

Desafortunadamente, no puede confiar en el mecanismo de copiar clases de casos para implementar lo que propone. La forma en que funciona el método de copia es simplemente un constructor de copia que puede implementar usted mismo en una clase que no sea case. Vamos a crear una clase de caso y desmontarla en REPL:

scala> case class MyClass(name:String, surname:String, myJob:String) defined class MyClass scala> :javap MyClass Compiled from "<console>" public class MyClass extends java.lang.Object implements scala.ScalaObject,scala.Product,scala.Serializable{ public scala.collection.Iterator productIterator(); public scala.collection.Iterator productElements(); public java.lang.String name(); public java.lang.String surname(); public java.lang.String myJob(); public MyClass copy(java.lang.String, java.lang.String, java.lang.String); public java.lang.String copy$default$3(); public java.lang.String copy$default$2(); public java.lang.String copy$default$1(); public int hashCode(); public java.lang.String toString(); public boolean equals(java.lang.Object); public java.lang.String productPrefix(); public int productArity(); public java.lang.Object productElement(int); public boolean canEqual(java.lang.Object); public MyClass(java.lang.String, java.lang.String, java.lang.String); }

En Scala, el método de copia toma tres parámetros y puede eventualmente usar el de la instancia actual para el que no ha especificado (el idioma de Scala proporciona entre sus características valores predeterminados para los parámetros en las llamadas a métodos)

Pasemos a nuestro análisis y retomemos el código tal como se actualizó:

sealed abstract class Base(val myparam:String) case class Foo(override val myparam:String) extends Base(myparam) case class Bar(override val myparam:String) extends Base(myparam) def getIt( a:Base ) = a.copy(myparam="changed")

Ahora para hacer esta compilación, necesitaríamos usar en la firma de getIt(a:MyType) un MyType que respete el siguiente contrato:

Cualquier cosa que tenga un parámetro myparam y tal vez otros parámetros que tengan un valor predeterminado

Todos estos métodos serían adecuados:

def copy(myParam:String) = null def copy(myParam:String, myParam2:String="hello") = null def copy(myParam:String,myParam2:Option[Option[Option[Double]]]=None) = null

No hay forma de expresar este contrato en Scala, sin embargo, existen técnicas avanzadas que pueden ser útiles.

La primera observación que podemos hacer es que hay una relación estricta entre las case classes y las tuples en Scala. De hecho, las clases de casos son de alguna manera tuplas con comportamiento adicional y propiedades con nombre.

La segunda observación es que, dado que no se garantiza que el número de propiedades de la jerarquía de clases sea el mismo, no se garantiza que la firma del método de copia sea ​​la misma.

En la práctica, suponiendo que AnyTuple[Int] describe cualquier Tuple de cualquier tamaño donde el primer valor es de tipo Int, estamos buscando hacer algo como eso:

def copyTupleChangingFirstElement(myParam:AnyTuple[Int], newValue:Int) = myParam.copy(_1=newValue)

Esto no sería difícil si todos los elementos fueran Int . Una tupla con todos los elementos del mismo tipo es una List , y sabemos cómo reemplazar el primer elemento de una List . Tendríamos que convertir cualquier TupleX en List , reemplazar el primer elemento y convertir la List nuevamente en TupleX . Sí, tendremos que escribir todos los convertidores para todos los valores que X pueda asumir. Molesto pero no difícil

Sin embargo, en nuestro caso, no todos los elementos son Int . Queremos tratar a Tuple donde los elementos son de tipo diferente, como si todos fueran iguales si el primer elemento es un Int. Se llama

"Resumiendo sobre arity"

es decir, tratar tuplas de diferentes tamaños de forma genérica, independientemente de su tamaño. Para hacerlo, debemos convertirlos en una lista especial que admita tipos heterogéneos, denominados HList

Conclusión

La herencia de las clases de casos ha quedado obsoleta por una buena razón, como puede descubrir a partir de varias publicaciones en la lista de correo: http://www.scala-lang.org/node/3289

Tienes dos estrategias para lidiar con tu problema:

  1. Si tiene un número limitado de campos que necesita cambiar, use un enfoque como el sugerido por @Ron, que tiene un método de copia. Si quieres hacerlo sin perder información tipo, me gustaría generar la clase base

    sealed abstract class Base[T](val param:String){ def copy(param:String):T } class Foo(param:String) extends Base[Foo](param){ def copy(param: String) = new Foo(param) } def getIt[T](a:Base[T]) : T = a.copy("hello") scala> new Foo("Pippo") res0: Foo = Foo@4ab8fba5 scala> getIt(res0) res1: Foo = Foo@5b927504 scala> res1.param res2: String = hello

  2. Si realmente desea abstraerse por encima de la realidad, una solución es usar una biblioteca desarrollada por Miles Sabin llamada Shapeless. Hay una pregunta aquí que se ha formulado después de una discusión: ¿las listas HL no son más que una forma enrevesada de escribir tuplas? pero te digo que esto te va a dar un poco de dolor de cabeza


TL; DR: Logré declarar el método de copia en Base mientras dejo que el compilador genere automáticamente sus implementaciones en las clases de casos derivados. Esto implica un pequeño truco (y en realidad yo solo rediseñé la jerarquía de tipos) pero al menos demuestra que puedes hacer que funcione sin escribir el código de la placa de la caldera en ninguna de las clases de casos derivados.

En primer lugar, y como ya mencionaron Ron y Edmondo1984, tendrá problemas si las clases de su caso tienen campos diferentes.

Sin embargo, me limitaré estrictamente a su ejemplo, y supongo que todas las clases de su caso tienen los mismos campos (mirando su enlace github, este también es el caso de su código real).

Dado que todas las clases de su caso tienen los mismos campos, los métodos de copy autogenerados tendrán la misma firma, lo que es un buen comienzo. Parece razonable agregar simplemente la definición común en Base , como hiciste: abstract class Base{ def copy(myparam: String):Base } El problema ahora es que Scala no generará los métodos de copy , porque ya hay uno en la clase base.

Resulta que hay otra manera de garantizar estáticamente que Base tenga el método de copy correcto, y es mediante tipado estructural y anotación de tipo propio:

type Copyable = { def copy(myParam: String): Base } sealed abstract class Base(val myParam: String) { this : Copyable => }

Y a diferencia de nuestro intento anterior, esto no impedirá que Scala genere automáticamente los métodos de copy . Hay un último problema: la anotación de tipo propio asegura que las subclases de Base tengan un método de copy , pero no hace que esté disponible públicamente en Base :

val foo: Base = Foo("hello") foo.copy() scala> error: value copy is not a member of Base

Para solucionar esto, podemos agregar una conversión implícita de Base a Copyable. Un simple elenco servirá, ya que una Base garantiza ser Copyable:

implicit def toCopyable( base: Base ): Base with Copyable = base.asInstanceOf[Base with Copyable]

Concluyendo, esto nos da:

object Base { type Copyable = { def copy(myParam: String): Base } implicit def toCopyable( base: Base ): Base with Copyable = base.asInstanceOf[Base with Copyable] } sealed abstract class Base(val myParam: String) { this : Base. Copyable => } case class Foo(override val myParam: String) extends Base( myParam ) case class Bar(override val myParam: String) extends Base( myParam ) def getIt( a:Base ) = a.copy(myParam="changed")

Efecto de bonificación: si tratamos de definir una clase de caso con una firma diferente, obtenemos un error de compilación:

case class Baz(override val myParam: String, truc: Int) extends Base( myParam ) scala> error: illegal inheritance; self-type Baz does not conform to Base''s selftype Base with Base.Copyable

Para finalizar, una advertencia: probablemente solo deberías revisar tu diseño para evitar tener que recurrir al truco anterior. En su caso, la sugerencia de ron de usar una única clase de caso con un campo de tipo etype adicional parece más que razonable.