2012-04-18 24 views
6

Say Tengo una función de tomar un argumentoScala transformación función

def fun(x: Int) = x 

Basado en eso, quiero generar una nueva función con la misma convención de llamada, pero que voy a aplicar un poco de transformación de sus argumentos antes de delegar a la función original. Por eso, pude

def wrap_fun(f: (Int) => Int) = (x: Int) => f(x * 2) 
wrap_fun(fun)(2) // 4 

¿Cómo puede uno ir haciendo lo mismo, con excepción de las funciones de cualquier aridad que sólo tiene la parte de los argumentos para aplicar la transformación en común?

def fun1(x: Int, y: Int) = x 
def fun2(x: Int, foo: Map[Int,Str], bar: Seq[Seq[Int]]) = x 

wrap_fun(fun1)(2, 4) // 4 
wrap_fun(fun2)(2, Map(), Seq()) // 4 

¿Cómo sería una definición wrap_fun haciendo las invocaciones anteriores funciona parecerse?

+0

Fwiw, tales cosas pueden ser realmente simples en los lenguajes dinámicos: http://ideone.com/MYP2W. – missingfaktor

Respuesta

6

Esto se puede hacer de forma directa utilizando bastante shapeless's instalaciones de abstracción sobre aridad función,

import shapeless._ 
import HList._ 
import Functions._ 

def wrap_fun[F, T <: HList, R](f : F) 
    (implicit 
    hl : FnHListerAux[F, (Int :: T) => R], 
    unhl : FnUnHListerAux[(Int :: T) => R, F]) = 
     ((x : Int :: T) => f.hlisted(x.head*2 :: x.tail)).unhlisted 

val f1 = wrap_fun(fun _) 
val f2 = wrap_fun(fun1 _) 
val f3 = wrap_fun(fun2 _) 

ejemplo de sesión de REPL,

scala> f1(2) 
res0: Int = 4 

scala> f2(2, 4) 
res1: Int = 4 

scala> f3(2, Map(), Seq()) 
res2: Int = 4 

Tenga en cuenta que no se puede aplicar la función envuelta inmediatamente (como en la pregunta) en lugar de a través de un valor asignado (como he hecho anteriormente) porque la lista de argumentos explícitos de la función envuelta se confundirá con la lista de argumentos implícitos de wrap_fun. Lo más cerca que podemos llegar a la forma en que la pregunta es por nombrar explícitamente el método apply como abajo,

scala> wrap_fun(fun _).apply(2) 
res3: Int = 4 

scala> wrap_fun(fun1 _).apply(2, 4) 
res4: Int = 4 

scala> wrap_fun(fun2 _).apply(2, Map(), Seq()) 
res5: Int = 4 

Aquí la mención explícita de apply marca sintácticamente de la primera aplicación (de wrap_fun junto con la lista de parámetros implícitos) desde la segunda aplicación (de la función transformada con su lista explícita de argumentos).

+0

¡Esto es genial! ¿Existe la posibilidad de obtener documentación más explícita de esto (específicamente, 'FnHListerAux' y' FnUnHListerAux') en la página de GitHub (ya sea en el archivo Léame o en la wiki)? – Destin

+0

Gracias. Sí, eso está en mi lista de cosas por hacer, pero si tiene el tiempo y la inclinación, una solicitud de documentación sería muy bienvenida :-) –

2

Dado que las funciones que toman diferentes números de argumentos son tipos diferentes, no relacionados, no se puede hacer de forma genérica. trait Function1 [-T1, +R] extends AnyRef y nada más. Necesitará un método diferente para cada arity.

+2

