Giter Site home page Giter Site logo

frptools / collectable Goto Github PK

View Code? Open in Web Editor NEW
271.0 10.0 14.0 2.98 MB

High-performance immutable data structures for modern JavaScript and TypeScript applications. Functional interfaces, deep/composite operations API, mixed mutability API, TypeScript definitions, ES2015 module exports.

License: MIT License

TypeScript 100.00%
immutable typescript-definitions es2015-modules data-structures typescript immutablejs red-black-tree hash-array-mapped-trie set sorted-set

collectable's Introduction

Collectable.js: Immutable Data Structures

An all-you-can-eat buffet of high-performance, persistent, immutable, functional data structures. Collect them all!

Build Status NPM version Gitter

Note: This library is an ongoing work in progress. The data structures mentioned below are all working nicely, but will probably have additional methods introduced as time goes on, and there may be some breaking changes in future versions when work commences to iron out API inconsistencies between data structures. Documentation is also somewhat lacking and out of date, but take a look at the functions folder for each data structure package, which is almost as good as actual documentation, due to the one-operation-per-file policy, and the comprehensive TypeScript annotations.

Features

  • A robust suite of high-performance data structures for most use cases
  • Full ES2015 module support so that your application bundle only need grow in size according to what you actually use
  • Functional API, prioritising an order of parameters best suited for currying and composition
  • ๐Ÿšง API for deep operations on nested structures
  • Deep and shallow conversion to and from native types, including arrays, objects, iterables, Maps and Sets
  • Complete set of TypeScript definitions
  • Extensive documentation and examples
  • One package import to access everything, or isolated npm packages for each individual data structure
  • Comprehensive unit test suite, consisting of hundreds of tests for every data structure
  • Strong focus on a code style that emphasises high performance internally

Data Structures

See the road map for information on further development and plans for additional features and data structures.

Installation

# via NPM
npm install collectable

# or Yarn
yarn add collectable

TypeScript type definitions are built in.

Usage

API Reference: [ General | List | Map | Sorted Map | Set | Sorted Set | Red Black Tree | Others... ]

Contributor Credits (Deliberate or Unwitting)

Want to help out? See the guide for contributors.

collectable's People

Contributors

axefrog avatar davidchase avatar dependabot[bot] avatar eliranek1 avatar not-an-aardvark avatar simonmeskens avatar thomastay avatar tylors 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

collectable's Issues

Immutable list: keyed lookup capability

An order/sorted map is easy to implement if you'll always start with an ordinal position, then get the key from the value retrieved at that location. If you start with a key though, it's no longer straightforward to maintain a fast way of looking up values.

Two approaches, and possibly both could be provided, with different performance characteristics depending in the needs of the user:

Sorted Map

This would be a map that happens to keep items in order via a custom comparator.

  • List-like operations such as concatenation or insertion at a particular index would not be supported, as the sort order would be controlled by the comparator.
  • An immutable list would be used under the hood, with keys mapping to index positions.
  • Index-based lookup would be available by deferring directly to the list's indexing functionality.
  • The default comparator would simply maintain the original insertion order, with new items appended to the end of the list.
  • Entries would be stored in the list as a [key, value] pair in order to allow iteration through pairs.

Keyed List

