2012-06-20 5 views
10

Clojure ofrece una macro llamada doto que lleva su argumento y una lista de funciones y, esencialmente, llama a cada función, anteponiendo el argumento (evaluado):`doto` de Scala

(doto (new java.util.HashMap) (.put "a" 1) (.put "b" 2)) 
-> {a=1, b=2} 

¿Hay alguna manera de poner en práctica algo similar en Scala? Me imagino algo con la siguiente forma:

val something = 
    doto(Something.getInstance) { 
    x() 
    y() 
    z() 
    } 

que será equivalente a

val something = Something.getInstance 
something.x() 
something.y() 
something.z() 

¿Sería posible utilizar scala.util.DynamicVariable s?

Tenga en cuenta que con los métodos de fábrica, como Something.getInstance, no es posible utilizar el patrón común Scala

val something = 
    new Something { 
    x() 
    y() 
    z() 
    } 
+0

Como nota al margen, esta pregunta es un dup. No puedo encontrar el original, sin embargo. –

Respuesta

12

No creo que hay una cosa tal incorporada en la biblioteca, pero puede imitar con bastante facilidad:

def doto[A](target: A)(calls: (A => A)*) = 
    calls.foldLeft(target) {case (res, f) => f(res)} 

Uso:

scala> doto(Map.empty[String, Int])(_ + ("a" -> 1), _ + ("b" ->2)) 
res0: Map[String,Int] = Map(a -> 1, b -> 2) 

scala> doto(Map.empty[String, Int])(List(_ + ("a" -> 1), _ - "a", _ + ("b" -> 2))) 
res10: Map[String,Int] = Map(b -> 2) 

Por supuesto, funciona siempre que su función devuelva el tipo correcto. En su caso, si la función sólo tiene efectos secundarios (que no es tan "scalaish"), que se pueden cambiar doto y utilizar foreach en lugar de foldLeft:

def doto[A](target: A)(calls: (A => Unit)*) = 
    calls foreach {_(target)} 

Uso:

scala> import collection.mutable.{Map => M} 
import collection.mutable.{Map=>M} 

scala> val x = M.empty[String, Int] 
x: scala.collection.mutable.Map[String,Int] = Map() 

scala> doto(x)(_ += ("a" -> 1), _ += ("a" -> 2)) 

scala> x 
res16: scala.collection.mutable.Map[String,Int] = Map(a -> 2) 
+0

Ese es un enfoque interesante. No pensé usar un doblez. Maravillas de la programación funcional ... – Ralph

+0

Cuidado, una vez más, de acuerdo con el ejemplo, el 'versión fold' no cabrá y usted tiene que pegarse a la' foreach'version – Nicolas

+0

Como le comenté a @unknown usuario, lo estoy tratando de utilizar este modelo para inicializar objetos de la biblioteca estándar de Java, específicamente un 'java.net.URLConnection' devuelto por una' java.net.HttpConnection.openConnection'. La API no fue diseñada con encadenamiento en mente. Desafortunadamente, al interactuar con las API de la biblioteca estándar y al tratar de escribir en un estilo funcional, los kludges son frecuentemente necesarios. – Ralph

4

creo, que lo más cerca habría que importar los miembros de este objeto en su alcance:

val something = ... 
import something._ 

x() 
y() 
z() 

en este post se puede encontrar otro ejemplo (en la sección "actualización pequeña sobre el terreno teórico"):

http://hacking-scala.posterous.com/side-effecting-without-braces


también pequeña ventaja de este enfoque - puede importar los miembros individuales y cambiar su nombre:

import something.{x, y => doProcessing} 
1

más sencilla supongo:

val hm = Map [String, Int]() + ("a"-> 1) + ("b"-> 2) 

Su muestra

val something = 
    doto (Something.getInstance) { 
    x() 
    y() 
    z() 
    } 

no se ve muy funcional, porque - ¿cuál es el resultado? Supongo que estás haciendo efecto secundario.

Something.x().y().z() 

podría ser una manera si cada llamada produce el tipo en el que la siguiente función puede actuar.

z(y(x(Something))) 

otro tipo de producir un resultado.

Y no es el método andThen al método de la cadena pide a las colecciones, es posible que desee echar un vistazo a.

para su mapa-ejemplo, un pliegue-izquierda es otra manera de ir:

val hm = Map [String, Int]() + ("a"-> 1) + ("b"-> 2) 

val l = List (("a", 8), ("b", 7), ("c", 9)) 
(hm /: l)(_ + _) 
// res8: scala.collection.immutable.Map[String,Int] = Map(a -> 8, b -> 7, c -> 9) 
+0

No es muy funcional, pero tampoco es la biblioteca estándar de Java. El ejemplo se aplica a muchas de las funciones de la biblioteca, como la inicialización de un componente Swing, una conexión URL (obtenida de una HttpConnection), etc. – Ralph

5

En Scala, la forma "típica" de hacer esto sería la cadena de "tap" o métodos de "tubo" . Estos no están en la biblioteca estándar, pero se definen con frecuencia como así:

implicit class PipeAndTap[A](a: A) { 
    def |>[B](f: A => B): B = f(a) 
    def tap[B](f: A => B): A = { f(a); a } 
} 

Luego lo haría

(new java.util.HashMap[String,Int]) tap (_.put("a",1)) tap (_.put("b",2)) 

Esto no es tan compacta como la versión Clojure (o tan compacto como Scala puede ser) , pero es casi lo más canónico que uno puede obtener.

(Nota: si desea minimizar la sobrecarga en tiempo de ejecución para la adición de estos métodos, puede hacer a un private val y tienen PipeAndTap extienden AnyVal, entonces esto va a ser una "clase de valor", que sólo se convierte en un verdadero clase cuando se necesita un objeto para pasar alrededor, sólo llamar a un método en realidad no requiere la creación de clases)

(Segunda nota:.. en versiones anteriores de Scala, implicit class no existe tienes que escribir por separado la clase y un implicit def que convierte un genérico a a un PipeAndTap.)

0

Bueno, se puede pensar en dos maneras de hacerlo: la transmisión de cadenas como parámetros, y tener un cambio macro de la cuerda y compilarlo, o simplemente importar los métodos. Si Scala hubiera descompuesto las macros, quizás podrían usarse también, ya que no las tiene, no voy a especular sobre ellas.

En cualquier caso, voy a salir de las alternativas macro a otros. La importación de los métodos es bastante simple:

val map = collection.mutable.Map[String, Int]() 
locally { 
    import map._ 
    put("a", 1) 
    put("b", 2) 
} 

Tenga en cuenta que locally no hace nada, excepto restringir el ámbito en el que se importan los miembros de map.

0

Una forma muy básica para la cadena de varias acciones es la composición de funciones:

val f:Map[Int,String]=>Map[Int,String] = _ + (1 -> "x") 
val g:Map[Int,String]=>Map[Int,String] = _ + (2 -> "y") 
val h:Map[Int,String]=>Map[Int,String] = _ + (3 -> "z") 

(h compose g compose f)(Map(42->"a")) 
// Map[Int,String] = Map((42,a), (1,x), (2,y), (3,z)) 

En este caso no es muy práctico, aunque, como el tipo de las funciones no se puede deducir fácilmente ...