Lists
Lists are
- Immuteable
- Homogeneous
- Fast at adding items as the first item (Head)
The difference between lists and arrays in ReasonML is
Arrays are
- Mutable
- Fast @random access and update
So lets create a list of student names in a class
let students: list(string) = ["Tevin", "Martin"]
let withnewStudents: list(string) = ["Mark", "Theresa", ...students]
We are using prepending when we are adding “Mark” and “Theresa” and then spreading the rest of the items from the previous list.
You may be familiar with the spread operator if you know es6.
Accessing List Items
Lets say you are a teacher and want to call the very first student in the classroom to give him an assignment. If the class is empty, you will do the task yourself.
let call =
switch (withnewStudents) {
| [] => "I'll do it myself"
| [a, ..._] => "Hi " ++ a ++ " please fetch me some water"
};
Recall the recursive function in the functions section that calculated the sum of all items in a list
let rec sum = lst => {
switch lst {
| [] => 0
| [hd, ...tl] => hd + sum(tl)
};
}
List Operations
We’ll go through all the list operations provided in the API documentation and on creating lists and applying the functions on them you will be able to see how to apply the functions on your own in your own projects.
If a function is tail recursive, I will mention it, otherwise I won’t state if it isn’t.
NoteA function call is said to be tail recursive if there is nothing to do after the function returns except return its value. This saves on space complexity as there are no new stack frames for the function.
length: list('a) => int
Gets the length of the list
let students: list(string) = ["Tevin", "Martin"];
let withnewStudents: list(string) = ["Mark", "Theresa", ...students];
let studentsnumber = List.length(withnewStudents);
let hd: list('a) => 'a;
Gets the head of a list / first item and throws exeption of (Failure “hd”) if list is empty.
let withnewStudents: list(string) = ["Mark", "Theresa", ...students];
let first =
switch (List.hd(withnewStudents)) {
| f => f
| exception (Failure("hd")) => "None"
};
let tl: list('a) => list('a);
Returns the tail of a list and throws an exception of (Failure “tl”) if list is empty
let withnewStudents: list(string) = ["Mark", "Theresa", ...students];
let tail =
switch (List.tl(withnewStudents)) {
| f => f
| exception (Failure("tl")) => []
};
let nth: (list('a), int) => 'a;
Returns the nth element of a given list. Throws an exception of (Failure “nth”) if the list is shorter than the index provided. and also throws an exception of Invalid_argument “List.nth” if the index you provide is negative
let students: list(string) = ["Tevin", "Martin"];
let withnewStudents: list(string) = ["Mark", "Theresa", ...students];
let getNthItem = n =>
switch (List.nth(withnewStudents, n)) {
| name => name
| exception (Failure("nth")) => "List is shorter"
| exception (Invalid_argument("nth")) => "You provided a negative index"
};
let rev: list('a) => list('a);
Reverses a list
let students: list(string) = ["Tevin", "Martin"];
let withnewStudents: list(string) = ["Mark", "Theresa", ...students];
let backToForward = List.rev(withnewStudents);
let append: (list('a), list('a)) => list('a);
Concatenates 2 lists and makes them one list
let withnewStudents: list(string) = ["Mark", "Theresa", ...students];
let repeatingStudents: list(string) = ["Cyrus", "Faith"];
let newClass = List.append(withnewStudents, repeatingStudents)
let rev_append: (list('a), list('a)) => list('a);
Reverses list1 and concatenates it to list2. (This function is tail recursive).
let withnewStudents: list(string) = ["Mark", "Theresa", ...students];
let repeatingStudents: list(string) = ["Cyrus", "Faith"];
let newClassInReverse = List.rev_append(withnewStudents, repeatingStudents);
let concat: list(list('a)) => list('a);
Concatenates a list of lists into a list.
let listoflists = [[1, 2], [3, 4], [5, 6]];
let newList = List.concat(listoflists);
let flatten: list(list('a)) => list('a);
Same as concat above
let listoflists = [[1, 2], [3, 4], [5, 6]];
let newList = List.flatten(listoflists);
List iterators
let iter: ('a => unit, list('a)) => unit;
This function returns a unit back, which isnt very interesting, but its useful if you are using it to perform impure function calls. Its like forEach in Javascript
let printOne = n => print_endline(string_of_int(n));
let newListIteration = List.iter(printOne, [1, 2, 3]);
let iteri: ((int, 'a) => unit, list('a)) => unit;
Same as List.iter but provides index of the item then the item itself as the second parameter.
let printTwo = (idx, n) =>
print_endline(
"The item at index " ++ string_of_int(idx) ++ " is " ++ string_of_int(n),
);
let newListIterWithIndx = List.iter2(printTwo, [1, 3, 4, 6]);
let map: ('a => 'b, list('a)) => list('b);
Map takes a list A and returns a new List B after applying a function F on each element on A -> B.
let addOneNum = n => n + 1;
let addNew = List.map(addOneNum, [1,2,4])
let mapi: ((int, 'a) => 'b, list('a)) => list('b);
Same as map, but the function to be applied receives 2 parameters , the index of the item then the item itself.
let addOneWithIndex = (indx: int, item: int) => {
switch (indx) {
| 0 => 11111
| _ => item
};
};
List.mapi(addOneWithIndex, [1, 2, 3, 4]);
/**The first item in the new list will be 11111**/
let rev_map: ('a => 'b, list('a)) => list('b);
This function is tail recursive and is similar to map above, but in this case it reverses the order of the list.
let plusOne = n => n + 1;
let reversalList = List.rev_map(plusOne, [1, 2, 3, 4, 5]);
If you are familiar with Javascript’s reduce fold functions work the same way. They combine elements of a list into 1.
let fold_left: (('a, 'b) => 'a, 'a, list('b)) => 'a;
Fold_left folds a list from left to right
((0+a)+b)+c
let accFunction = (acc, item) => acc + item;
let sum = List.fold_left(accFunction, 0, [1, 2, 3]);
let fold_right: (('a, 'b) => 'b, list('a), 'b) => 'b;
Folds a list from right to left. This function is not efficient as it isn’t tail recursive.
let accFunction = (acc, item) => acc + item;
let sum = List.fold_right(accFunction, [1, 2, 3], 0);
Iterators on two lists
let iter2: (('a, 'b) => unit, list('a), list('b)) => unit;
This function takes a function that returns unit after receiving items from each list , then takes on 2 lists.
The two lists must be of the same length otherwise an exception will be raised.
let printFromBothLists = (l1, l2) => {
print_endline(
"From list 1 "
++ string_of_int(l1)
++ " From list 2 "
++ string_of_int(l2),
);
};
let printMsg = List.iter2(printFromBothLists, [1, 2, 3], [4, 5, 6]);
let map2: (('a, 'b) => 'c, list('a), list('b)) => list('c);
Takes 2 lists. One of type a the other type band returns a third list of type c
an exception is raised if the lists don’t match in length.
let multiply = (fst, snd) => fst *. snd;
let multiplyList =
switch (List.map2(multiply, [1.1, 2.2, 3.3], [4.4, 5.5, 6.6])) {
| floats => floats
| exception (Invalid_argument(_)) => []
};
let rev_map2: (('a, 'b) => 'c, list('a), list('b)) => list('c);
Just like rev_map its an efficient tail recursive function. but reverses the order of the lists.
let revFn = (fst, snd) => string_of_int(fst) ++ " " ++ string_of_int(snd);
let revMapping = List.rev_map2(revFn, [1, 2], [3, 4]);
let fold_left2: (('a, 'b, 'c) => 'a, 'a, list('b), list('c)) => 'a;
Unlike fold_left fold_left2 now folds two lists into a single value.
let fldLeft2 = (acc, fst, snd) => acc + fst + snd;
let sumTwoLists = List.fold_left2(fldLeft2, 0, [1, 2, 3], [4, 5, 6]);
let fold_right2: (('a, 'b, 'c) => 'c, list('a), list('b), 'c) => 'c;
Its not tail recursive, but folds two lists into a single value. from right to left.
let fldRight2 = (acc, fst, snd) => acc + fst + snd;
let sumLists = List.fold_right2(fldLeft2, [1, 2, 3], [4, 5, 6], 0);
All the above functions throw an error when the length of the two lists do not match.
See the implimentation of map2 to know how to handle the errors.
List scanning
let for_all: ('a => bool, list('a)) => bool;
Checks if all items in a list satisfy the predicate function ('a => bool)
NoteA predicate function is a function that takes an item and returns boolean.
let isItemLessThan10 = itm => itm < 10;
let scanForLessThn10 = List.for_all(isItemLessThan10, [1, 2, 3, 4, 5]);
let exists: ('a => bool, list('a)) => bool;
Checks if at least one item satisfies the predicate
let isItemLessThan10 = itm => itm < 10;
let scanAnyForLessThn10 = List.exists(isItemLessThan10, [1, 2, 3, 4, 5]);
let for_all2: (('a, 'b) => bool, list('a), list('b)) => bool;
Takes 2 lists and returns a boolean to based on whether all elements in the 2 lists pass the predicate function
let checkBothAreLessThan10 = (fst, snd) => fst < 10 && snd < 10;
let areBothListsSmall =
List.for_all2(checkBothAreLessThan10, [4, 5, 6], [7, 8, 9]);
let exists2: (('a, 'b) => bool, list('a), list('b)) => bool;
Checks if item in any of the two lists passes the predicate function.
let checkIfAnyAreLessThan10 = (fst, snd) => fst < 10 || snd < 10;
let isAnyItemInBothListsSmall =
List.exists2(checkIfAnyAreLessThan10, [2, 3, 4], [6, 7, 8]);
let mem: ('a, list('a)) => bool;
returns true if any element in the list is equal to a.
let isThereASix = List.mem(6, [1, 2, 6, 7]);
let memq: ('a, list('a)) => bool;
Same as List.mem but uses physical instead of structural equality.
NoteStructural equality compares the contents of two items, for example, if two strings have the same sequence of characters. Physical equality compares, the addresses of the two items to see if they are the same chunk of memory.
So we have covered,
- List operations in general
- List iteration
- 2 Lists iteration
- List scanning.
There’s more to this check the api docs and you’ll see
https://reasonml.github.io/api/List.htmlYou can play around with list operations and see whats possible for you.