dart-lang/language

Make it easy/efficient to create immutable collections via literals

kevmoo opened this issue ยท 5 comments

See

https://github.com/dart-lang/sdk/blob/031e77eea11a16a5e486a3673549adbd68862c8e/sdk/lib/core/uri.dart#L2233-L2237

      // ASCII, a single percent encoded sequence.
      codeUnits = new List(3);
      codeUnits[0] = _PERCENT;
      codeUnits[1] = _hexDigits.codeUnitAt(char >> 4);
      codeUnits[2] = _hexDigits.codeUnitAt(char & 0xf);

Also in the SDK,
2x in this file https://github.com/dart-lang/sdk/blob/031e77eea11a16a5e486a3673549adbd68862c8e/sdk/lib/vmservice/message.dart#L176-L182

4x in this file https://github.com/dart-lang/sdk/blob/031e77eea11a16a5e486a3673549adbd68862c8e/runtime/bin/vmservice/loader.dart#L1107-L1112

This entire file in pkg:isolate - https://github.com/dart-lang/isolate/blob/master/lib/src/util.dart

In the Dart sources in Google (some of these duplicate the above)

  • List.unmodifiable\(\[ - 135 hits
  • List\<.+\>\.unmodifiable\(\[ - 96 times
  • Map\<.+,.+>\.unmodifiable\(\{ - 25 times
  • Map\.unmodifiable\(\{ - 28 times
  • UnmodifiableSetView from pkg:collection is used dozens of times, not as a view over mutable data, but as a immutable set.

Benefits:

  • Less copying!
  • Immutable collections are more efficient
    • iteration doesn't need to track version, etc
    • no need to have indirection to a fixed-sized backing store to support growing
  • Enables special-casing implementations - e.g. immutable([1,2,3]) could be created as Uint8List.

Related: dart-lang/sdk#27755

In Flutter as soon as you pass a collection of objects, typically a List<Widget> but not always, to a widget, conceptually you are giving up write ownership to that list. IOW, the list becomes read-only. It is a common mistake to pass a list, mutate it, then expect Flutter to update the UI correctly. Nothing in the type system of collections prevents this mistake, and it is a very natural one to make when you are coming from a non-functional world.

CC @lrhn, who has written a bit of the code that created fixed-size lists for efficiency.

The first four examples you link to don't create read-only lists, they create mutable fixed-size ones. Likewise, uses of unmodifiable() and friends don't always create immutable lists, they create read-only views of an otherwise mutable list. In many cases, that view is the only way to get to the list, so it is effectively immutable, but that's not true for some unknown fraction of those cases.

I really think we need to be clear and explicit when we talk about this. I know of, at least:

  • Fixed-size lists โ€” you can change elements but not the length. This matters because if the list can never be resized than internally you don't need an extra indirection between the list object and its backing storage.

  • Constant lists โ€” it contains only data known at compile time and the contents never change.

  • Immutable lists โ€” once created, no piece of data in it ever changes. It may contain data known only at runtime.

  • Frozen lists โ€” the list can be mutated for a while. Then at some point, it gets locked down and further changes are prohibited (usually by way of a runtime error).

  • Read-only views โ€” some code is given an object that lets them access the list's data but not modify it. Other code may be able to modify that same list in which cases even users of the read-only view may observe that modification.

  • Persistent lists โ€” you can "modify" the list but doing so always creates a new list and the API for working with the list reflects that explicitly (i.e. add() returns a List, not void). References to the original list never see the change.

  • Copy-on-write lists โ€” if you pass a list to another function, it is passed efficiently without copying it eagerly. But if that function modifies the list, the list is copied and only that function sees the modification. The list that the caller has is left unchanged.

Each of these has different use cases, affordances, and constraints. Before we go in the direction of any of these (beyond what we already have), I think we need clearer data about which specific goals users have.

Updated the title to explicitly call out "immutable". Even in the cases in the SDK where we're creating (strictly speaking) fixed-sized lists, immutable would be more desirable.

See also #2477