Me veo usando más y más tipos inmutables cuando no se espera que las instancias de la clase cambien . Requiere más trabajo (ver el ejemplo a continuación), pero facilita el uso de los tipos en un entorno multiproceso.
Al mismo tiempo, rara vez veo tipos inmutables en otras aplicaciones, incluso cuando la mutabilidad no beneficiaría a nadie.
Pregunta: ¿Por qué los tipos inmutables se usan tan raramente en otras aplicaciones?
- ¿Es esto porque es más largo escribir código para un tipo inmutable,
- ¿O me estoy perdiendo algo y hay algunos inconvenientes importantes al usar tipos inmutables?
Ejemplo de la vida real
Digamos que obtienes Weather
de una API RESTful como esa:
public Weather FindWeather(string city)
{
// TODO: Load the JSON response from the RESTful API and translate it into an instance
// of the Weather class.
}
Lo que generalmente veríamos es (nuevas líneas y comentarios eliminados para acortar el código):
public sealed class Weather
{
public City CorrespondingCity { get; set; }
public SkyState Sky { get; set; } // Example: SkyState.Clouds, SkyState.HeavySnow, etc.
public int PrecipitationRisk { get; set; }
public int Temperature { get; set; }
}
Por otro lado, lo escribiría de esta manera, dado que obtener un Weather
API de la API y luego modificarlo sería extraño: cambiar Temperature
o Sky
no cambiaría el clima en el mundo real, y cambiar CorrespondingCity
tampoco tiene sentido.
public sealed class Weather
{
private readonly City correspondingCity;
private readonly SkyState sky;
private readonly int precipitationRisk;
private readonly int temperature;
public Weather(City correspondingCity, SkyState sky, int precipitationRisk,
int temperature)
{
this.correspondingCity = correspondingCity;
this.sky = sky;
this.precipitationRisk = precipitationRisk;
this.temperature = temperature;
}
public City CorrespondingCity { get { return this.correspondingCity; } }
public SkyState Sky { get { return this.sky; } }
public int PrecipitationRisk { get { return this.precipitationRisk; } }
public int Temperature { get { return this.temperature; } }
}
fuente
{get; private set;}
, e incluso la variable mutable debería tener un constructor, porque todos esos campos siempre deberían establecerse y ¿por qué no aplicarías eso? Hacer esos dos cambios perfectamente razonables los lleva a la paridad de característica y LoC.Respuestas:
Programa en C # y Objective-C. Realmente me gusta la escritura inmutable, pero en la vida real siempre me he visto obligado a limitar su uso, principalmente para tipos de datos, por las siguientes razones:
StringBuilder
oUriBuilder
en C #, oWeatherBuilder
en su caso. Esta es la razón principal para mí, ya que muchas clases que diseño no merecen tal esfuerzo.En resumen, la inmutabilidad es buena para los objetos que se comportan como valores, o que solo tienen algunas propiedades. Antes de hacer algo inmutable, debe considerar el esfuerzo necesario y la usabilidad de la clase en sí después de hacerla inmutable.
fuente
En general, los tipos inmutables creados en lenguajes que no giran en torno a la inmutabilidad tenderán a costar más tiempo al desarrollador para crearlos y potencialmente a usarlos si requieren algún tipo de objeto "generador" para expresar los cambios deseados (esto no significa que el trabajo será más, pero hay un costo por adelantado en estos casos). Además, independientemente de si el lenguaje hace que sea realmente fácil crear tipos inmutables o no, siempre requerirá algo de procesamiento y sobrecarga de memoria para los tipos de datos no triviales.
Hacer que las funciones carezcan de efectos secundarios
Si está trabajando en idiomas que no giran en torno a la inmutabilidad, entonces creo que el enfoque pragmático no es tratar de hacer que cada tipo de datos sea inmutable. Una mentalidad potencialmente mucho más productiva que le brinda muchos de los mismos beneficios es enfocarse en maximizar la cantidad de funciones en su sistema que causan cero efectos secundarios .
Como ejemplo simple, si tiene una función que causa un efecto secundario como este:
Entonces no necesitamos un tipo de datos entero inmutable que prohíba a los operadores como la asignación posterior a la inicialización para que esa función evite los efectos secundarios. Simplemente podemos hacer esto:
Ahora la función no se mete
x
ni nada fuera de su alcance, y en este caso trivial, incluso podríamos haber afeitado algunos ciclos evitando cualquier sobrecarga asociada con la indirección / alias. Por lo menos, la segunda versión no debería ser más costosa desde el punto de vista computacional que la primera.Cosas que son caras de copiar en su totalidad
Por supuesto, la mayoría de los casos no son tan triviales si queremos evitar que una función cause efectos secundarios. Un caso de uso complejo en el mundo real podría ser más parecido a esto:
En ese momento, la malla puede requerir un par de cientos de megabytes de memoria con más de cien mil polígonos, incluso más vértices y bordes, múltiples mapas de textura, objetivos de transformación, etc. Sería muy costoso copiar toda la malla solo para hacer esto
transform
Función libre de efectos secundarios, así:Y es en estos casos donde copiar algo en su totalidad normalmente sería una sobrecarga épica en la que he encontrado útil convertirme
Mesh
en una estructura de datos persistente y en un tipo inmutable con el "generador" analógico para crear versiones modificadas de la misma. puede simplemente copiar superficialmente y contar piezas que no son únicas. Todo se centra en poder escribir funciones de malla que no tengan efectos secundarios.Estructuras de datos persistentes
Y en estos casos donde copiar todo es tan increíblemente costoso, encontré el esfuerzo de diseñar un sistema inmutable
Mesh
que realmente valga la pena a pesar de que tenía un costo inicial ligeramente elevado, ya que no solo simplificaba la seguridad del hilo. También simplificó la edición no destructiva (permitiendo al usuario aplicar capas a las operaciones de malla sin modificar su copia original), deshacer sistemas (ahora el sistema de deshacer puede almacenar una copia inmutable de la malla antes de los cambios realizados por una operación sin destruir memoria use), y excepción-seguridad (ahora si ocurre una excepción en la función anterior, la función no tiene que retroceder y deshacer todos sus efectos secundarios ya que no causó ninguno para empezar).Puedo decir con confianza en estos casos que el tiempo requerido para hacer que estas estructuras de datos pesadas sean inmutables ahorró más tiempo del que costó, ya que comparé los costos de mantenimiento de estos nuevos diseños con los anteriores que giraban en torno a la mutabilidad y las funciones que causan efectos secundarios, y los diseños mutables anteriores costaban mucho más tiempo y eran mucho más propensos a errores humanos, especialmente en áreas que son realmente tentadores para los desarrolladores que descuidan durante el tiempo de crisis, como la seguridad de excepción.
Por lo tanto, creo que los tipos de datos inmutables realmente dan resultado en estos casos, pero no todo tiene que hacerse inmutable para que la mayoría de las funciones de su sistema estén libres de efectos secundarios. Muchas cosas son lo suficientemente baratas como para copiarlas en su totalidad. Además, muchas aplicaciones del mundo real necesitarán causar algunos efectos secundarios aquí y allá (al menos como guardar un archivo), pero generalmente hay muchas más funciones que podrían estar desprovistas de efectos secundarios.
El punto de tener algunos tipos de datos inmutables para mí es asegurarnos de que podamos escribir el número máximo de funciones para estar libres de efectos secundarios sin incurrir en una sobrecarga épica en forma de copia profunda de estructuras de datos masivas de izquierda a derecha en su totalidad cuando solo pequeñas porciones de ellos necesitan ser modificados. Tener estructuras de datos persistentes en esos casos termina convirtiéndose en un detalle de optimización que nos permite escribir nuestras funciones para estar libres de efectos secundarios sin pagar un costo épico por hacerlo.
Sobrecarga inmutable
Ahora conceptualmente, las versiones mutables siempre tendrán una ventaja en eficiencia. Siempre existe esa sobrecarga computacional asociada con estructuras de datos inmutables. Pero me pareció un intercambio digno en los casos que describí anteriormente, y puede concentrarse en hacer que los gastos generales sean lo suficientemente mínimos por naturaleza. Prefiero ese tipo de enfoque donde la corrección se vuelve fácil y la optimización se vuelve más difícil en lugar de que la optimización sea más fácil, pero la corrección se vuelve más difícil. No es tan desmoralizador tener un código que funcione perfectamente correctamente y necesite más ajustes sobre el código que no funciona correctamente en primer lugar, sin importar cuán rápido logre sus resultados incorrectos.
fuente
El único inconveniente que se me ocurre es que, en teoría, el uso de datos inmutables puede ser más lento que los mutables: es más lento crear una nueva instancia y recopilar la anterior que modificar la existente.
El otro "problema" es que no solo puedes usar tipos inmutables. Al final, tiene que describir el estado y debe usar tipos mutables para hacerlo, sin cambiar el estado no puede hacer ningún trabajo.
Pero aún así, la regla general es usar tipos inmutables siempre que sea posible y hacer que los tipos sean mutables solo cuando realmente haya una razón para hacerlo ...
Y para responder la pregunta " ¿Por qué los tipos inmutables se usan tan raramente en otras aplicaciones? " - Realmente no creo que estén ... donde sea que miren, todos recomiendan hacer que sus clases sean tan inmutables como puedan ser ... por ejemplo: http://www.javapractices.com/topic/TopicAction.do?Id=29
fuente
Car
objeto mutable se actualiza continuamente con la ubicación de un automóvil físico en particular, entonces si tengo una referencia a ese objeto, puedo averiguar el paradero de ese automóvil de manera rápida y fácil. SiCar
fuera inmutable, encontrar el paradero actual probablemente sería mucho más difícil.Para modelar cualquier sistema del mundo real en el que las cosas puedan cambiar, el estado mutable deberá codificarse en alguna parte, de alguna manera. Hay tres formas principales en que un objeto puede mantener un estado mutable:
Usar primero facilita que un objeto realice una instantánea inmutable del estado actual. Usar el segundo facilita que un objeto cree una vista en vivo del estado actual. Usar el tercero a veces puede hacer que ciertas acciones sean más eficientes en casos en los que hay poca necesidad esperada de instantáneas inmutables ni vistas en vivo.
Más allá del hecho de que actualizar el estado almacenado usando una referencia mutable a un objeto inmutable es a menudo más lento que actualizar el estado almacenado usando un objeto mutable, usar una referencia mutable requerirá que uno renuncie a la posibilidad de construir una vista en vivo barata del estado. Si uno no necesita crear una vista en vivo, eso no es un problema; sin embargo, si fuera necesario crear una vista en vivo, la imposibilidad de usar una referencia inmutable hará que todas las operaciones con la vista, tanto lecturas como escriturasMucho más lento de lo que serían de otra manera. Si la necesidad de instantáneas inmutables excede la necesidad de vistas en vivo, el rendimiento mejorado de las instantáneas inmutables puede justificar el impacto de rendimiento para las vistas en vivo, pero si uno necesita vistas en vivo y no necesita instantáneas, utilizar referencias inmutables a objetos mutables es la forma ir.
fuente
En su caso, la respuesta se debe principalmente a que C # tiene un soporte deficiente para la inmutabilidad ...
Sería genial si:
todo será inmutable por defecto a menos que se indique lo contrario (es decir, con una palabra clave 'mutable'), mezclar tipos inmutables y mutables es confuso
los métodos de mutación (
With
) estarán disponibles automáticamente, aunque esto ya se puede lograr, vea Conhabrá una manera de decir que el resultado de una llamada a un método específico (es decir
ImmutableList<T>.Add
) no se puede descartar o al menos producirá una advertenciaY principalmente si el compilador podría garantizar la inmutabilidad en la medida de lo posible (consulte https://github.com/dotnet/roslyn/issues/159 )
fuente
MustUseReturnValueAttribute
atributo personalizado que hace exactamente eso.PureAttribute
tiene el mismo efecto y es aún mejor para esto.¿Ignorancia? ¿Inexperiencia?
Los objetos inmutables son ampliamente considerados como superiores hoy en día, pero es un desarrollo relativamente reciente. Los ingenieros que no se han mantenido actualizados o simplemente están atrapados en "lo que saben" no los usarán. Y se necesitan algunos cambios de diseño para usarlos de manera efectiva. Si las aplicaciones son viejas, o los ingenieros tienen habilidades de diseño débiles, usarlas puede ser incómodo o problemático.
fuente