@@ -363,93 +363,100 @@ const nested4 = nested3.updateIn([ 'a', 'b', 'c' ], list => list.push(6))
363
363
```
364
364
365
365
366
- Lazy Seq
367
- --------
366
+ Equality treats Collections as Values
367
+ -------------------------------------
368
368
369
- ` Seq ` describes a lazy operation, allowing them to efficiently chain
370
- use of all the sequence methods (such as ` map ` and ` filter ` ).
369
+ Immutable.js collections are treated as pure data * values* . Two immutable
370
+ collections are considered * value equal* (via ` .equals() ` or ` is() ` ) if they
371
+ represent the same collection of values. This differs from JavaScript's typical
372
+ * reference equal* (via ` === ` or ` == ` ) for Objects and Arrays which only
373
+ determines if two variables represent references to the same object instance.
371
374
372
- ** Seq is immutable** — Once a Seq is created, it cannot be
373
- changed, appended to, rearranged or otherwise modified. Instead, any mutative
374
- method called on a Seq will return a new Seq.
375
-
376
- ** Seq is lazy** — Seq does as little work as necessary to respond to any
377
- method call.
378
-
379
- For example, the following does not perform any work, because the resulting
380
- Seq is never used:
375
+ Consider the example below where two identical ` Map ` instances are not
376
+ * reference equal* but are * value equal* .
381
377
378
+ <!-- runkit:activate -->
382
379
``` js
383
- const { Seq } = require (' immutable' )
384
- const oddSquares = Seq ([ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ])
385
- .filter (x => x % 2 )
386
- .map (x => x * x)
387
- ```
388
-
389
- Once the Seq is used, it performs only the work necessary. In this
390
- example, no intermediate arrays are ever created, filter is called three times,
391
- and map is only called once:
380
+ // First consider:
381
+ const obj1 = { a: 1 , b: 2 , c: 3 }
382
+ const obj2 = { a: 1 , b: 2 , c: 3 }
383
+ obj1 !== obj2 // two different instances are always not equal with ===
392
384
393
- ``` js
394
- console .log (oddSquares .get (1 )); // 9
385
+ const { Map , is } = require (' immutable' )
386
+ const map1 = Map ({ a: 1 , b: 2 , c: 3 })
387
+ const map2 = Map ({ a: 1 , b: 2 , c: 3 })
388
+ map1 !== map2 // two different instances are not reference-equal
389
+ map1 .equals (map2) // but are value-equal if they have the same values
390
+ is (map1, map2) // alternatively can use the is() function
395
391
```
396
392
397
- Any collection can be converted to a lazy Seq with ` .toSeq() ` .
393
+ Value equality allows Immutable.js collections to be used as keys in Maps or
394
+ values in Sets, and retrieved with different but equivalent collections:
398
395
399
396
<!-- runkit:activate -->
400
397
``` js
401
- const { Map } = require (' immutable' )
402
- const seq = Map ({ a: 1 , b: 2 , c: 3 }).toSeq ()
398
+ const { Map , Set } = require (' immutable' )
399
+ const map1 = Map ({ a: 1 , b: 2 , c: 3 })
400
+ const map2 = Map ({ a: 1 , b: 2 , c: 3 })
401
+ const set = Set ().add (map1)
402
+ set .has (map2) // true because these are value-equal
403
403
```
404
404
405
- Seq allows for the efficient chaining of sequence operations, especially when
406
- converting to a different concrete type (such as to a JS object):
405
+ Note: ` is() ` uses the same measure of equality as [ Object.is] [ ] for scalar
406
+ strings and numbers, but uses value equality for Immutable collections,
407
+ determining if both are immutable and all keys and values are equal
408
+ using the same measure of equality.
407
409
408
- ``` js
409
- seq .flip ().map (key => key .toUpperCase ()).flip ().toObject ();
410
- // { A: 1, B: 2, C: 3 }
411
- ```
410
+ [ Object.is ] : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
412
411
413
- As well as expressing logic that would otherwise seem memory-limited:
412
+ #### Performance tradeoffs
414
413
415
- <!-- runkit:activate -->
416
- ``` js
417
- const { Range } = require (' immutable' )
418
- Range (1 , Infinity )
419
- .skip (1000 )
420
- .map (n => - n)
421
- .filter (n => n % 2 === 0 )
422
- .take (2 )
423
- .reduce ((r , n ) => r * n, 1 );
424
- // 1006008
425
- ```
426
-
427
- Note: A Collection is always iterated in the same order, however that order may
428
- not always be well defined, as is the case for the ` Map ` .
414
+ While value equality is useful in many circumstances, it has different
415
+ performance characteristics than reference equality. Understanding these
416
+ tradeoffs may help you decide which to use in each case, especially when used
417
+ to memoize some operation.
429
418
419
+ When comparing two collections, value equality may require considering every
420
+ item in each collection, on an ` O(N) ` time complexity. For large collections of
421
+ values, this could become a costly operation. Though if the two are not equal
422
+ and hardly similar, the inequality is determined very quickly. In contrast, when
423
+ comparing two collections with reference equality, only the initial references
424
+ to memory need to be compared which is not based on the size of the collections,
425
+ which has an ` O(1) ` time complexity. Checking reference equality is always very
426
+ fast, however just because two collections are not reference-equal does not rule
427
+ out the possibility that they may be value-equal.
430
428
431
- Equality treats Collections as Data
432
- -----------------------------------
429
+ #### Return self on no-op optimization
433
430
434
- Immutable.js provides equality which treats immutable data structures as pure
435
- data, performing a deep equality check if necessary.
431
+ When possible, Immutable.js avoids creating new objects for updates where no
432
+ change in * value* occurred, to allow for efficient * reference equality* checking
433
+ to quickly determine if no change occurred.
436
434
437
435
<!-- runkit:activate -->
438
436
``` js
439
- const { Map , is } = require (' immutable' )
440
- const map1 = Map ({ a: 1 , b: 2 , c: 3 })
441
- const map2 = Map ({ a: 1 , b: 2 , c: 3 })
442
- assert .equal (map1 !== map2, true ) // two different instances
443
- assert .equal (is (map1, map2), true ) // have equivalent values
444
- assert .equal (map1 .equals (map2), true ) // alternatively use the equals method
437
+ const { Map } = require (' immutable' )
438
+ const originalMap = Map ({ a: 1 , b: 2 , c: 3 })
439
+ const updatedMap = originalMap .set (' b' , 2 )
440
+ updatedMap === originalMap // No-op .set() returned the original reference.
445
441
```
446
442
447
- ` Immutable.is() ` uses the same measure of equality as [ Object.is] [ ]
448
- including if both are immutable and all keys and values are equal
449
- using the same measure of equality.
450
-
451
- [ Object.is ] : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
443
+ However updates which do result in a change will return a new reference. Each
444
+ of these operations occur independently, so two similar updates will not return
445
+ the same reference:
452
446
447
+ <!-- runkit:activate -->
448
+ ``` js
449
+ const { Map } = require (' immutable' )
450
+ const originalMap = Map ({ a: 1 , b: 2 , c: 3 })
451
+ const updatedMap = originalMap .set (' b' , 1000 )
452
+ // New instance, leaving the original immutable.
453
+ updatedMap !== originalMap
454
+ const anotherUpdatedMap = originalMap .set (' b' , 1000 )
455
+ // Despite both the results of the same operation, each created a new reference.
456
+ anotherUpdatedMap !== updatedMap
457
+ // However the two are value equal.
458
+ anotherUpdatedMap .equals (updatedMap)
459
+ ```
453
460
454
461
Batching Mutations
455
462
------------------
@@ -493,6 +500,71 @@ and `splice` will always return new immutable data-structures and never mutate
493
500
a mutable collection.
494
501
495
502
503
+ Lazy Seq
504
+ --------
505
+
506
+ ` Seq ` describes a lazy operation, allowing them to efficiently chain
507
+ use of all the sequence methods (such as ` map ` and ` filter ` ).
508
+
509
+ ** Seq is immutable** — Once a Seq is created, it cannot be
510
+ changed, appended to, rearranged or otherwise modified. Instead, any mutative
511
+ method called on a Seq will return a new Seq.
512
+
513
+ ** Seq is lazy** — Seq does as little work as necessary to respond to any
514
+ method call.
515
+
516
+ For example, the following does not perform any work, because the resulting
517
+ Seq is never used:
518
+
519
+ ``` js
520
+ const { Seq } = require (' immutable' )
521
+ const oddSquares = Seq ([ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ])
522
+ .filter (x => x % 2 )
523
+ .map (x => x * x)
524
+ ```
525
+
526
+ Once the Seq is used, it performs only the work necessary. In this
527
+ example, no intermediate arrays are ever created, filter is called three times,
528
+ and map is only called once:
529
+
530
+ ``` js
531
+ console .log (oddSquares .get (1 )); // 9
532
+ ```
533
+
534
+ Any collection can be converted to a lazy Seq with ` .toSeq() ` .
535
+
536
+ <!-- runkit:activate -->
537
+ ``` js
538
+ const { Map } = require (' immutable' )
539
+ const seq = Map ({ a: 1 , b: 2 , c: 3 }).toSeq ()
540
+ ```
541
+
542
+ Seq allows for the efficient chaining of sequence operations, especially when
543
+ converting to a different concrete type (such as to a JS object):
544
+
545
+ ``` js
546
+ seq .flip ().map (key => key .toUpperCase ()).flip ().toObject ();
547
+ // { A: 1, B: 2, C: 3 }
548
+ ```
549
+
550
+ As well as expressing logic that would otherwise seem memory-limited:
551
+
552
+ <!-- runkit:activate -->
553
+ ``` js
554
+ const { Range } = require (' immutable' )
555
+ Range (1 , Infinity )
556
+ .skip (1000 )
557
+ .map (n => - n)
558
+ .filter (n => n % 2 === 0 )
559
+ .take (2 )
560
+ .reduce ((r , n ) => r * n, 1 );
561
+ // 1006008
562
+ ```
563
+
564
+ Note: A Collection is always iterated in the same order, however that order may
565
+ not always be well defined, as is the case for the ` Map ` .
566
+
567
+
496
568
Documentation
497
569
-------------
498
570
0 commit comments