que por objetos objeto mutables modificar inmutables inmutable estructuras clases scala map immutability mutable use-case

scala - por - objetos mutables e inmutables



Scala mapa inmutable, ¿cuándo ir mutable? (4)

Mi caso de uso actual es bastante trivial, ya sea que el Mapa mutable o inmutable hará el truco.

Tener un método que toma un Mapa inmutable, que luego llama a un método API de terceros que también toma un Mapa inmutable

def doFoo(foo: String = "default", params: Map[String, Any] = Map()) { val newMap = if(someCondition) params + ("foo" -> foo) else params api.doSomething(newMap) }

El Mapa en cuestión generalmente será bastante pequeño, como máximo podría haber una Lista de instancias de clases de casos incrustada, con un máximo de unos miles de entradas. Entonces, nuevamente, supongamos que el impacto es inmutable en este caso (es decir, que tiene esencialmente 2 instancias del Mapa a través de la nueva copia val val).

Aún así, me molesta un poco, copiar el mapa solo para obtener un nuevo mapa con algunas entradas k-> v pegadas en él.

Podría ir mutable y params.put("bar", bar) , etc. para las entradas que quiero params.toMap , y luego params.toMap para convertir a inmutable para la llamada api, esa es una opción. pero luego tengo que importar y pasar los mapas mutables, lo que es un poco complicado en comparación con ir con el Mapa inmutable predeterminado de Scala.

Entonces, ¿cuáles son las pautas generales para cuando está justificado / es una buena práctica utilizar un Mapa mutable sobre un Mapa inmutable?

Gracias

EDITAR , entonces, parece que una operación de adición en un mapa inmutable toma un tiempo casi constante, confirmando la afirmación de @dhg y @ Nicolas de que no se hace una copia completa, lo que resuelve el problema para el caso concreto presentado.


Además de la respuesta de dhg, puede echar un vistazo al rendimiento de las colecciones de scala . Si una operación de agregar / quitar no toma un tiempo lineal, debe hacer algo más que simplemente copiar la estructura completa. (Tenga en cuenta que lo contrario no es cierto: no es lógico que le lleve un tiempo lineal que copie toda la estructura)


Dependiendo de la implementación del Mapa inmutable, agregar algunas entradas puede que no copie todo el Mapa original. Esta es una de las ventajas del enfoque de la estructura de datos inmutables: Scala intentará salir copiando lo menos posible.

Este tipo de comportamiento es más fácil de ver con una List . Si tengo un val a = List(1,2,3) , entonces esa lista se almacena en la memoria. Sin embargo, si antepago un elemento adicional como val b = 0 :: a , obtengo una nueva List 4 elementos, pero Scala no copió la lista original a . En su lugar, solo creamos un nuevo enlace, lo llamamos b , y le dimos un puntero a la Lista existente a .

También puede visualizar estrategias como esta para otros tipos de colecciones. Por ejemplo, si agrego un elemento a un Map , la colección podría simplemente envolver el mapa existente, recurriendo a él cuando sea necesario, y al mismo tiempo proporcionar una API como si fuera un solo Map .


Me gusta utilizar collections.maps como los tipos de parámetros declarados (valores de entrada o retorno) en lugar de mapas mutables o inmutables. Los mapas de colecciones son interfaces inmutables que funcionan para ambos tipos de implementaciones. Un método de consumo que usa un mapa realmente no necesita conocer la implementación de un mapa o cómo se construyó. (Realmente no es de su incumbencia).

Si opta por el método de ocultar la construcción particular de un mapa (ya sea mutable o inmutable) a los consumidores que lo usan, entonces todavía está obteniendo un mapa esencialmente inmutable río abajo. Y al usar collection.Map como una interfaz inmutable, elimina por completo toda la ineficiencia ".toMap" que tendría con los consumidores escritos para utilizar objetos tipificados de Immutable.Map. Tener que convertir un mapa completamente construido en otro simplemente para cumplir con una interfaz que no es compatible con la primera es una sobrecarga absolutamente innecesaria cuando se piensa en ello.

Sospecho que dentro de unos años veremos los tres conjuntos separados de interfaces (mapas mutables, mapas inmutables y mapas de colecciones) y nos daremos cuenta de que el 99% de las veces solo 2 son realmente necesarias (mutables y colecciones) y el uso de la interfaz de mapa inmutable predeterminada (desafortunadamente) realmente agrega una gran cantidad de gastos generales innecesarios para el "Idioma escalable".


Usar un objeto mutable no es malo en sí mismo, se vuelve malo en un entorno de programación funcional, donde intenta evitar los efectos secundarios manteniendo las funciones puras y los objetos inmutables.

Sin embargo, si crea un objeto mutable dentro de una función y modifica este objeto, la función seguirá siendo pura si no libera una referencia a este objeto fuera de la función. Es aceptable tener código como:

def buildVector( x: Double, y: Double, z: Double ): Vector[Double] = { val ary = Array.ofDim[Double]( 3 ) ary( 0 ) = x ary( 1 ) = y ary( 2 ) = z ary.toVector }

Ahora, creo que este enfoque es útil / recomendado en dos casos: (1) Rendimiento, si crear y modificar un objeto inmutable es un cuello de botella en toda su aplicación; (2) Legibilidad del código, porque a veces es más fácil modificar un objeto complejo en su lugar (en lugar de recurrir a lentes, cremalleras, etc.)