Digamos que tengo los siguientes dos case class
es:
case class Address(street: String, city: String, state: String, zipCode: Int)
case class Person(firstName: String, lastName: String, address: Address)
y la siguiente instancia de Person
clase:
val raj = Person("Raj", "Shekhar", Address("M Gandhi Marg",
"Mumbai",
"Maharashtra",
411342))
Ahora si quiero actualizar zipCode
de raj
entonces tendré que hacer:
val updatedRaj = raj.copy(address = raj.address.copy(zipCode = raj.address.zipCode + 1))
Con más niveles de anidación, esto se vuelve aún más feo. ¿Hay una forma más limpia (algo así como Clojure update-in
) de actualizar estas estructuras anidadas?
scala
case-class
zipper
missingfaktor
fuente
fuente
Respuestas:
Cremalleras
Huet's Zipper proporciona un recorrido conveniente y una 'mutación' de una estructura de datos inmutable. Scalaz proporciona cremalleras para
Stream
( scalaz.Zipper ) yTree
( scalaz.TreeLoc ). Resulta que la estructura de la cremallera se deriva automáticamente de la estructura de datos original, de una manera que se asemeja a la diferenciación simbólica de una expresión algebraica.Pero, ¿cómo te ayuda esto con tus clases de casos Scala? Bueno, Lukas Rytz recientemente creó un prototipo de una extensión para scalac que crearía automáticamente cremalleras para clases de casos anotados. Reproduciré su ejemplo aquí:
Por lo tanto, la comunidad necesita persuadir al equipo de Scala de que este esfuerzo debe continuar e integrarse en el compilador.
Por cierto, Lukas publicó recientemente una versión de Pacman, programable por el usuario a través de un DSL. Sin embargo, no parece que haya usado el compilador modificado, ya que no puedo ver ninguna
@zip
anotación.Reescritura de árboles
En otras circunstancias, es posible que desee aplicar alguna transformación en toda la estructura de datos, de acuerdo con alguna estrategia (de arriba hacia abajo, de abajo hacia arriba) y en función de las reglas que coinciden con el valor en algún punto de la estructura. El ejemplo clásico es transformar un AST para un idioma, tal vez para evaluar, simplificar o recopilar información. Kiama admite Reescritura , vea los ejemplos en RewriterTests y vea este video . Aquí hay un fragmento para abrir el apetito:
Tenga en cuenta que Kiama sale del sistema de tipos para lograr esto.
fuente
Es curioso que nadie haya agregado lentes, ya que fueron HECHOS para este tipo de cosas. Entonces, aquí hay un documento de antecedentes de CS sobre él, aquí hay un blog que trata brevemente sobre el uso de lentes en Scala, aquí hay una implementación de lentes para Scalaz y aquí hay un código que lo usa, que sorprendentemente se parece a su pregunta. Y, para reducir la placa de la caldera, aquí hay un complemento que genera lentes Scalaz para clases de casos.
Para obtener puntos de bonificación, aquí hay otra pregunta SO que toca lentes y un artículo de Tony Morris.
Lo importante de las lentes es que son compostables. Por lo tanto, son un poco engorrosos al principio, pero siguen ganando terreno cuanto más los usas. Además, son excelentes para la capacidad de prueba, ya que solo necesita probar lentes individuales y puede dar por sentado su composición.
Entonces, basado en una implementación proporcionada al final de esta respuesta, así es como lo haría con lentes. Primero, declare lentes para cambiar un código postal en una dirección y una dirección en una persona:
Ahora, compónelos para obtener una lente que cambie el código postal de una persona:
Finalmente, use esa lente para cambiar raj:
O, usando un poco de azúcar sintáctica:
O incluso:
Aquí está la implementación simple, tomada de Scalaz, utilizada para este ejemplo:
fuente
personZipCodeLens.set(raj, personZipCodeLens.get(raj) + 1)
es el mismo quepersonZipCodeLens mod (raj, _ + 1)
mod
no es primitivo para lentes.Herramientas útiles para usar lentes:
Solo quiero agregar que los proyectos Macrocosmos y Rillit , basados en macros Scala 2.10, proporcionan la creación dinámica de lentes.
Usando Rillit:
Usando Macrocosmos:
fuente
He estado buscando qué biblioteca de Scala que tenga la mejor sintaxis y la mejor funcionalidad y una biblioteca que no se menciona aquí es el monóculo, que para mí ha sido realmente bueno. A continuación se muestra un ejemplo:
Estos son muy agradables y hay muchas formas de combinar las lentes. Scalaz, por ejemplo, exige una gran cantidad de repeticiones y esto se compila rápido y funciona muy bien.
Para usarlos en su proyecto simplemente agregue esto a sus dependencias:
fuente
Sin forma hace el truco:
con:
Tenga en cuenta que si bien algunas otras respuestas aquí le permiten componer lentes para profundizar en una estructura dada, estas lentes sin forma (y otras bibliotecas / macros) le permiten combinar dos lentes no relacionadas de modo que pueda hacer lentes que establezcan un número arbitrario de parámetros en posiciones arbitrarias en tu estructura Para estructuras de datos complejas, esa composición adicional es muy útil.
fuente
Lens
código en la respuesta de Daniel C. Sobral y evité agregar una dependencia externa.Debido a su naturaleza composable, las lentes proporcionan una solución muy agradable al problema de las estructuras muy anidadas. Sin embargo, con un bajo nivel de anidamiento, a veces siento que las lentes son demasiado, y no quiero presentar el enfoque de lentes completos si solo hay pocos lugares con actualizaciones anidadas. En aras de la exhaustividad, aquí hay una solución muy simple / pragmática para este caso:
Lo que hago es simplemente escribir algunas
modify...
funciones auxiliares en la estructura de nivel superior, que se ocupan de la copia anidada fea. Por ejemplo:Mi objetivo principal (simplificar la actualización en el lado del cliente) se logra:
Crear el conjunto completo de ayudantes de modificación es obviamente molesto. Pero para cosas internas, a menudo está bien crearlas la primera vez que intente modificar un determinado campo anidado.
fuente
Quizás QuickLens coincida mejor con su pregunta. QuickLens usa macros para convertir una expresión amigable IDE en algo que está cerca de la declaración de copia original.
Dados los dos ejemplos de clases de casos:
y la instancia de la clase Person:
puede actualizar el código postal de raj con:
fuente