No diría 'no puedo'. [Shapeless] (https://github.com/milessabin/shapeless) tiene algunas características interesantes para abstraer sobre arity. Como [liftO] (https://github.com/milessabin/shapeless/blob/master/src/main/scala/shapeless/lift.scala) puede trabajar con funciones de aridad arbitraria, esto debería ser posible. – leedm777

+1

@dave Shapeless hace posible escribir todos los casos posibles: las tuplas y las funciones solo van al arity 22, ya que Scala tampoco puede abstraerlas, por lo que cada una debe definirse. –

+1

@daniel No, no hay necesidad de enumerar todos los casos para hacer esto sin forma ... ver mi respuesta. –

1

Mientras que voté y estoy de acuerdo con la respuesta de Luigi, porque, ya sabes ... él es derecho; Scala no tiene soporte directo incorporado para tal cosa; vale la pena señalar que lo que está tratando de hacer no es imposible; es solo que es un poco doloroso lograrlo, y, muchas veces, es mejor que simplemente implementes un método separado por arity deseado.

Dicho esto, sin embargo ... en realidad podemos hacer esto HList s. Si estás interesado en probarlo, naturalmente, necesitarás obtener una implementación de HList. Recomiendo utilizar el excelente proyecto shapeless de Miles Sabin y su implementación de HList s. De todos modos, aquí está un ejemplo de su uso que logra algo parecido a lo que parece que está buscando:

import shapeless._ 

trait WrapperFunner[T] { 
    type Inputs <: HList 
    def wrapFun(inputs: Inputs) : T 
} 

class WrapsOne extends WrapperFunner[Int] { 
    type Inputs = Int :: HNil 
    def wrapFun(inputs: Inputs) : Int = { 
    inputs match { 
     case num :: HNil => num * 2 
    } 
    } 
} 

class WrapsThree extends WrapperFunner[String] { 
    type Inputs = Int :: Int :: String :: HNil 
    def wrapFun(inputs: Inputs) : String = { 
    inputs match { 
     case firstNum :: secondNum :: str :: HNil => str + (firstNum - secondNum) 
    } 
    } 
} 

object MyApp extends App { 

    val wo = new WrapsOne 
    println(wo.wrapFun(1 :: HNil)) 
    println(wo.wrapFun(17 :: HNil)) 
    //println(wo.wrapFun(18 :: 13 :: HNil)) // Would give type error 

    val wt = new WrapsThree 
    println(wt.wrapFun(5 :: 1 :: "your result is: " :: HNil)) 
    val (first, second) = (60, 50) 
    println(wt.wrapFun(first :: second :: "%s minus %s is: ".format(first, second) :: HNil)) 
    //println(wt.wrapFun(1 :: HNil)) // Would give type error 

} 

Correr MyApp resultados en:

2 
34 
your result is: 4 
60 minus 50 is: 10 

O, extendida más cerca de su caso particular:

import shapeless._ 

trait WrapperFunner[T] { 
    type Inputs <: HList 
    def wrapFun(inputs: Inputs) : T 
} 

trait WrapperFunnerBase extends WrapperFunner[Int] { 
    // Does not override `Inputs` 
    def wrapFun(inputs: Inputs) : Int = { 
    inputs match { 
     case (num: Int) :: remainder => num 
    } 
    } 
} 

class IgnoresNothing extends WrapperFunnerBase { 
    type Inputs = Int :: HNil 
} 

class IgnoresLastTwo extends WrapperFunnerBase { 
    type Inputs = Int :: Int :: String :: HNil 
} 

object MyApp extends App { 

    val in = new IgnoresNothing 
    println(in.wrapFun(1 :: HNil)) 
    println(in.wrapFun(2 :: HNil)) 
    //println(in.wrapFun(3 :: 4 :: HNil)) // Would give type error 

    val ilt = new IgnoresLastTwo 
    println(ilt.wrapFun(60 :: 13 :: "stupid string" :: HNil)) 
    println(ilt.wrapFun(43 :: 7 :: "man, that string was stupid..." :: HNil)) 
    //println(ilt.wrapFun(1 :: HNil)) // Would give type error 

} 

resultados en:

1 
2 
60 
43 
+0

¡Realmente te has entristecido un poco! Vea mi respuesta para una solución mucho más simple usando informe. –

+0

Vaya ... s/mean/meal/ –

+0

@MilesSabin Sí, esperaba que mi camino no fuera el mejor posible. ¡Gracias por ofrecer una solución muy superior! – Destin

6

Como es habitual en Scala, hay otra forma de lograr lo que quiere hacer.

Aquí son una toma basado en currying del primer argumento, junto con los compose de Function1:

def fun1(x : Int)(y : Int) = x 
def fun2(x : Int)(foo : Map[Int, String], bar : Seq[Seq[Int]]) = x 

def modify(x : Int) = 2*x 

Los tipos resultantes como REPL muestra va a ser:

fun1: (x: Int)(y: Int)Int 
fun2: (x: Int)(foo: Map[Int,String], bar: Seq[Seq[Int]])Int 
modify: (x: Int)Int 

Y en vez de envolver las funciones fun1 y fun2, usted compose, como técnicamente, ahora son ambos objetos Function1.Esto le permite hacer llamadas como las siguientes:

(fun1 _ compose modify)(2)(5) 
(fun2 _ compose modify)(2)(Map(), Seq()) 

Ambos de los cuales volverá 4. Por supuesto, la sintaxis no es tan bonita, dado que hay que añadir el _ distinguir fun1 's aplicación de la función el objeto mismo (en el que desea llamar al método compose en este caso).

Por lo tanto, el argumento de Luigi de que es imposible en general sigue siendo válido, pero si puede curry sus funciones, puede hacerlo de esta manera.