Fast Immutable Collections (FIC)

repository·master·Indexed 42 days ago

https://github.com/marcglasberg/fast_immutable_collections

A high-performance Dart library providing immutable data structures including IList, ISet, IMap, and IMapOfSets. It offers efficient alternatives to standard Dart collections, featuring lock/unlock extensions for mutable-immutable transformation, deep equality by default, and optimized performance by saving differences rather than copying entire structures.

Tokens
29.7K
Snippets
96
Records
126
Agent score
52%

What's inside Fast Immutable Collections

  1. Overview of Fast Immutable Collections (FIC)

    master

    Fast Immutable Collections (FIC) is a Dart package providing high-performance immutable collection types. It is designed to be easier to use than built_collection (no manual mutable/immutable cycles required) and significantly faster than kt_dart by internally saving only the differences between collections rather than copying the entire structure.

    Core Immutable Collections:

    • IList: An immutable list.
    • ISet: An immutable set.
    • IMap: An immutable map.
    • IMapOfSets: An immutable map of sets (multimap).

    Key Features:

    • Lock/Unlock Extensions: Easily transform mutable collections to immutable ones (e.g., [1, 2].lock) and vice-versa.
    • Configuration: Global and local settings for equality, sorting, caching, and flushing.
    • Value-Object Support: Optional deep equalities and cached hashCodes.
    • Efficient Mutable Alternatives:
      • ListSet: An efficient, fixed-size mutable collection that acts as both an ordered Set and a List.
      • ListMap: A mutable, fixed-size, ordered map with efficient List methods like sort and shuffle.
    • Native Extensions: Extensive extensions for native List, Set, Map, Iterable, Iterator, and more (e.g., [1, 2, 1].removeDuplicates()).
    • Const Support: Ability to create const versions of IList, ISet, and IMap.
  2. Overview of Fast Immutable Collections

    master
    Fast Immutable Collections is a Dart library providing high-performance immutable collection implementations. It is designed to offer efficient, immutable alternatives to standard Dart collections, suitable for performance-critical applications and state management patterns where immutability is required.
  3. Performance and memory benefits of Immutable collections

    master

    While mutable collections are often faster for single operations, fast_immutable_collections provides several performance and memory advantages:

    • O(1) Deep Copies: Creating a 'copy' of an immutable list is instantaneous because it only requires passing a reference.
    • Efficient Insertions: Unlike array-lists that require shifting elements, immutable lists can record changes (like inserting at index 0) without massive data movement.
    • O(1) Equality Checks: Comparing two immutable collections can be done via reference equality (checking if they are the same object in memory), which is much faster than O(N) value equality.
    • Lazy HashCode Caching: Since the collection cannot change, its hashCode can be calculated lazily and cached, speeding up its use in Sets or as Map keys.
    • Reduced Defensive Copying: You don't need to manually copy collections before passing them to untrusted code, saving both CPU and memory.
  4. Use ListSet for ordered, fixed-size sets

    master

    A ListSet is a hybrid collection that acts as both a mutable, fixed-sized, ordered Set and a mutable, fixed-sized List where values cannot repeat.

    Key Characteristics:

    • Performance: Similar to LinkedHashSet for set operations, but uses less memory.
    • Indexing: Supports efficient constant-time access by index.
    • Ordering: Can be sorted or rearranged like a list.
    • Limitation: It has a fixed size (unlike LinkedHashSet).

    Use ListSet when you need a set that maintains order, allows efficient index-based access, and requires less memory than a standard LinkedHashSet.

    ListSet<int> listSet = ListSet.of([1, 2, 3]);
    expect(listSet[2], 3); // List behavior
    expect(listSet.contains(2), isTrue); // Set behavior
    
    List<int> list = listSet;
    Set<int> set = listSet;
  5. Use fixed-size immutable lists (IListOf2, IListOf3, IListOf4)

    master

    The library provides specialized immutable list types for small, fixed numbers of elements to improve performance and clarity:

    • IListOf2<T>: Contains exactly 2 items, accessible via .first and .last.
    • IListOf3<T>: Contains exactly 3 items, accessible via .first, .second, and .third.
    • IListOf4<T>: Contains exactly 4 items, accessible via .first, .second, .third, and .fourth.
  6. How to create immutable objects in Dart

    master

    While Dart cannot strictly prevent subclassing (which could introduce mutability), you can follow these 5 rules to create effectively immutable objects:

    1. Make all immutable fields final or private so they cannot be changed.
    2. Make all mutable fields private to prevent direct external access.
    3. Create defensive copies of all mutable fields passed to the constructor to prevent external modification of the internal state.
    4. Do not provide methods that grant external access to internal mutable fields.
    5. Do not provide methods that change fields, except for safe internal uses like lazy initialization or caching.

    Best Practice for Subclassing: To get as close to strict immutability as possible, avoid invoking public non-static methods from the constructor, as these can be overridden in subclasses to return different values.

  7. When to use Fast Immutable Collections (FIC)

    master

    Deciding whether to use FIC over native mutable collections should be based on your specific performance requirements and architectural goals.

    Performance Considerations

    Performance gains from immutable collections can be highly dependent on your specific application, target platform, and real-world usage. It is recommended to perform actual profiling of your application to identify if collection operations are a genuine bottleneck.

    Architectural Benefits

    Even if performance is not the primary driver, the main reasons to use FIC are:

    • Readability: Code is easier to reason about when data structures are immutable.
    • Maintainability: Immutability reduces side effects and state-related bugs.
    • Sanity: It provides a low-cost way to implement immutability at the bottom-most layer of your program without manually creating complex immutable structures.

    Implementation Strategy

    FIC uses a strategy of recording changes and periodically "flushing" them into regular mutable Dart collections. This approach leverages the speed of native Dart collections while providing an immutable interface, often resulting in significant performance improvements over pure manual implementations.

  8. Use ListMap for ordered, fixed-size maps

    master

    A ListMap is a mutable, fixed-sized, and ordered map.

    Key Characteristics:

    • Ordering: Maintains order like a LinkedHashMap.
    • Memory: Uses less memory than a LinkedHashMap.
    • Performance: Slightly slower than LinkedHashMap for key-based retrieval.
    • List-like features: Provides efficient sort and shuffle methods, and allows efficient access by index via entryAt, keyAt, and valueAt.
    • Limitation: It has a fixed size (unlike LinkedHashMap).
  9. Handle type inference with IList

    master

    When using the .lock extension, Dart's type inference might result in a more specific type than expected.

    • [Dog()].lock results in IList<Dog>.
    • If you need a more general type, specify the type explicitly: <Animal>[Dog()].lock results in IList<Animal>.

    This behavior is consistent with how Dart handles list literals, where [Dog()] is inferred as List<Dog>.

    IList<Animal> ilist1 = [Dog()].lock;       // IList<Dog>
    IList<Animal> ilist2 = <Animal>[Dog()].lock; // IList<Animal>
  10. Understand the difference between Unmodifiable and Immutable collections

    master

    In Dart, List.unmodifiable() creates an unmodifiable list, which is a read-only view. While it prevents mutation via its own methods (throwing errors instead), it does not guarantee that the underlying data won't change if another reference to the same data exists. This can lead to confusion and bugs.

    In contrast, Immutability in fast_immutable_collections is a guarantee that the object cannot be mutated. To enforce this at the type level, IList (and other FIC collections) does not extend List. This ensures that if a method accepts an IList, it is mathematically impossible for that method to modify the collection, making the code easier to reason about and providing 'failure atomicity' (objects are never left in an indeterminate state if an exception occurs).

  11. How ListSet and ListMap work internally

    master

    To support both sorted and insertion-ordered sets and maps, FIC implements specialized internal data structures:

    • ListSet: Used internally by ISet. It maintains both a HashMap and an array-list (rather than a linked-list). This allows the set to be both sorted and insertion-ordered while remaining memory-efficient and allowing index-based access.
    • ListMap: An analogous data structure for maps that uses a HashMap and an array-list internally.

    This design differs from standard implementations like Java's LinkedHashMap, which uses a doubly-linked list to manage order. By using an array-list, FIC can optimize for fixed-size or periodically flushed structures.

  12. Use IList equality and comparison methods

    master

    Unlike regular Dart Lists which compare by identity, ILists compare by deep equality (same items in the same order) by default. This allows IList to be used as a key in a Map.

    Comparison Methods

    • equalItems: Returns true if items are equal and in the same order (can be slow for large lists).
    • unorderedEqualItems: Returns true if items are the same regardless of order.
    • equalItemsAndConfig: Returns true if items are equal, in order, AND the list configurations match.
    • same: Returns true if the internal state (not necessarily the exact instance) is the same. This is a fast identity-based check.
    var ilist1 = [1, 2, 3].lock;
    var ilist2 = [1, 2, 3].lock;
    
    print(identical(ilist1, ilist2)); // False
    print(ilist1 == ilist2);        // True (Deep equality)