The interface would be based on list-centric operations, and would require some additional functionality that would overlap with some aspects of the existing RRB-tree implementation.

  • Ordinal-based insertions, concatenation, range deletion and so forth would be standard operations.
  • A key function provided by the user would map a data item to a key. Alternatively, the key could be passed as an argument by the caller.
  • Because list management treats ordinal positions as a first-order concern, it is possible to have multiple items with the same key, or with no key. Key-based lookup would therefore return the first item with the specified key, or an iterable of items matching the specified key. These could be provided as separate functions.
  • Each node would need to maintain a descending lookup table of the key set that it contains. Adding or removing slots to or from a node would carry the additional cost of updating the local lookup table. The lookup table would be hash-array-mapped (like the persistent trie structure of the same name). Each entry will map to one or more slot indices that should be traversed to find elements matching the specified key. When more than one slot index is present, it means each branch should be traversed in order to find the specified entry.
  • When returning an iterable, its behaviour will be late-bound in order to prevent traversal down secondary branches before it is known that they are definitely going to be accessed.
  • Without optimisations, key-based lookup will force any uncommitted views to first be committed, as descent occurs from the root. A reference to the root will be maintained for fast key-based reading, but voided when there is at least one uncommitted view. The slowest way of accessing the list would be writes interspersed with key-based lookups, though the speed would only be slightly slower than if the view-based write optimization that is currently in place did not exist.
  • The existing code might be able to be "upgraded" with the additional functionality if there is a fast way of checking whether the list includes keys. An isDefined check against a list state property may be sufficient for this purpose, with additional logic hidden within conditional branches. Just as regular nodes are upgraded to RRB nodes, they could have a secondary upgrade as well, if key-based storage and lookup is active.
  • If a key extraction function is used, then we're actually treating the structure as a hashed list. Keys would therefore not be required to be any particular data type or shape. Immutable.js has a built-in hashing function for custom objects, which would be good to use as a default, but could be replaced by the user in order to ensure that a controlled set of properties or characteristics are checked, and that others are disregarded. It would also make it possible to group different items under common hash keys.

I'm not certain that a keyed list is an important data structure yet. An ordered map is super easy to build because it is composed from a map and a list, with no data structure internals needing to be modified or implemented from scratch, and will probably cover most use cases.

fantasy-land compliance

Typed Static-land compliance

This document outlines a strategy that promises at the very least a guarantee a library will not become incompatible with the Static-land specification and at best allows a path towards full compatibility, without clobbering users with hard to understand terminology, such as monoids, monads and functors.

Rules

A list of Static-land reserved function names, with TypeScript types, is provided at the bottom

  1. No type will export a function name out of the reserved list, without also sharing type and behavior
  2. If a function shares the type of a Static-land reserved function and has the same behavior, either an alias is made or the method receives the same name

The first rule simply means that if you have, for example, a map method that's not compliant with the Functor.map, you name it transform or select instead. Array.map is compliant with Functor.map, it's actually unusual for name collisions to happen, as Static-land is based on vanilla JavaScript.
The second rule is just to make sure that if there exists, for example, a flatMap compliant with Chain.chain (as in many libraries), compatibility with Static-land is ensured by either having an alias or simply naming it chain in the first place.

Fantasy-land compliance

