In this chapter, we look at the ReasonML data structure arrays.
Arrays are a mutable data structure with random access whose elements all have the same type. It is especially well suited for large amounts of data and whenever you need random access.
We’ll again prefer the labeled module ListArray
over the unlabeled Array
when we are accessing functionality from the standard library. Consult the chapter on lists for details.
The following table compares lists and arrays.
Lists | Arrays | |
---|---|---|
Size | small–medium | small–large |
Resizable? | flexible | fixed |
Mutability | immutable | mutable |
Elem types | same | same |
Access via | destructuring | index |
Fastest | prepend/remove first | read/write elems |
Arrays are much like lists: all of their elements have the same type and they are accessed by position. But they are also different:
The following subsections explain three common ways of creating arrays.
# [| "a", "b", "c" |];
- : array(string) = [|"a", "b", "c"|]
ArrayLabels.make()
Signature:
let make: (int, 'a) => array('a);
The first parameter specifies the length of the result. The second parameter specifies the value it is to be filled with. Why is the second parameter mandatory? The result of make()
must only contain values of type 'a
. ReasonML has no null
, so you must pick a member of type 'a
, manually.
This is how make()
works:
# ArrayLabels.make(3, "x");
- : array(string) = [|"x", "x", "x"|]
# ArrayLabels.make(3, true);
- : array(bool) = [|true, true, true|]
ArrayLabels.init()
Signature:
let init: (int, ~f: int => 'a) => array('a);
The first parameter specifies the length of the result. The function ~f
maps an index to an initial value at that index. For example:
# ArrayLabels.init(~f=i=>i, 3);
- : array(int) = [|0, 1, 2|]
# ArrayLabels.init(~f=i=>"abc".[i], 3);
- : array(char) = [|'a', 'b', 'c'|]
ListLabels.length()
returns the length of an array:
# ArrayLabels.length([| "a", "b", "c" |]);
- : int = 3
This is how you read and write array elements:
# let arr = [| "a", "b", "c" |];
let arr: array(string) = [|"a", "b", "c"|];
# arr[1]; /* read */
- : string = "b"
# arr[1] = "x"; /* write */
- : unit = ()
# arr;
- : array(string) = [|"a", "x", "c"|]
Pattern-matching arrays is similar to matching tuples, not to matching lists. Let’s start with tuples and lists (we can ignore the exhaustiveness warnings, because we are working with fixed data):
# let (a, b) = (1, 2);
let a: int = 1;
let b: int = 2;
# let [a, ...b] = [1, 2, 3];
Warning: this pattern-matching is not exhaustive.
let a: int = 1;
let b: list(int) = [2, 3];
We’ll destructure an array next:
# let [| a, b |] = [| 1, 2 |];
Warning: this pattern-matching is not exhaustive.
let a: int = 1;
let b: int = 2;
Similar to tuples, the pattern must have the same length as the data (that’s what the exception is about):
# let [| a, b |] = [| 1, 2, 3 |];
Warning: this pattern-matching is not exhaustive.
Exception: Match_failure
This is how you convert between lists and arrays:
From array to list (module ArrayLabels
):
let to_list: array('a) => list('a);
From list to array (module ArrayLabels
):
let of_list: list('a) => array('a);
Sometimes you have data in an array that would be easier to process in a list. Then you can convert it to a list (and convert it back to an array afterwards, should that be needed).
The standard library is still in flux. Therefore, I’ll only demonstrate a few highlights for now.
ArrayLabels.map()
map()
for arrays works similar to the same function for lists:
# ArrayLabels.map(s => s ++ "x", [| "a", "b" |]);
- : array(string) = [|"ax", "bx"|]
ArrayLabels.fold_left()
fold_left()
is also similar to its list version:
let maxOfArray = (arr) =>
ArrayLabels.fold_left(~f=max, ~init=min_int, arr);
This is how maxOfArray()
is used:
# maxOfArray([||]);
- : int = -4611686018427387904
# maxOfArray([|3, -1, 5|]);
- : int = 5
Once again, we have used fold
to go from a binary operation (max()
) to an n-ary operation (maxOfArray
). In addition to max()
, we also use the integer constant min_int
. Both are part of module Pervasives
and therefore available without qualification.
max
is a binary function that works for most types:
# max(1.0, 1.1);
- : float = 1.1
# max(None, Some(1));
- : option(int) = Some(1)
# max("a", "b");
- : string = "b"
# max(4, -3);
- : int = 4
min_int
is the lowest possible int value (its exact value depends on the platform that you are using):
# min_int;
- : int = -4611686018427387904
fold_right()
fold_right()
works like fold_left()
, but it starts with the last element. Its type signature is:
let fold_right: (~f: ('b, 'a) => 'a, array('b), ~init: 'a) => 'a;
One use case for this function is converting an array to a list. That list has to be constructed as follows (i.e., you have to start with the last array element):
[··· [x_2nd_last, ...[x_last, ...[]]]]
The function looks like this:
let listFromArray = (arr: array('a)) =>
ArrayLabels.fold_right(~f=(ele, l) => [ele, ...l], arr, ~init=[]);
This is listFromArray()
in action:
# listFromArray([||]);
- : list('a) = []
# listFromArray([| 1, 2, 3 |]);
- : list(int) = [1, 2, 3]
# listFromArray([| "a", "b", "c" |]);
- : list(string) = ["a", "b", "c"]
All array functions return arrays that have the same length as the input arrays. Therefore, if you want to remove elements, you have to take a detour via lists:
let filterArray = (~f, arr) =>
arr
|> ArrayLabels.to_list
|> ListLabels.filter(~f)
|> ArrayLabels.of_list;
filterArray()
in use:
# filterArray(~f=x=>x>0, [|-2, 3, -4, 1|]);
- : array(int) = [|3, 1|]