2011-01-23 6 views
5

Supongamos que tengo una clase de caso simple que envuelve enteros, y un método de orden superior que acepta una función que transporta enteros a envoltorios.¿Por qué reemplazar mi clase de caja Scala con un extractor rompió mi función de orden superior?

case class Wrapper(x :Int) 
def higherOrder(f : Int => Wrapper) = println(f(42)) 

Luego, puedo llamar a la función de orden superior, pasando la función de aplicación generada del contenedor. Sorprendentemente, también puedo pasar el nombre del envoltorio.

higherOrder(Wrapper.apply) // okay 
higherOrder(Wrapper)  // okay, wow! 

Esto es realmente genial. Nos permite tratar el nombre de la clase de caso como una función, que fomenta abstracciones expresivas. Para ver un ejemplo de esta frialdad, mira la respuesta aquí. What does "abstract over" mean?

Supongamos que mi clase de caso no es lo suficientemente potente, y necesito crear un extractor en su lugar. Como un caso de uso levemente artificial, digamos que necesito poder hacer coincidir patrones en cadenas que se convierten en enteros.

// Replace Wrapper case class with an extractor 
object Wrapper { 
    def apply(x :Int) = new Wrapper(x) 
    def unapply(s :String) :Option[Wrapper] = { 
     // details elided 
    } 
} 
class Wrapper(x :Int) { 
    override def toString = "Wrapper(" + x + ")" 
    // other methods elided 
} 

Bajo este cambio, todavía puedo pasar Wrapper.apply en mi función de orden superior, pero al pasar en tan sólo Envoltura ya no funciona.

higherOrder(Wrapper.apply) // still okay 
higherOrder(Wrapper)  // DOES NOT COMPILE 
      ^^^^^^^ 
// type mismatch; found : Wrapper.type (with underlying type Wrapper) 
// required: (Int) => Wrapper 

Ouch! He aquí por qué esta asimetría es preocupante. El consejo de Odersky, Spoon y Venners (Programación en Scala, página 500), dice

Siempre puede comenzar con las clases de casos y luego, si es necesario, cambiar a los extractores. Debido a que los patrones sobre los extractores y los patrones sobre las clases de casos se ven exactamente iguales en Scala, las coincidencias de patrones en sus clientes continuarán funcionando.

Muy cierto, por supuesto, pero romperemos a nuestros clientes si están utilizando nombres de clases de casos como funciones. Y como hacerlo permite abstracciones poderosas, algunos seguramente lo harán.

Por lo tanto, al pasarlos a funciones de orden superior, ¿cómo podemos hacer que los extractores se comporten de la misma manera que las clases de casos?

+3

Por favor, no se sienta avergonzado de aceptar una respuesta para cualquiera de sus preguntas. Realmente es muy aceptable socialmente, y casi todos lo han hecho en algún momento. –

Respuesta

8

Scala objetos extractora de compañía deben extenderse Función

Para las clases de casos, el compilador genera un objeto acompañante bajo el capó. Esto contiene métodos estáticos relevantes para la clase de caso Wrapper, incluido apply. Observando el bytecode, descubrimos que el objeto complementario de Wrapper se extiende a Function1. (En realidad, se extiende AbstractFunction1, que usa @specialized para obviar el autoboxing.)

Esto también se menciona aquí. Why do case class companion objects extend FunctionN?

Cuando reemplacemos nuestra clase de estuche Wrapper con un extractor, debemos tener cuidado de ampliar nuestro objeto compañero de fabricación casera con AbstractFunction1 para preservar la compatibilidad con versiones anteriores para nuestros clientes.

Esto representa una pequeña modificación en el código fuente. El método de aplicación no cambia en absoluto.

object Wrapper extends scala.runtime.AbstractFunction1[Int, Wrapper] { 
+1

Esto realmente no tiene nada que ver con el extractor 'unapply', por lo que referirlo a esto como" objetos complementarios del extractor "es un poco incorrecto. Creo que el término correcto debería ser "objetos complementarios de fábrica" ​​(es decir, aquellos que implementan 'aplicar' como una forma de construir un nuevo objeto). –

+0

@Ken, gracias por tu comentario. ¿Por qué tendría que reemplazar una clase de caso con un objeto complementario de fábrica? Es precisamente que estoy reemplazando un patrón de comparación menos poderoso por uno más poderoso que mi problema. Necesito un extractor que sea exactamente equivalente a mi clase de caso, por lo que mi código heredado no se romperá. –

+2

reemplazó la clase de caso con un objeto complementario que tenía dos funciones: una de esas funciones era como una fábrica (la función 'aplicar') y la otra como un extractor (la función' unapply'). Aunque su motivación fue mejorar el aspecto del extractor de este objeto complementario, los problemas que estamos discutiendo provienen de la semántica del aspecto de fábrica, y no tienen nada que ver con la función 'unpply' en el objeto complementario. Un ejemplo mínimo con 'clase C; el objeto C {def apply (x: Int) = ...} 'tendría el mismo problema. –

12

En pocas palabras, Wrapper (el objeto) no es una función, es sólo un objeto con un método de aplicación. Esto no tiene absolutamente nada que ver con que el objeto sea también un extractor.

Prueba esto:

class Wrapper(val x: Int) { 
    override def toString = "Wrapper(" + x + ")" 
    // other methods elided 
} 

object Wrapper extends (Int => Wrapper) { 
    def apply(x: Int) = new Wrapper(x) 
    def unapply(w: Wrapper): Option[Int] = Some(w.x) 
} 

def higherOrder(f: Int => Wrapper) = println(f(42)) 

También hice el parámetro a Wrapper un val cambiado de sitio y el parámetro y el valor de retorno en su definición de unapply para que se correspondería con el comportamiento de casos y clase.

Cuestiones relacionadas