Exploring ReasonML
Please support this book: buy it or donate
(Ad, please don’t block.)

15 Arrays

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.

15.1 Lists vs. arrays

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:

15.2 Creating arrays

The following subsections explain three common ways of creating arrays.

15.2.1 Array literals

# [| "a", "b", "c" |];
- : array(string) = [|"a", "b", "c"|]

15.2.2 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|]

15.2.3 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'|]

15.3 Getting the length of an array

ListLabels.length() returns the length of an array:

# ArrayLabels.length([| "a", "b", "c" |]);
- : int = 3

15.4 Reading and writing array elements

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"|]

15.5 Pattern matching and arrays

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

15.6 Converting between lists and arrays

This is how you convert between lists and arrays:

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).

15.7 Processing arrays

The standard library is still in flux. Therefore, I’ll only demonstrate a few highlights for now.

15.7.1 ArrayLabels.map()

map() for arrays works similar to the same function for lists:

# ArrayLabels.map(s => s ++ "x", [| "a", "b" |]);
- : array(string) = [|"ax", "bx"|]

15.7.2 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

15.7.3 Converting arrays to lists via 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"]

15.7.4 Filtering arrays

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|]