built_collection de Dart para colecciones inmutables

Hace un mes hablé en el Dart Developer Summit sobre modelos de objetos con source_gen (video). Mencioné algunos paquetes y técnicas que…

Hace un mes hablé en el Dart Developer Summit sobre modelos de objetos con source_gen (video). Mencioné algunos paquetes y técnicas que merecen una mirada más detallada, así que aquí estoy para contar la historia completa.

Primero: built_collection.

El paquete built_collection proporciona colecciones tipo SDK con inmutabilidad. Inmutabilidad para:

  • Simplicidad, conveniencia. Una colección inmutable se puede pasar sin preocuparse por quién podría modificarla.

  • Rendimiento. La mutabilidad conduce a patrones costosos como la copia defensiva y la detección de cambios.

El hecho de que built_collection se suponga conveniente plantea algunos desafíos para la API. Echemos un vistazo. Primero, una lista inmutable:

var list = new BuiltList<int>([1, 2, 3]);

Ahora me gustaría añadirle un valor. Por supuesto, es inmutable, así que no puedo; lo que realmente quiero es crear una nueva lista con un valor adicional añadido al final.

Aquí es donde nos encontramos con el patrón builder por el cual el paquete recibe su nombre. Obtienes una nueva lista convirtiendo a un builder, haciendo modificaciones, y luego construyendo:

var builder = list.toBuilder();
builder.add(4);
var newList = builder.build();

Los builders se usan normalmente inline, así que hagámoslo así:

var newList = (list.toBuilder()..add(4)).build()

Esto es hasta donde llega normalmente el patrón builder, pero Dart tiene lambdas, así que podemos hacerlo mejor:

var newList = list.rebuild((b) => b.add(4));

Lo que encontramos es que el encadenamiento de métodos en Dart hace que el patrón builder sea muy poderoso. Casi siempre puedes hacer lo que quieras inline:

var newList = list.rebuild((b) => b
    ..add(4)
    ..addAll([7, 6, 5])
    ..sort()
    ..remove(1));

Así que, como esperaba — built_collection es conveniente.

Hablemos de rendimiento.

Las Built Collections están actualmente optimizadas para código que tiene un ciclo de actualización/publicación. Eso significa: las colecciones se convierten a un builder, se actualizan, se construyen, y luego se publican — se pasa una referencia — para que el resto del código las consuma.

Esto se ajusta al uso típico en aplicaciones web, por ejemplo apps de Angular. Allí, una interacción del usuario o respuesta RPC desencadena una actualización. El modelo de datos se reconstruye antes de publicarse para renderización.

Las Built Collections aún no están optimizadas para reconstrucciones muy frecuentes, lo cual requerirría implementarse sobre estructuras de datos cuidadosamente elegidas. Si tienes un caso de uso para esto, por favor ponte en contacto — es parte del diseño de Built Collections que esto sea posible cuando se necesite.

Más allá de la inmutabilidad, built_collection te ayuda a escribir código bueno y correcto. Todo un conjunto de propiedades ayuda aquí:

  • Las Built Collections deben crearse con parámetros de tipo explícitos; no hay tal cosa como una BuiltList<dynamic>.
new BuiltList([1, 2, 3]);     // Throws an exception!
new BuiltList<int>([1, 2, 3]); // Better.
  • Los valores deben ser no nulos; los nulos no se pueden añadir a una Built Collection.
new BuiltList([1, 2, null]);  // Throws an exception!
  • Las Built Collections son comparables y hashable. Esto significa que puedes ponerlas en maps, sets y multimaps, creando exactamente las colecciones que coincidan con tus datos.

Estas propiedades son particularmente adecuadas para colecciones de objetos de negocio: en una aplicación web de compras, por ejemplo, para contener el contenido actual del carrito de compras del usuario.

Esa fue una vista rápida de lo que proporciona built_collection y por qué; más información está disponible en la página de github. Para hacer el mejor uso de colecciones inmutables, sin embargo, necesitas clases inmutables. Lo siguiente: built_value!

Edición: siguiente artículo.

Más de Dart