Git Product home page Git Product logo

built_collection.dart's Introduction

Build pub package package publisher

Introduction

Built Collections are immutable collections using the builder pattern.

Each of the core SDK collections is split in two: a mutable builder class and an immutable "built" class. Builders are for computation, "built" classes are for safely sharing with no need to copy defensively.

Immutable collections work particularly well with immutable values. See built_value.

You can read more about built_collection on medium.

Design

Built Collections:

  • are immutable, if the elements/keys/values used are immutable;
  • are comparable;
  • are hashable;
  • use copy-on-write to avoid copying unnecessarily.

See below for details on each of these points.

Recommended Style

A project can benefit greatly from using Built Collections throughout. Methods that will not mutate a collection can accept the "built" version, making it clear that no mutation will happen and completely avoiding the need for defensive copying.

For code that is public to other projects or teams not using Built Collections, prefer to accept Iterable where possible. That way your code is compatible with SDK collections, Built Collections and any other collection implementation that builds on Iterable.

It's okay to accept List, Set or Map if needed. Built Collections provide efficient conversion to their SDK counterparts via BuiltList.toList, BuiltListMultimap.toMap, BuiltSet.toSet, BuiltMap.toMap and BuiltSetMultimap.toMap.

Built Collections are Immutable

Built Collections do not offer any methods that modify the collection. In order to make changes, first call toBuilder to get a mutable builder.

In particular, Built Collections do not implement or extend their mutable counterparts. BuiltList implements Iterable, but not List. BuiltSet implements Iterable, but not Set. BuiltMap, BuiltListMultimap and BuiltSetMultimap share no interface with the SDK collections.

Built Collections can contain mutable elements. However, this use is not recommended, as mutations to the elements will break comparison and hashing.

Built Collections are Comparable

Core SDK collections do not offer equality checks by default.

Built Collections do a deep comparison against other Built Collections of the same type using Dart's equality operator (==). A Built Collection cannot be compared to another Built Collection when they are of different types, such as a BuiltList and a BuiltSet. Hashing is used to make repeated comparisons fast.

// true: same contents according to `int.operator==`, `MyClass.operator==`
BuiltList([1, 2, 3]) == BuiltList([1, 2, 3]);
BuiltList([MyClass(1), MyClass(2)]) == BuiltList([MyClass(1), MyClass(2)]);

// false: BuiltList and BuiltSet are never equal
BuiltList([1, 2, 3]) == BuiltSet([1, 2, 3]);
// false: different contents according to `int.operator==`
BuiltList([1, 2, 3]) == BuiltList([2, 3, 4]);

Built Collections are Hashable

Core SDK collections do not compute a deep hashCode.

Built Collections do compute, and cache, a deep hashCode. That means they can be stored inside collections that need hashing, such as hash sets and hash maps. They also use the cached hash code to speed up repeated comparisons.

Built Collections Avoid Copying Unnecessarily

Built Collections and their builder and helper types collaborate to avoid copying unless it's necessary.

In particular, BuiltList.toList, BuiltListMultimap.toMap, BuiltSet.toSet, BuiltMap.toMap and BuiltSetMultimap.toMap do not make a copy, but return a copy-on-write wrapper. So, Built Collections can be efficiently and easily used with code that needs core SDK collections but does not mutate them.

When you want to provide a collection that explicitly throws when a mutation is attempted, use BuiltList.asList, BuiltListMultimap.asMap, BuiltSet.asSet, BuiltSetMultimap.asMap and BuiltMap.asMap.

Features and bugs

Please file feature requests and bugs at the issue tracker.

built_collection.dart's People

Contributors

a-babin avatar cbracken avatar davidmorgan avatar dependabot[bot] avatar devoncarew avatar diablodale avatar frankborden avatar gmascia avatar har79 avatar keertip avatar kevmoo avatar lisenish avatar lorecarra avatar manuelf avatar michaelrfairhurst avatar pidwid avatar pschiffmann avatar region40 avatar scheglov avatar srawlins avatar stereotype441 avatar wikiwix avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

built_collection.dart's Issues

Bump quiver dependency

Hi,
can you please increase the dependencies of this lib to support quiver 0.26.0, in order to be able to use this package in the latest master of Flutter?

Thanks a lot

Add option to use HashSet and HashMap instead of default Set (LinkedHashSet) and Map (LinkedHashMap)

Currently BuiltSet and BuiltMap use Dart's default Set and Map collections, that are not efficient in inserting or deletion large amounts of data. I would like to have ability to specify underlying Set and Map collection implementation, at least as a bool flag to use HashSet and HashMap. Another option would be to use HashSet and HashMap as default backend for those collections. What do you think about it?

