|
| 1 | +# IArray |
| 2 | + |
| 3 | +An Immutable Array that looks, behaves (and IS) a standard JavaScript Array but with immutable properties. |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## Why |
| 8 | + |
| 9 | +One of the cornerstones of functional programming is immutable data structures. Using immutable data structures insures to the author and other programmers that once they have a handle to an object, it won't change beneath their feet. This is important for reasoning about code. It aids with testing, debugging and refactoring. It provides a much faster way to determine if state has changed. And it helps you write pure functions. |
| 10 | + |
| 11 | +Arrays are an extremely popular data structure in most JavaScript programs. But they are far from immutable. One can simply assign values to an array at a given index via `myArray[10] = 100`. And many of the Array methods are mutating, such as `push`, `sort` and `slice`. |
| 12 | + |
| 13 | +Some libraries exist that provide random access data structures similar to the Array but that are immutable, such as [Mori](http://swannodette.github.io/mori/) or [Immutable](https://facebook.github.io/immutable-js/) - but they are large opinionated libraries that expose a completely different API. |
| 14 | + |
| 15 | +**IArray** provides an Immutable array only, and is very light. |
| 16 | + |
| 17 | +```bash |
| 18 | +wc -l IArray.js |
| 19 | +119 IArray.js |
| 20 | +``` |
| 21 | + |
| 22 | +119 lines in the *source* file, much of which is comments and the universal module definition. |
| 23 | + |
| 24 | +## How |
| 25 | + |
| 26 | +`IArray` extends the `Array` object by building a prototype which includes `Array.prototype`. This enables `IArray` to offer all the API methods contained in the standard `Array`, and appear to debuggers and most other type checkers as an `Array` |
| 27 | + |
| 28 | +```javascript |
| 29 | +const a = IArray() |
| 30 | +if(a instanceof Array) // true! |
| 31 | +// ... |
| 32 | +``` |
| 33 | + |
| 34 | +But `IArray` intercepts calls to mutating methods, such as `push` and internally clones the array, calls the `Array.push` using your same arguments, and finally returning a new `IArray` with the result. |
| 35 | + |
| 36 | +So mutating methods all exist, but their behavior is slightly different - returning a new `IArray` with the changes applied, rather than mutating the array upon which its called. |
| 37 | + |
| 38 | +In cases where a method mutated the underlying array and also returned a value (such as `pop`), the value is available at `IArray.ret`. |
| 39 | + |
| 40 | +## API |
| 41 | + |
| 42 | +Since this extends the standard JavaScript `Array`, I will only document the methods that have *changed* from the standard Array API: |
| 43 | + |
| 44 | + |
| 45 | +### `concat(value1[, value2 ...]) => IArray` |
| 46 | + |
| 47 | +Behaves just as the [standard](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat) `Array.concat` with the exception that the returned array is an `IArray`. |
| 48 | + |
| 49 | +```javascript |
| 50 | +IArray([1, 2, 3]).concat([7, 8]) // => IArray([1, 2, 3, 7, 8]) |
| 51 | +IArray().concat(IArray([3, 4, 5]) // => IArray([3, 4, 5]) |
| 52 | +IArray([1, 2, 3]).concat(100) // => IArray([1, 2, 3, 100]) |
| 53 | +``` |
| 54 | +
|
| 55 | +
|
| 56 | +### `copyWithin(target[, start[, end]]) => IArray` |
| 57 | +
|
| 58 | +Behaves just as the [standard](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/copyWithin) `Array.copyWithin` with the exception that the returned array is an `IArray` and the original array is not modified |
| 59 | +
|
| 60 | +```javascript |
| 61 | +var a = IArray([5, 6, 7, 8]) |
| 62 | +var b = a.copyWithin(2) // => IArray([5, 6, 5, 6]) (a unmodified) |
| 63 | +``` |
| 64 | +
|
| 65 | +### `fill(target[, start[, end]]) => IArray` |
| 66 | +
|
| 67 | +Behaves just as the [standard](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fill) `Array.fill` with the exception that the returned array is an `IArray`. |
| 68 | +
|
| 69 | +```javascript |
| 70 | +var a1 = IArray([1, 2, 3, 4, 5]) |
| 71 | +var a2 = a1.fill(9, 2, 4) |
| 72 | +// a1 = IArray([1, 2, 3, 4, 5]) - this shouldn't change |
| 73 | +// a2 = IArray([1, 2, 9, 9, 5]) - fill 9s starting at item 2, ending at 4 |
| 74 | +``` |
| 75 | +
|
| 76 | +### `filter(fn[, thisArg]) => IArray` |
| 77 | +
|
| 78 | +Behaves just as the [standard](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) `Array.filter` with the exception that the returned array is an `IArray`. |
| 79 | +
|
| 80 | +```javascript |
| 81 | +function even(n) { return n % 2 === 0 } |
| 82 | + |
| 83 | +var a1 = IArray([1, 2, 3, 4, 5, 6]) |
| 84 | +var a2 = a1.filter(even) |
| 85 | +// a1 = IArray([1, 2, 3, 4, 5, 6]) - this shouldn't change |
| 86 | +// a2 = IArray([2, 4, 6]) - even values only |
| 87 | +``` |
| 88 | +
|
| 89 | +### `map(fn[, thisArg]) => IArray` |
| 90 | +
|
| 91 | +Behaves just as the [standard](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) `Array.map` with the exception that the returned array is an `IArray`. |
| 92 | +
|
| 93 | +```javascript |
| 94 | +var a1 = IArray([1, 4, 9, 25]) |
| 95 | +var a2 = a1.map(Math.sqrt) |
| 96 | +// a1 = IArray([1, 4, 9, 25]) - ensure it is unchanged |
| 97 | +// a2 = IArray([1, 2, 3, 5]) - mapped values |
| 98 | +``` |
| 99 | +
|
| 100 | +### `push(value1, ..., valuen) => IArray` |
| 101 | +
|
| 102 | +The same API signature as the [standard](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push) `Array.push`, but the original `IArray` is not modified. A new `IArray` is returned with the value(s) appended, and the length property is stored in the `ret` property (which is mostly redundant since it is also available in the `length` property - but is included for consistency) |
| 103 | +
|
| 104 | +```javascript |
| 105 | +var a1 = IArray([1, 4, 9]) |
| 106 | +var a2 = a1.push(100) |
| 107 | +// a1 = IArray([1, 4, 9]) |
| 108 | +// a2 = IArray([1, 4, 9, 100]) |
| 109 | +``` |
| 110 | +
|
| 111 | +### `pop() => IArray` |
| 112 | +
|
| 113 | +The same API signature as the [standard](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/pop) `Array.pop`, but the original `IArray` is not modified. A new `IArray` is returned with the last value removed, and the value that was removed is stored in the `ret` property. |
| 114 | +
|
| 115 | +```javascript |
| 116 | +var a1 = IArray([1, 4, 9]) |
| 117 | +var a2 = a1.pop() |
| 118 | +// a1 = IArray([1, 4, 9]) - unchanged |
| 119 | +// a2 = IArray([1, 4]) - same as a1 with last element removed |
| 120 | +// a2.ret = 9 - last element stored in ret property |
| 121 | +``` |
| 122 | +
|
| 123 | +### `shift() => IArray` |
| 124 | +
|
| 125 | +The same API signature as the [standard](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift) `Array.shift`, but the original `IArray` is not modified. A new `IArray` is returned with the first element removed. This removed element is available through the `ret` property of the returned `IArray`. |
| 126 | +
|
| 127 | +```javascript |
| 128 | +var a1 = IArray([1, 4, 9]) |
| 129 | +var a2 = a1.shift() |
| 130 | + |
| 131 | +// a1 = IArray([1, 4, 9]) - no change |
| 132 | +// a2 = IArray([4, 9]) - first value removed |
| 133 | +// a2.ret = 1 - removed value |
| 134 | +``` |
| 135 | +
|
| 136 | +### `slice([begin[, end]]) => IArray` |
| 137 | +
|
| 138 | +Behaves just as the [standard](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) `Array.slice` with the exception that the returned array is an `IArray`. |
| 139 | +
|
| 140 | +```javascript |
| 141 | +var a1 = IArray([5, 10, 15, 20]) |
| 142 | +var a2 = a1.slice(1, 3) |
| 143 | +// a1 = IArray([5, 10, 15, 20])) - no changes |
| 144 | +// a2 = IArray([10, 15])) - elements 1 to 3 (non-inclusive) |
| 145 | +``` |
| 146 | +
|
| 147 | +### `sort([compareFn]) => IArray` |
| 148 | +
|
| 149 | +Behaves just like the [standard](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) `Array.sort` with the exception that the original `IArray` is not modified, and the newly sorted `IArray` is returned. |
| 150 | +
|
| 151 | +```javascript |
| 152 | +var fruit = ["cherries", "apples", "bananas", "pears"] |
| 153 | +var a1 = IArray(fruit) |
| 154 | +var a2 = a1.sort() |
| 155 | +var a3 = a1.sort(function(a, b) { return a.length - b.length }) // sort by word length |
| 156 | +// a1 remains unchanged: IArray(["cherries", "apples", "bananas", "pears"]) |
| 157 | +// a2 is sorted: IArray(["apples", "bananas", "cherries", "pears"]) |
| 158 | +// a3 is sorted according to comparitor: IArray(["pears", "apples", "bananas", "cherries"]) |
| 159 | +``` |
| 160 | +
|
| 161 | +### `splice([start[, deleteCount[, value1, value2, ...]]]) => IArray` |
| 162 | +
|
| 163 | +Behaves just like the [standard](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice) `Array.splice` with the exception that the original `IArray` is not modified but is returned as a new `IArray`. The removed items will be stored as an `IArray` on the `ret` property. |
| 164 | +
|
| 165 | +```javascript |
| 166 | +var a1 = IArray([5, 10, 15, 20]) |
| 167 | +var a2 = a1.splice(1, 1, 22) |
| 168 | +// a1 is unchanged |
| 169 | +// a2 is IArray([5, 22, 15, 20]) - item 1 removed and replaced with 22 |
| 170 | +// a2.ret is IArray([10]) - the removed items in an IArray |
| 171 | +``` |
| 172 | +
|
| 173 | +
|
| 174 | +### `unshift([element1, ..., elementN]) => IArray` |
| 175 | +
|
| 176 | +The same API signature as the [standard](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift) `Array.unshift`, but the original `IArray` is not modified. A new `IArray` is returned with elements added to the front of the array. |
| 177 | +
|
| 178 | +```javascript |
| 179 | +var a1 = IArray([5, 10, 20]) |
| 180 | +var a2 = a1.unshift("hello", "world") |
| 181 | + |
| 182 | +// a1 = IArray([5, 10, 20])) - unchanged |
| 183 | +// a2 = IArray(["hello", "world", 5, 10, 20])) |
| 184 | +``` |
| 185 | +
|
| 186 | +
|
| 187 | +
|
| 188 | +### License |
| 189 | +
|
| 190 | +See the LICENSE file for license rights and limitations (MIT). |
0 commit comments