2012-10-07 28 views
6

Dado algo como:Extracto método de función independiente en Scala

class A { 
    def f(x: X) = ... 
    def g(y: Y, z: Z) = ... 
    ... 
} 

Cómo extraer (automáticamente) la función (s):

object A { 
    val f' = (a: A, x: X) => a.f(x) // do this automagically for any arbitrary f 
    val g' = (a: A, y: Y, z: Z) => a.g(y, z) // and deal with arity > 1 too 
    ... 
} 

Con esta firma tipo exacto (en primer lugar el objeto, luego la lista de parámetros). Permítanme decir el problema con toda claridad:

"Teniendo en cuenta un método f(x1: X1, ..., xn: Xn) se define en el contexto de una clase A, cómo extraer automáticamente una función f' que (i) recibe una instancia a de tipo A y (ii) una lista de parámetros que corresponde 1: 1 a la lista de parámetros de f, viz x1: X, ... xn: Xn, que la implementación es exactamente a.f(x1: X1, ..., xn: Xn) "

.

O incluso:

Capturar el concepto de extensionalidad de cálculo lambda, tales que extraiga automáticamente una λx.(f x) de f siempre x no aparece libre en f.

Esta primera podría abordarse mediante la búsqueda de un camino de acceso, los identificadores fg, ... sin tener una específica a: A (uno podría tener una específica A después, por supuesto). Simplemente podríamos escribir f' o g' a mano, pero permitamos DRYness.


P.S. Tal vez esto no sea posible sin la reflexión en tiempo de ejecución (aunque puede ser posible con las macros Scala 2.10+), ya que no puedo encontrar una manera de referirme a los identificadores de f o g sin una instancia específica (a: A) de antemano. Pero sería algo como lo siguiente, sin tener que recurrir a strings:

A.getMethod("f"): Function2[A, X, ...] 

También me di cuenta de que los usos prácticos de la pregunta puede ayudar a los participantes a sugerir alternativas, pero estoy discutiendo esto en un sentido abstracto . No estoy tratando de resolver otro problema que he reducido a este. Estoy tratando de saber si es posible :-) Aquí hay un very nice article para entender realmente la motivación detrás de esta pregunta, con comentarios sobre Eta-expansions en Scala.

+0

El uso de la reflexión para tales fines en realidad no es una buena idea. Inevitablemente resultará ser una sobrecomplicación, reducirá el rendimiento y destruirá todo el punto de utilizar un lenguaje estático, ya que todas las resoluciones de métodos e invocaciones se realizarán en tiempo de ejecución. Realmente debería considerar diferentes enfoques para su problema, como, por ejemplo, el uso de implícitos. Probablemente extender su pregunta con la información adecuada podría ayudar a otros a ayudarlo con eso. –

+0

@NikitaVolkov Estoy de acuerdo contigo. La reflexión es mala. Pero si se resuelve en tiempo de compilación, entonces no hay ninguna razón para reducir el rendimiento ni perder la seguridad de tipo. –

+0

La reflexión no puede resolverse en tiempo de compilación, ya que el objetivo es introspectar su programa en tiempo de ejecución. Desde 2.10 hay una función de macros en Scala, pero al ser capaz de resolver su problema en tiempo de compilación definitivamente parece ser una exageración. No soy un experto en eso, así que no puedo ayudarte con eso. Y, por cierto, en el estado actual, su pregunta es muy amplia y es probable que esté cerrada. Es difícil obtener lo que en realidad estás preguntando aquí. –

Respuesta

2

Sin duda podría hacer esto en tiempo de compilación con macros. Hago algo muy parecido en the preview release of ScalaMock 3 - los objetos simulados son instancias de clases anónimas donde cada miembro es implementado por una instancia de una función simulada. Es posible que pueda usarlo como punto de partida para lo que está tratando de hacer.

Advertencia: ScalaMock 3 actualmente solo funciona con Scala 2.10.0-M6.No funciona con M7 o la versión de desarrollo actual debido a los cambios de última hora en la macro API que no (¡todavía!) Tuve la oportunidad de abordar.

+0

¡Gracias por los comentarios! ¿Podría dar algunos consejos? :-) –

+0

No estoy seguro de poder decir mucho más que sea muy útil, me temo. Estoy seguro de que lo que intentas hacer es posible, pero no va a ser trivial.Sospecho que, como tuve que hacer en ScalaMock, probablemente tendrías que construir el árbol que deseas "a mano", y vas a encontrar muchas de las mismas complicaciones que he relacionado con el manejo métodos parametrizados y sobrecargados. El archivo fuente para mirar es https://github.com/paulbutcher/ScalaMock/blob/develop/core/src/main/scala/org/scalamock/Mock.scala - Sospecho que podría usarlo como punto de partida . –

+0

También debe tener en cuenta que esta área está muy cambiada en este momento. El código que acabo de señalar funciona contra M6, pero está completamente roto contra M7 (y aún más contra la versión de desarrollo actual de 2.10). –

0

Esto se parece al proyecto Lensed que crea lenses para las clases de casos de Scala. Creo que sería posible modificarlo para crear los métodos que describes en lugar de los lentes.

+1

Desafortunadamente Lensed usa un plugin de compilador. Como sé por la amarga experiencia con ScalaMock 2, existen limitaciones significativas con los complementos de compilación (razón por la cual ScalaMock 3 se está moviendo a usar macros) –

-2

Sé que no es una solución particularmente hermosa, pero si no puede usar macros, podría generar código fuente para un objeto complementario que necesita y compilarlo en un paso de compilación independiente.

def generateCompanionObject(clazz: Class[_]) = { 
    val className = clazz.getName.split("\\.").last 
    val firstMethod = clazz.getMethods.head//for simplicity 
    val methodName = firstMethod.getName 
    val parametersClasses = firstMethod.getParameterTypes.map(_.getName).toSeq 

    val objectDefinition = "object " + className + " {\n" + 
     generateMethodDefinition(className, methodName, parametersClasses) + 
     "\n}" 

    objectDefinition 
} 

def generateMethodDefinition(className: String, methodName: String, parameterClasses: Seq[String]) = { 
    val newMethodName: String = " def invoke" + methodName.capitalize + "On" 

    val parameterList: String = "(o:" + className + ", " + parameterClasses.zipWithIndex.map { 
     case (argClassName, index) => "arg" + index + ": " + argClassName 
    }.mkString(", ") + ")" 

    val generateOldMethodCall: String = "o." + methodName + parameterClasses.zipWithIndex.map { 
     case (argClassName, index) => "arg" + index 
    }.mkString("(", ",", ")") 

    newMethodName + parameterList + " = " + generateOldMethodCall 
} 

para la clase

class A { 
    def foo(x: String, y: String) = x + y 
} 

generará

object A { 
    def invokeFooOn(o:A, arg0: java.lang.String, arg1: java.lang.String) = o.foo(arg0,arg1) 
}