Each type that exports Static-land compliant functions also has non-enumerable namespaced methods on the type. For example, if a map function exists, the type itself also has a fantasy-land/map property, which is compliant with Fantasy-land (usually the same signature as Static-land, but with this functioning as the type to act on. By making these non-enumerable, you only come across them if you're looking (no pollution of the type). These act as a formal protocol for libraries such as Ramda, so they know how to handle that type.

Static-land reserved functions

For expected behavior, check the Static-land spec. These mostly do exactly what you expect them to do. Static-land never collapses though, so a list of lists is never automatically flattened, for example. Obviously, names of parameters are not important, signature is.

In these, Type is a stand-in for the type in question, generic parameters are noted as A, B, C, etc. A type can always have more generic parameters than specified, but never less.

  equals(a: Type, b: Type): boolean
  lte(a: Type, b: Type): boolean
  concat(a: Type, b: Type): Type
  empty(): Type
  map<A, B>(f: (a: A) => B, ta: Type<A>): Type<B>
  bimap<A, B, C, D>(fa: (a: A) => B, fc: (c: C) => D, ta: Type<A, C>): Type<B, D>
  contramap<A, B>(f: (a: A) => B, tb: Type<B>): Type<A>
  promap<A, B, C, D>(fa: (a: A) => B, fc: (c: C) => D, tbc: Type<B, C>): Type<A, D>
  ap<A, B>(f: Type<(a: A) => B>, ta: Type<A>): Type<B>
  of<A>(a: A): Type<A>
  alt<A>(a: Type<A>, b: Type<A>): Type<A>
  zero<A>(): Type<A>
  chain<A, B>(f: (a: A) => Type<B>, ta: Type<A>): Type<B>
  chainRec // Not easily typable in TypeScript
  reduce<A, B>(f: (a: A, b: B) => A, a: A, tb: Type<B>): A
  extend<A, B>(f: (ta: Type<A>) => B, ta: Type<A>): Type<B>
  extract<A>(ta: Type<A>): A
  traverse // Not easily typable in TypeScript

Progress

General

  • Refactor all isEqual functions to equals
  • Add namespaced fantasy methods
  • Write tests for the laws in Static-land

List

  • Look at unifying List with other structures #31
  • Add fold primitive to internals
  • Add List/of
  • Add List/zero
  • Add List/alt
  • Add List/map #51
  • Add List/filter
  • Add List/ap
  • Add List/reduce
  • Add List/chain
  • Add List/traverse
  • Add List/extend

Original Post: How important is compliance to fantasy-land/static-land for this project? I'd like to use this project together with Ramda and fantasy-land libraries.

I'm willing to help out on this front

Custom import generator

Functional imports when using multiple collections is fiddly and messy because you end up with loads of imports to manage in every source file, and you have to use aliasing a lot to account for commonalities among function names (e.g. freeze, get, etc.). I've discovered that a good pattern is to create an internal collections script that imports all of the required functions and exports them as unified collection types/namespaces. It's better for this to be done by the consuming application, because you lose the ability to tree-shake unused imports, so you want to make sure you only expose the methods you're actually using.

In the docs area, I want a page that lets you select all the collections and methods you want, then automatically generates a script with all of imports and exports ready to go.

Example script that would be generated:

export {batch, isMutable, isImmutable} from '@collectable/core';

import {
  List as _List,
  fromArray as list_fromArray,
  size as list_size,
  get as list_get,
  iterate as list_iterate,
  skip as list_skip,
  take as list_take,
} from '@collectable/list';

export type List<T> = _List<T>;
export namespace List {
  export const fromArray = list_fromArray;
  export const size = list_size;
  export const get = list_get;
  export const iterate = list_iterate;
  export const skip = list_skip;
  export const take = list_take;
}

import {
  Map as _Map,
  empty as map_empty,
  entries as map_entries,
  keys as map_keys,
  get as map_get,
  set as map_set,
  thaw as map_thaw,
  isFrozen as map_isFrozen,
  update as map_update,
} from '@collectable/map';

export type Map<K, V> = _Map<K, V>;
export namespace Map {
  export const empty = map_empty;
  export const entries = map_entries;
  export const keys = map_keys;
  export const get = map_get;
  export const set = map_set;
  export const thaw = map_thaw;
  export const isFrozen = map_isFrozen;
  export const update = map_update;
}

import {
  SortedMap as _SortedMap,
  SortedMapEntry,
  empty as smap_empty,
  get as smap_get,
  set as smap_set,
  thaw as smap_thaw,
  isFrozen as smap_isFrozen,
  update as smap_update,
} from '@collectable/sorted-map';

export type SortedMap<K, V> = _SortedMap<K, V>;
export namespace SortedMap {
  export const empty = smap_empty;
  export const get = smap_get;
  export const set = smap_set;
  export const thaw = smap_thaw;
  export const isFrozen = smap_isFrozen;
  export const update = smap_update;

  export function KeyComparator(a: SortedMapEntry<any, any, any>, b: SortedMapEntry<any, any, any>): number {
    return a.key - b.key;
  }
}

import {
  Set as _Set,
  empty as set_empty,
  add as set_add,
  remove as set_remove,
  values as set_values,
  isEmpty as set_isEmpty,
} from '@collectable/set';

export type Set<T> = _Set<T>;
export namespace Set {
  export const empty = set_empty;
  export const add = set_add;
  export const remove = set_remove;
  export const values = set_values;
  export const isEmpty = set_isEmpty;
}

import {
  SortedSet as _SortedSet,
  empty as sset_empty,
  add as sset_add,
  remove as sset_remove,
} from '@collectable/sorted-set';

export type SortedSet<T> = _SortedSet<T>;
export namespace SortedSet {
  export const empty = sset_empty;
  export const add = sset_add;
  export const remove = sset_remove;
}

Autogenerate documentation, pre-curried exports and class wrappers

  • Learn how to use the TypeScript compiler to extract relevant metadata
  • Extract and summarise symbols as JSON to build folder
  • Regenerate README files from templates
  • Generate HTML documentation site under /docs
  • [Prebuild] Generate curried exports
  • [Prebuild] Generate Immutable.js-style class exports

Note: Use the JSON as backing for #40

setIn() for numerical indexes fails on empty lists

I'd like to better understand the intended behavior of setIn() regarding integers used in the set path.

This is from @axefrog on #55

When the path does not match what exists already, what exists should be replaced with the best collection type for the path element type (a string would suggest a map key, whereas a number would suggest a list index, unless there is already a map at that location).

Consider this example:

setIn(
  ['a', 0],         // at: input.a[0]
  true,             // set: true
  from({a: []})     // for: {a:[]} โŒ
); 
// expected result: from({ a: [true] }) 

This use of a numerical index throws the following error:

     Error: Index -1 is out of range
      at Object.setValueAtOrdinal 
(node_modules/@collectable/list/lib/commonjs/internals/values.js:15:15)

It looks like this is because the intended path violates a safeguards against setting values at indexes beyond the length of the list. For example:

setIn(
  ['a', 9],         // at: input.a[9]
  true,             // set: true
  from({a: []})     // for: { a: [] } โŒ
); 
// what is this supposed to do? what goes in the first 8 indexes? 
// undefined? just crash

This has several implications:

  1. You cannot set the last value of a list (that seems problematic). Solution: change the safeguard logic.
  2. You cannot have arrays in the path of a deep setIn(), like so:
setIn(
  ['a', 0, 'b', 0, 'c'], // at: input.a[0].b[0].c
  true,                  // set: true
  from({a: []})          // for: {a:[]}
); 
// expected: from({ a: [ { b: [ { c: true} ]}]}) โŒ

I'm not sure if [2] is confusing or not. I'm not sure whether people would want to do that or not. This comment from @axefrog suggests that something like that is desirable though. Maybe all that it means is that existing lists and indexes along the path should be settable, but not dynamically filled in where missing.

setIn() seems to work just as expected for existing lists and indexes:

setIn(
  ['a', 0, 'b', 4, 'c'],              // at: input.a[0].b[4].c
  true,                               // set: true
  from({ a: [ { b: [ 0, 1, 2, 3 ]}]}) // for: { a: [ { b: [ 0, 1, 2, 3 ]}]}
); 
// expected: from({ a: [ { b: [ 0, 1, 2, 3, { c: true} ]}]}) ๐Ÿ‘Œ๐Ÿป

One-step regenerate, publish for updated package versions

  • Auto-detect changed package versions that haven't been published to NPM
  • Publish subpackages that have changed (@collectable/*), or which depend on changed packages
  • Update main package.json and yarn.lock to point at new packages
  • Publish main package

Map (HAMT)

Investigate other implementations that could serve as a suitable dependency. HAMT+ looks promising. May need to write and publish custom TypeScript typings, or fork the repository, or pull the code directly into Collectable.js.

pop()

  • pop
  • popFront
  • skip
  • take

Refactoring to support revised left/right view system

  • internal structures
  • ascend
  • focus/refocus
  • commit (group negation/reservation/release)
  • capacity (edge expansion)
  • capacity (subtree population)
  • debugging for the above changes
  • concat
  • debugging concat
  • debugging get
  • unit tests

Performance tests

There is a hierarchy of performance tests:

  1. Collection type (e.g. List vs Map)
  2. Primary operations (get, set, etc.)
  3. Composite operations (interleaved combinations of reads and writes)

Because this forms a matrix of tests, the project structure for performing tests will need to be considered. It should be possible to break tests down into subsections, run only the tests that have changed, and merge the results into the higher-level summarised performance report for the entire collection.

Power the tests using benchmark.js, but create an abstraction layer on top which can be used to streamline the addition of more tests.

  • Write performance testing infrastructure
  • Implement relevant performance tests for existing packages
  • Identify important targets for performance revision; create issues, add to project board

concat()

  • left/right
  • more than two arguments
  • balancing/compaction
  • reduction from rnode to vnode
  • leave tail in uncommitted state

Cuckoo Filter: Implementation

Implementation seems fairly straightforward. Investigate the best way to implement an immutable variant for use for fast operations on sets and sorted sets, and querying on large collections.

prepend()

  • focus head
  • expand semi-populated node
  • leftward increaseCapacity/populateSubtree
  • grow tree as needed
  • prepend multiple arguments left-to-right sequentially

slice()

  • core implementation

Testing/debugging:

  • basic (all or nothing)
  • subset of head or tail only
  • truncate left
  • truncate right
  • truncate both
  • simple top-level vnode boundary
  • mixed vnode/rnode slice
  • full rnode slice
  • single central node

List.remove behaving weirdly

I think there's something wrong with List.remove:

import {List, Mutation} from "collectable";

let list = List.fromArray([1, 2, 3, 4, 5]);

let modify = Mutation.modify(list);

List.remove(0, modify);
List.remove(2, modify);
List.remove(4, modify);

modify = Mutation.commit(modify);
console.log(List.toArray(modify));

let modify2 = Mutation.modify(list);

List.remove(0, modify2);
List.remove(1, modify2);
List.remove(2, modify2);

modify2 = Mutation.commit(modify2);
console.log(List.toArray(modify2));

Output:

[2, 3, 3]
Uncaught RangeError: Invalid array length
at Slot.cloneWithAdjustedRange (bundle.js:8284)
at View.adjustSlotRange (bundle.js:9291)
at sliceInternal (bundle.js:8162)
at Object.sliceList (bundle.js:8076)
at Object.deleteValues (bundle.js:9017)
at removeRange (bundle.js:7105)
at Object.remove (bundle.js:7100)
at Object.1.collectable (bundle.js:14)
at s (bundle.js:1)
at e (bundle.js:1)
cloneWithAdjustedRange @ bundle.js:8284
adjustSlotRange @ bundle.js:9291
sliceInternal @ bundle.js:8162
sliceList @ bundle.js:8076
deleteValues @ bundle.js:9017
removeRange @ bundle.js:7105
remove @ bundle.js:7100
1.collectable @ bundle.js:14
s @ bundle.js:1
e @ bundle.js:1
(anonymous) @ bundle.js:1

List: Tidy up public API

Being the first structure I introduced, the public API for collectable/list has a few inconsistencies with the other data structures and could do with some polishing up. Extra methods will also be required for stuff liking map, filter and so forth.

Removing elements from a mutable red-black tree affects its immutable clones

Using current master (84d7bdb):

const RBT = require('./packages/red-black-tree');

const mutableTree = RBT.emptyWithNumericKeys(true);

RBT.set(1, 'one', mutableTree);
RBT.set(2, 'two', mutableTree);
RBT.set(3,'three', mutableTree);
RBT.set(4, 'four', mutableTree);

const immutableClone = RBT.clone(mutableTree, false);

RBT.remove(1, mutableTree);

console.log([...RBT.keys(immutableClone)])

Expected output:

[1, 2, 3, 4]

Actual output:

[2]

I encountered this when I tried to use an immutable clone for stable iterators in #44 (comment). I think I'm using the new API correctly (freeze/thaw seem to have been removed), but let me know if I'm misunderstanding how it's supposed to work.

First package release

  • Master branch only used for publishing releases; development in separate branch
  • Use staltz' compatible versioning proposal, which I agree with
  • Publish stopgap map/set structures (proper implementations unimportant for first release)
  • Northbrook setup to allow streamlined publishing
  • Travis integration with automatic publish to npm
  • Package published to npm

This will be a useful process that I will then know how to duplicate with Salix (and other libraries).

batch.owner(true) should prefer -1 over current owner value

If explicitly requesting a mutable ownership value, -1 should be treated as the default, rather than the fallback, or an unexpectedly-active batch could interfere with external logic intending to make a collection mutable for purposes other than that of the active batch, the termination of which will inadvertently and implicitly refreeze the mutable collection.

https://github.com/frptools/collectable/blob/master/packages/core/src/ownership.ts#L34

set()

  • node reservation
  • tests

List: Optimisations and improvements

Performance testing shows superior performance with many bulk and sequential operations, and inferior performance with random groups of adhoc, interleaved operations that jump around the list in no particular order. Currently it's hard to predict what the most common usage will be, but performance needs to be strong across the board, as real-world usage isn't going to cater to the "preferred" set of operations for which the List structure works best.

The following optimisations are worth having a look at when there's time, and should not only help to significantly improve performance in adhoc scenarios, but may also help to reduce code complexity, which is currently very high.

  • Use the existing left and right views as write targets as is currently the case, but only use them for reading if they already point at the correct location. Do not relocate them except for writing. By doing this, the number of checks and balances surrounding reading will drop significantly.
  • Keep a separate reader view and move it around independently of the write views.
    Uncommitted ranges for intermediate views should be included in this. Caching this information will also reduce the complexity of managing the anchoring of the left/write views.
  • The reader view should keep a direct array of all slots in its path, and their ranges. There is no need to string multiple views together for this purpose. Traversal operations will be minimised by doing this. The array should be able to be treated like a super-simple skip list.
  • The reader is always subservient to write state, and does not affect data. Therefore, it only needs to be copied for changes to the list. For multiple reads against the same list, it is completely mutable. If two application modules are heavily using the same list in different ways and need to optimise performance further, the second module can simply clone the list in order to acquire an isolated reader.
  • As an extension of the above, the reader will have a direct reference to the root, allowing for quick random access reads.
  • Cache the write locations and ranges as list fields to eliminate the need for traversal-based range checking. The reader should be able to consult these to very quickly decide whether a commit first needs to be made. If writes are infrequent and reads are frequent, the amortized cost of reads will be almost the same as if uncommitted writes were not a concern at all. Caching these values may also allow a number of areas of code to be simplified or eliminated entirely.

Further Thinking

  • Use a single writer instead of dual left/right writers. By eliminating the second writer, a massive amount of complex logic can likely be removed. The left/right anchoring technique could remain in place as-is, but seeing as it applies to only a single write path, numerous checks and verifications would be able to be discarded.
  • Having one reader and one writer removes the need for view-selection logic.
  • Having the writer use a chain of lazily-committed views, as is the case right now, allows write speed to remain very fast. Rather than maintaining them as a chain though, keeping them in a short array instead allows much faster switching between different locations for writing, because, with a cached value indicating the lowest depth containing uncommitted changes, upward traversal can be skipped entirely in many cases by jumping straight to the correct level. Also, as the array will rarely be larger than 2-4 elements (one per depth level), the cost of cloning the array should be negligible, and the management of the view chain is easier to reduce to simple loops.
  • By having the reader and the writer maintain structural commonalities, the code footprint will likely be reduced, given that traversal logic will be shared.
  • The thought had crossed my mind that multiple writers may be useful for times when heavy modifications are being performed, such as during a sort operation. Instead of that, however, start by making sure the writer is committed, then perform the bulk operation outside of the management layer, then just reset the writers for subsequent adhoc operations.
  • The writer is the source of truth. The reader can be reconstructed from the writer (and root node discovery), but not vice versa.

push()

  • single
  • multiple
  • vnode
  • rnode
  • via constructor

Set

Direct wrapper around HAMT, with additional methods:

  • Union
  • Subtract
  • Intersect

get()

  • vnode
  • rnode
  • use of uncommitted views
  • maintenance of focus view for fast indexing
  • auto-commit central uncommitted write view
  • first focus view not counted as head during concatenation/appending if start > 0

Removing the last element from a mutable red-black tree results in incorrect behavior

Using @collectable/[email protected] with Node 8.1.3:

const RedBlackTree = require("@collectable/red-black-tree");
const myTree = RedBlackTree.thaw(RedBlackTree.empty());

RedBlackTree.set(0, 'foo', myTree);
RedBlackTree.remove(0, myTree);

console.log(RedBlackTree.arrayFrom(myTree));

Expected output:

[]

Actual output:

[ { _group: 0,
    key: undefined,
    value: undefined,
    _red: false,
    _left: [Circular],
    _right: [Circular],
    _count: 0 } ]

This does not seem to be a problem with immutable red-black trees -- the following outputs [] as expected:

RedBlackTree.arrayFrom(
  RedBlackTree.remove(
    0,
    RedBlackTree.set(
      0,
      'foo',
      RedBlackTree.empty()
    )
  )
)

setIn() second parameter is treated as a value

In #55 I alluded to this, but currently setIn() has this API:

setIn([path to your destination in target collection], valueToSet, targetCollection)

thus:

setIn(['a', 'b', 'c', 'd', 'e'], true, from({}))
// expected: from({a: {b: {c: {d: {e: true}}}}}) ๐Ÿ‘Œ๐Ÿป

However, the README describes the API like so:

setIn(['foo', 'bar'], x => 'baz', from({})); 
// expected: <{foo: <{bar: 'baz'}>, xyz: ...> โŒ
// actual: <{foo: <{bar: x => 'baz'}>, xyz: ...> ๐Ÿ˜ฑ

Note that in the above example, the expected value of the resulting collection at bar is 'baz'. In actuality, the resulting value will be x => 'baz', because setIn() will set the target value as a reference to the function, rather than evaluating the function to get 'baz'

To get the results described in the README, the example would read:

setIn(['foo', 'bar'], 'baz', from({})); 
// expected: <{foo: <{bar: 'baz'}>, xyz: ...> ๐Ÿ‘Œ๐Ÿป

I think this might just be a typo in the README, but I want to make sure first.

insert()

  • single value
  • range of values
  • other list
  • debugging

Red Black Tree

Forms the basis of:

  • Set (could probably also be handled by the HashMap)
  • Sorted Set
  • Sorted Map

setIn behavior contradicts documentation

Problem

Using [email protected]

It looks like C.setIn() tries to interact with values at times when I expect it to be interacting with keys.

See this webpackbin for a demo.

Take special note of map2, which is the operation that appears to throw the type error. Copy-pasted from the README:

import * as C from 'collectable';

const input = {
  foo: 'abc', <--- setIn tries to do stuff to 'abc' when it should be operating on input.foo...
  xyz: [3, [5, 6], 7, 9]
};
const map0 = C.from(input); // <{foo: 'abc', xyz: <[3, [5, 6], 7, 9]>}>
const map1 = C.updateIn(['xyz', 1, 0], n => 4, map0); // <{foo: 'abc', xyz: <[3, [4, 6], 7, 9]>}>
const map2 = C.setIn(['foo', 'bar'], x => 'baz', map1); // <{foo: <{bar: 'baz'}>, xyz: ...>

VM181 dll.js:9431 Uncaught TypeError: Cannot use 'in' operator to search for '@@is-collection' in abc
    at isCollection (VM181 dll.js:9431)
    at isIndexedCollection (VM181 dll.js:9434)
    at setDeep (VM181 dll.js:7644)
    at path.length.__WEBPACK_IMPORTED_MODULE_0__collectable_core__.IndexedCollection.updateEntry (VM181 dll.js:7647)
    at update (VM181 dll.js:9817)
    at HashMapStructure.@@update (VM181 dll.js:3664)
    at Object.updateEntry (VM181 dll.js:9425)
    at setDeep (VM181 dll.js:7647)
    at Object.setIn (VM181 dll.js:7639)
    at Object.<anonymous> (VM182 main.js:96)

Desired Outcome

  • If the documentation is misleading, then the example would be corrected to demonstrate the actual setIn() behavior. This is likely, since there are many calls for documentation throughout the project.

  • If the documentation reflects the intended API correctly, then this is a bug in the implementation of setIn()

More universal methods in the main package

I want the main package to expose more methods that are interchageable among collections, including methods for shallow getting/setting, iteration, and for creating new collections and so forth.

  • Collection-creation functions; var list = Collectable.List()
  • As per #40, export a full set of (optional) exports, providing a set of namespaced functional interfaces, but with the prepackaged convenience offered by the Immutable.js approach.
  • Universal methods, e.g. get() and set(), that looks at the collection type to determine which operation to perform.

orderBy() function

  • Generic (main package)
  • SortedMap (preserve internal key map for efficiency)
  • SortedSet (preserve internal key map for efficiency)

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.