Time complexity of uncons called on Array is O(n) because .slice() is used to preserve purity. Thus iterating over Array using uncons is O(n2).
But since purescript Arrays are persistent at runtime, it is possible to defer multiple .slice() calls by introducing another data structure and operating on it instead.
ArrayView contains a reference to some Array coupled with two numbers: index where the view starts relative to the beginning of the array and the length of the view.
newtype ArrayView a = View { from :: Int, len :: Int, arr :: Array a }So, instead of slicing, it is possible to just shift the indices.
Obviously, this technique does not improve the asymptotics of cons/snoc/append and other array constructing functions, so if the code uses these, there will be no benefit in replacing Array with ArrayView.
It should be noted once more that iterating over ordinary arrays using cons and uncons, which is common in Haskell (with lists) is a bad practice. Often such code can be refactored to use some type of fold and/or other standard functionals. However, there are cases where it is not straightforward, or where using uncons/unsnoc is more syntactically appealing, e.g. when iterating over many structures at once.
For every function in Data.Array there is a corresponding function in Data.ArrayView, though most of them are plain reuses up to conversions between Array and ArrayView. Those with different time complexities are listed below:
| functions | Array / NonEmptyArray | ArrayView / NonEmptyArrayView | Note |
|---|---|---|---|
slice, uncons, unsnoc, tail, init, take, drop, takeEnd, dropEnd |
O(n) | O(1) | n is the length of the resulting array |
span (used by takeWhile, dropWhile) |
O(n+m) | O(n) | n is the length of the init array, m is the length of the rest |
Data.ArrayView.toArray |
- | O(n) | O(1) if the given view corresponds to the whole array |
Data.ArrayView.fromArray |
O(1) | - |
This package's API mimics the API of purescript-arrays up to certain extent. For most use cases, just changing the imports is enough.
This table may be useful for incorporating this library into existing codebase:
| Name | Replacement | Note |
|---|---|---|
Data.Array |
Data.ArrayView |
|
Data.Array.NonEmpty |
Data.ArrayView.NonEmpty |
|
Data.Array.NonEmpty.fromArray |
Data.ArrayView.NonEmpty.fromArrayView |
|
Data.Array.NonEmpty.toArray |
Data.ArrayView.NonEmpty.toArrayView |
|
Data.Array.some, many |
Data.ArrayView.some, many |
Lazy (f (Array a)) constaint is not changed to Lazy (f (ArrayView a)) because of OrphanInstances |
Data.Array.NonEmpty.appendArray |
Data.ArrayView.NonEmpty.appendArrayView |
Since every ArrayView holds a reference to some array, the latter can't be garbage-collected while the former is used. This leads to a memory consumption overhead.
If you need to free unused parts of array, use Data.ArrayView.force :: forall a. ArrayView a -> ArrayView a (which performs slicing and therefore is O(n)).
purescript-slices is another implementation of the same technique.
Module documentation is published on Pursuit.