Documentation on iteration order

The documentation of the BuiltSet states that "It preserves iteration order."

It wasn't clear to me that this means/implies that the iteration order is the insertion order. Could this be made clearer in the documentation?

Add usage examples to README.md

Right now, you have to click and follow the Medium link to see some code examples. It might be a faster pitch to include some of the API usage in the README.md for everyone to see. (Some other packages I've seen seem to follow this pattern.)

Add support for returning typed collections in Strong mode

Using map on a built collection in strong mode produces a warning. For example,
BuiltSet is = new BuiltSet(const [1, 2, 3]);
Iterable ss = is.map((i) => '$i');
produces
Unsound implicit cast from Iterable to Iterable

This can be fixed by changing BuiltSet.map from
Iterable map(f(E element)) => set.map(f);
to
Iterable/
/ map//(/=T_/ f(E e)) => _set.map(f);
as in the built-in Iterable: https://github.com/dart-lang/sdk/blob/master/sdk/lib/core/iterable.dart#L159

I'm working on a patch to fix this across built_collection.

-- hcameron@

Set/Map/Multimap: Custom equality?

Any concern about support package:collection's Equality class, optionally?

I have some use cases where I'd like to use BuiltCollection(s), but the keys I don't have control over and don't really want to write wrapper objects just to change the identity.

If you're OK, I can send PRs. :)

Add getters to builders

Sometimes you do need getters to be able to do inline updates.

The same getters as List/Map/Set/etc should be provided, but without implementing List/Map/Set/Iterable, to prevent builders accidentally being used in place of mutable collections.

new BuiltList<A>(builtListOfB) should be efficient

B extends A.
If we have a BuiltList<B> but we need it as a BuiltList<A>, we currently need to create a copy of the list.
new BuiltList<A>() should be able to reuse the underlying list and without checking the type of each element.

add toJson() methods

currently the built collections don't have a toJson() the method which means JSON.encode() won't handle them.

Collections could merge when they compare equal

Possible performance improvement: if two collections compare equal, they could then drop one of the equal underlying lists and both point to the same one. This can be used to do faster equality checks later, and will save space.

immutable lists/maps with faster than linear time append/mutation

Immutable data structure suggestion:

In general, for an immutable tree, one can perform a "mutation" to any node in the graph in O(depth). Assuming the tree is roughly balanced, this ends up being O(lg(N)) were N is the total number of items in the tree. The "mutation" of course, is only seen in the newly returned tree. Original tree is unchanged. But they share as much structure as possible, to save time and space. This is the typical strategy for all languages that have immutable data structures.

For example, in Clojure, all of their immutable data structures took great pains to get efficient performance on changes. The array-like type used something like a B-tree with 32 as the branching factor. So for example, you can "mutate" a 1000 element array and it only rewrites 2 little Java arrays. (Disclaimer: it was like 5 yrs ago when I last looked, details may have changed.)

Similarly it would be possible to have BuiltMap that supports "mutation", returning a new BuiltMap that shares almost all of the structure with the original map.

Anyway, the reason I bring this up, I've seen a lot of folks use BuiltList/BuiltMap like:

void addItem(Item item) {
  _myBuiltList = _myBuiltList.rebuild((b) => b.add(item));
}

(edit: fixed example to make more sense)

That can easily lead to quadratic code. If you call addItem N times, your calling code will be O(N^2). At least this is what I gathered from reading the current implementation code for BuiltList/Map--that rebuild is O(N).

Anyway if this sounds like I good idea, I might try and prototype something, but I wanted to file this first to check my understanding. (p.s. <3 immutable data structures. this package is super useful.)

Add "builtAt" to builders

ListBuilder could for example have a "builtAt" method that converts the value at the specified index to a builder, similar to the "rebuild" method but for an element in the collection:

var list = new BuiltList<BuiltList>([new BuiltList[1]]);
var rebuiltList = list.rebuild((b) => b.rebuildAt(0).add(2));

--> [[1, 2]]

This should work for nested built collections and for built values.

Simplify checkGenericTypeParameters

UnusedClass is not needed (and the current code fails for e.g. BuiltList).

Instead we could use CheckNotDynamic:

import 'package:unittest/unittest.dart';

class CheckNotDynamic<T> {
  CheckNotDynamic() {
    if (isDynamic) {
      throw new UnsupportedError('explicit element type required');
    }
  }

  bool get isDynamic => null is T && T != Object;
}

class IsDynamic<T> {
  bool isDynamic() => null is T && T != Object;
}

void main() {
  new CheckNotDynamic<Type>();
  new CheckNotDynamic<String>();
  new CheckNotDynamic<Object>();

  expect(new IsDynamic<Type>().isDynamic(), false);
  expect(new IsDynamic<String>().isDynamic(), false);
  expect(new IsDynamic<Object>().isDynamic(), false);

  expect(new IsDynamic().isDynamic(), true);
  expect(new IsDynamic<dynamic>().isDynamic(), true);
}

Consider "asList", "asMap", and "asSet"

I like this library, but one thing stopping me from using it is that I don't want to require other users to use built_collection, so I tend to only use it as an implementation detail and pass a toList/toMap/toSet as part of my API.

I would use asList/Map/Set, where they could be, for example:

List<T> asList() => new List<T>.unmodifiable(_backingList);

Add BuiltIterable for return values of Iterable

By wrapping in a new class we can provide extra utility methods, e.g. toBuiltList, toBuiltSet.

This will be helpful for the "map" method on built collections, to allow you to map then convert immediately into another built collection.

BuiltMap<String, BuiltSet<String>> updates

How do I add an element to a contained BuiltSet?

MapBuilder does not allow me to retrieve elements and update them. Why is there no operator []?

Ideally, there was special casing for keys and values of BuiltMap such that toBuilder returned MapBuilder<String, ListBuilder> in this case.


What's a good workaround?

Right now, I've only been able to come up with:

BuiltMap<String, BuiltSet<String>> setMap;
String key, value;

setMap = setMap.rebuild((b) => 
    b[key] = (setMap[key] ?? new BuiltSet<String>()).rebuild((b) => b.add(value)));

Thanks,
Andreas

Support: why the iteration in the operator== algorithm?

I went through the BuiltList implementation and I assume the concept is the same for every built collection type. This is only an educated guess. In either case, what I saw in BuiltList can be found here: https://github.com/google/built_collection.dart/blob/f91f2307a919f3675f23cff8fef9be8daa9174bf/lib/src/list/built_list.dart#L65-74

Let's assume the following:
We create two BuiltList<String> collection instances. Both have got the same strings in it.

var al = new BuiltList<String>([ 'a', 'b' ]);
var bl = new BuiltList<String>([ 'a', 'b' ]);

We know that strings are always going to be equal if they are built up of the same code units. It's hashing algorithm will take that into account.

var a = 'a'; 
var b = 'a';
print('${a.hashCode == b.hashCode && a == b}'); // true

Since BuiltList combines the hashCodes of it's members, the hashCode of 2 BuiltList instances are going to be equal if their members have the same hashCodes.

var aa = [ 'a', 'b' ];
var bb = [ 'a', 'b' ];
print("${quiver.hashObjects( aa ) == quiver.hashObjects( bb )} "); // true

It stands to reason in such situations, a simple hashCode comparison would be sufficient to reliably determine if our 2 BuiltList instances are made up of the same strings without having to iterate over them and do all those extra steps.

bool opreator==(other) {
    return other is BuiltList && other.hashCode == hashCode;
}

Question: what am I missing, why the seemingly wasteful algorithm for comparison?

Typing for intersect/union is weird

Currently Set.intersection takes another Set, which means you can't intersect with something that might contain non-E elements. Should it take Set instead?

Get value from MapBuilder

It seems like MapBuilder only supports setting values, not getting them. My scenario is that I am using a MapBuilder<String, List<Foo>>. I have a loop where for each iteration the List for a particular String key in the map may be added to for a particular key in the map. I tried something like this but it does not seem to work since I cannot fetch the value for the key.

   var builder = new MapBuilder<String, List<Foo>>();
   for (Bar bar in bars) {
      var list = builder[bar.name]; // This line fails
      if(list == null) {
       list = new List<Foo>();
       builder[bar.name] = list;
      }
      list.add(bar.foos);
    }

Is there some other way to solve this scenario? I could use a regular Map in the loop and then create the MapBuilder from that, but I feel that would defeat the purpose of a MapBuilder :-).

Copy constructor returns wrong type

B extends A

listB = new BuiltList<B>();
listA = new BuiltList<A>(listB);

In this case listA has the same runtime type as listB because listA is BuiltList<B> evaluates to true in the copy constructor of BuiltList. This later causes problems when trying to rebuild listA with new elements of type A.

Tighten up type checking

Some operations do not check types if checked mode is turned off, e.g.

new BuiltList(['string'])

per the Built Collection docs they should always check types.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.