2010-10-22 13 views
5

En Rails, se podría utilizar:¿Hay un análogo en Scala para el método de "devolución" de Rails?

returning Person.create do |p| 
    p.first_name = "Collin" 
    p.last_name = "VanDyck" 
end 

evitar tener que hacer esto:

person = Person.create 
person.first_name = "Collin" 
person.last_name = "VanDyck" 
person 

creo que la primera forma es más limpio y menos repetitivo. Me encuentro creación de este método en mis proyectos Scala:

def returning[T](value: T)(fn: (T) => Unit) : T = { 
    fn(value) 
    value 
} 

sé que es de utilidad algo limitada debido a la tendencia de los objetos que son inmutables, pero por ejemplo, trabajando con Ascensor, usando este método en las clases Mapper funciona bastante bien

¿Existe un análogo de Scala para "regresar" del que no tengo conocimiento? ¿O hay una forma similar de hacer esto en Scala que es más idiomática?

Respuesta

5

realidad no se puede mejorar mucho más de lo que ya ha escrito. Como correctamente señaló, la Scala idiomática tiende a favorecer a los objetos inmutables, por lo que este tipo de cosas tiene un uso limitado.

Además, como una línea, ¡en realidad no es tan doloroso implementarlo si lo necesita!

def returning[T](value: T)(fn: T => Unit) : T = { fn(value); value } 
+0

Gracias por la respuesta. Pensé que este era probablemente el caso, pero después de aprender a usar Option/Box con map/flatMap/foreach, he estado ansioso por encontrar una forma más de Scala para abordar los problemas. – Collin

3

que haría:

scala> case class Person(var first_name: String = "", var last_name: String = "") 
defined class Person 

scala> Person(first_name="Collin", last_name="VanDyck") 
res1: Person = Person(Collin,VanDyck) 
+0

Gracias por la pedófula de respuesta, pero esto es realmente más una cuestión de la mejor manera de devolver un valor arbitrario, pero para permitir algunas operaciones a través de una función de nivel superior sin contaminar el ámbito externo con otra variable referencia. – Collin

+0

No veo tu punto. ¿Cómo puede una persona (first_name = "Collin", last_name = "VanDyck") contaminar el alcance externo? – pedrofurla

+0

Por cierto, este enfoque no requiere, pero permite, vars en lugar de vals. – pedrofurla

6

Su método se ve bien, aunque yo suelo hacer esto añadiendo un método de efectos secundarios, que pueden incluir el cambio de estado interno (o también cosas como println):

class SideEffector[A](a: A) { 
    def effect(f: (A => Any)*) = { f.foreach(_(a)); a } 
} 
implicit def can_have_side_effects[A](a: A) = new SideEffector(a) 

scala> Array(1,2,3).effect(_(2) = 5 , _(0) = -1) 
res0: Array[Int] = Array(-1, 2, 5) 

Editar: solo en caso de que no está claro cómo esto podría ser útil en el original ejemplo:

Person.create.effect(
    _.first_name = "Collin", 
    _.last_name = "VanDyck" 
) 

Editar: cambiar el nombre del método de "efecto". No sé por qué no fui de esa manera antes - lado efecto, no lado efecto para el nombramiento.

+0

Tuve que leer esto un par de veces para asimilarlo por completo. Gran solución, definitivamente la usaré, gracias. – Collin

+0

Lo uso principalmente así: 'list.map (_._ 2 <7) .effect (println) .filter (_._ 3 ==" yes ")'. Ser capaz de configurar campos o hacer otras actualizaciones es solo un feliz accidente. (Bueno, no del todo un accidente, pero ese no es mi caso de uso normal.) –

+0

Creo que Ruby llama a esto .tap - ¡tengo que decir que me gusta más tu nombre! –

2

No entiendo por qué Vasil suprime su propia respuesta, pero me gusta mucho (que era precisamente lo que iba a sugerir):

val person = Person.create 
locally { import person._ 
    first_name = "Collin" 
    last_name = "VanDyck" 
} 
person 

Una de las características de la gente ha estado pidiendo es la capacidad de importar automáticamente algo. Si fuera posible, entonces se podría hacer esto:

def returning[T](import value: T)(fn: => Unit) : T = { fn; value } 

returning(Person.create) { 
    first_name = "Collin" 
    last_name = "VanDyck" 
} 

Eso no es posible por el momento, ni está en la hoja de ruta Scala. Pero algunas personas piden algo así de vez en cuando.

+0

No sabía acerca de localmente, gracias. El primer ejemplo que diste es lo que trato de evitar (val persona ...; ... persona). Creo que la razón por la que he estado buscando esto es porque vine a Scala desde Ruby, donde llegamos bastante al Object # instance_eval :) – Collin

1

Otra sugerencia sería usar el forward pipe operator de Scalaz.

val person = Person.create |> { p => 
    p.firstName = "Collin" 
    p.lastName = "VanDyck" 
    p // or p.saveMe 
} 

La diferencia es que tendrá que devolver el valor usted mismo, si desea asignarlo.Si no necesita el valor devuelto (como en su ejemplo inicial), las cosas son más fáciles:

Person.create |> { p => 
    p.firstName = "Collin" 
    p.lastName = "VanDyck" 
    p.save 
} 

Y listo.

Era reacio a utilizarlo realmente en mi propio código (aunque soy partidario de esta manera de hacerlo, pero solo está documentado en scalaz y quizás sea difícil de descifrar para otras personas que miran el código), así que espero que estos ejemplos funcionen.

Por supuesto, puede definir su propia 'tubería de avance y retorno' usando |>.

class ReturningPipe[A](value: A) { 
    import Scalaz._ 
    def |>>[B](f: A => B):A = value.|>(a => { f(a); value}) 
} 
implicit def returningPipe[A](value: A) = new ReturningPipe(value) 
1

Es posible evitar la repetición del nombre de la variable, así:

val person = Person.create 
locally { import person._ 
    first_name = "Collin" 
    last_name = "VanDyck" 
} 

Tenga en cuenta que esto sólo funciona para val s. Además, locally es un método Predef que ayuda a crear bloques solo para limitar el alcance de la variable, sin contravenir la inferencia de punto y coma de Scala. Esto evita que la importación se interponga en su camino una vez que haya terminado de inicializar a la persona.

Cuestiones relacionadas