List

Standard library for list operations.

Functions in this module operate on type [A] (equivalent to List<A>), which represents a linked list with O(1) put() and O(n) at() and delete(). Literal lists can be created with the syntax [a, b, ...], where a, b, etc. are elements.

To import all names from this module, use:

import List (*)

With no import, you can still access anything with the prefix List., like List.at.

Index

NameTypeDescription

head(l)

[A] -> A

Returns the first element in the list l.

tail(l)

[A] -> [A]

Returns a list containing all elements in l except the first.

length(sized)

[A] -> Int

Returns the length of sized.

empty?(sized)

[A] -> Bool

Returns whether sized is empty.

concat(a, b)

([A], [A]) -> [A]

Concatenates a and b together, equivalent to a ++ b.

concat_all(l)

[[A]] -> [A]

Concatenates all elements in the list l together.

map(container, f)

([A], A -> B) -> [B]

Calls f for each element in container, collecting the results into a new container.

at(l, i)

([A], Int) -> A

Returns the element at index i in l.

slice(l, i, len)

([A], Int, Int) -> [A]

Returns a sublist of l starting at index i with length len.

range(l, start, end)

([A], Int, Int) -> [A]

Returns a sublist of l starting at index start and ending at index end, inclusive.

split_at(l, i)

([A], Int) -> ([A], [A])

Returns two lists: the first contains all elements from index 0 to i - 1, and the second contains all elements from index i to the end of the list.

take(l, n)

([A], Int) -> [A]

Takes n elements from the beginning of l, returning a new list.

drop(l, n)

([A], Int) -> [A]

Drops n elements from the beginning of l, returning a new list.

take_while(l, f)

([A], A -> Bool) -> [A]

Takes elements from l while f returns true, returning a new list.

drop_while(l, f)

([A], A -> Bool) -> [A]

Drops elements from l while f returns true, returning a new list.

split_while(l, f)

([A], A -> Bool) -> ([A], [A])

Takes elements from l while f returns true, and returns two new lists: the elements that were taken, and all remaining elements.

partition(l, f)

([A], A -> Bool) -> ([A], [A])

Returns two new lists: one containing elements for which f returns true, and the other containing elements for which f returns false.

reverse(l)

[A] -> [A]

Reverses the list l.

flat_map(l, f)

([A], A -> [B]) -> [B]

Calls f for each element in l, concatenating the resultant lists together into one list.

foldr(l, init, f)

([A], F, (F, A) -> F) -> F

Same as fold, expect that it goes through l in the reverse order, calling f with the last element, then the second-to-last element, etc.

foldr_map(l, init, f)

([A], F, (F, A) -> (F, B)) -> (F, [B])

Same as fold_map, expect that it goes through l in the reverse order, calling f with the last element, then the second-to-last element, etc.

sum(l)

[A ~ Num] -> A ~ Num

Returns the sum of all numbers in l.

sort(l)

[A ~ Ord] -> [A ~ Ord]

Sorts l in ascending order.

sort_by(l)

([A], A -> B ~ Ord) -> [A]

Sorts l in ascending order based on the return value of f for each element.

sort_cmp(l, f)

([A], (A, A) -> Bool) -> [A]

Sorts l in ascending order based on the comparator function f.

join(l, sep)

([String], String) -> String

Joins the list of strings l with the separator sep, returning a string.

max_in(l)

[A ~ Ord] -> Option<A ~ Ord>

Returns the maximum element in l as Some(max), or None if l is empty.

min_in(l)

[A ~ Ord] -> Option<A ~ Ord>

Returns the minimum element in l as Some(min), or None if l is empty.

max_by(l, f)

([A], A -> B ~ Ord) -> Option<A>

Returns the element in l for which f returns the maximum value.

min_by(l, f)

([A], A -> B ~ Ord) -> Option<A>

Returns the element in l for which f returns the minimum value.

seq(start, end)

(Int, Int) -> [Int]

Returns a list containing the sequence of integers from start to end, both inclusive.

repeat(a, n)

(A, Int) -> [A]

Creates a new list that has the element a repeated n times.

zip(l1, l2)

([A], [B]) -> [(A, B)]

Returns a list where the i-th element is a tuple containing the i-th element of l1 and the i-th element of l2.

unzip(l)

[(A, B)] -> ([A], [B])

Unpacks a list of tuples l into two separate lists.

group_by(l, f)

([A], A -> B) -> Map<B, [A]>

Calls f on each element of l, returning a map from each return value of f to the list of elements that gave that return value.

unique(l)

[A] -> [A]

Removes duplicates in l, returning a new list.

unique_by(l, f)

([A], A -> B) -> [A]

Removes duplicates in l, returning a new list, where two elements are duplicates if f returns the same value for them.

with_index(l)

[A] -> [(A, Int)]

Pairs each element in l with its index, returning a new list of tuples.

each(l, f)

([A], A -> B) -> ()

Calls f on each element of l.

fold(collection, init, f)

([A], F, (F, A) -> F) -> F

Computes a single value by applying the combining function f to each element of the given collection, starting with the initial value init.

filter(collection, f)

([A], A -> Bool) -> [A]

Calls f for each element in collection, returning a new collection that only contains the elements for which f returns true.

find(collection, f)

([A], A -> Bool) -> Option<A>

Returns the first element in collection for which f returns true.

filter_map(collection, f)

([A], A -> Option<B>) -> [B]

Calls f for each element in collection, returning a new collection that contains every e such that f returned Some(e).

fold_map(collection, init, f)

([A], F, (F, A) -> (F, B)) -> (F, [B])

Computes a new collection and a single value by applying the combining function f to each element of the given collection, starting with the initial value init.

put(collection, elem)

([A], A) -> [A]

Puts elem into collection, returning a new collection.

delete(collection, elem)

([A], A) -> [A]

Removes elem from collection, returning a new collection.

contains?(collection, elem)

([A], A) -> Bool

Returns true if collection contains elem.

all?(collection, f)

([A], A -> Bool) -> Bool

Calls f for each element in collection, and returns true if f returns true for every element.

any?(collection, f)

([A], A -> Bool) -> Bool

Calls f for each element in collection, and returns true if f returns true for at least one element.

to_set(a)

[A] -> Set<A>

Converts a to a set.

to_map(a)

[(K, V)] -> Map<K, V>

Converts a to a map.

Functions

Returns the first element in the list l. Raises EmptyList if l is empty.

This function is part of the Base module, meaning it's a builtin that's in the global namespace, so it doesn't need to be imported.

assert head([1]) == 1
assert head(["hi", "hey", "hello"]) == "hi"
tail : [A] -> [A]
tail(l)

Returns a list containing all elements in l except the first. Raises EmptyList if l is empty.

This function is part of the Base module, meaning it's a builtin that's in the global namespace, so it doesn't need to be imported.

assert tail([1]) == []
assert tail(["hi", "hey", "hello"]) == ["hey", "hello"]
at : ([A], Int) -> A
at(l, i)

Returns the element at index i in l. i can be negative, where -1 is the last index, -2 is the second-to-last index, etc. If at tries to access an element at an invalid index, it raises BadListIndex.

assert at(["cow", "dog", "cat"], 0) == "cow"
assert at(["cow", "dog", "cat"], -1) == "cat"
assert at([42.3, .428, 13.3], 2) == 13.3
assert at([42.3, .428, 13.3], -3) == 42.3
slice : ([A], Int, Int) -> [A]
slice(l, i, len)

Returns a sublist of l starting at index i with length len. i can be negative, where -1 is the last index, -2 is the second-to-last index, etc. If len <= 0, returns []. If slice tries to access an element at an invalid index, it raises BadListIndex.

assert slice(["cow", "dog", "cat"], 0, 2) == ["cow", "dog"]
assert slice(["cow", "dog", "cat"], -1, 1) == ["cat"]
assert slice(["cow", "dog", "cat"], 3, 0) == []
assert slice([42.3, .428, 13.3], -3, 2) == [42.3, .428]
range : ([A], Int, Int) -> [A]
range(l, start, end)

Returns a sublist of l starting at index start and ending at index end, inclusive. start and end can be negative, where -1 is the last index, -2 is the second-to-last index, etc. If start > end (after resolving negative indexes), returns []. If range tries to access an element at an invalid index, it raises BadListIndex.

assert range(["cow", "dog", "cat"], 0, 1) == ["cow", "dog"]
assert range(["cow", "dog", "cat"], -1, 2) == ["cat"]
assert range(["cow", "dog", "cat"], 3, 1) == []
assert range([42.3, .428, 13.3], -3, 1) == [42.3, .428]
split_at : ([A], Int) -> ([A], [A])
split_at(l, i)

Returns two lists: the first contains all elements from index 0 to i - 1, and the second contains all elements from index i to the end of the list. i can be negative, where -1 is the last index, -2 is the second-to-last index, etc. If split_at tries to access an element at an invalid index, it raises BadListIndex.

assert split_at(["cow", "dog", "cat"], 0) == ([], ["cow", "dog", "cat"])
assert split_at(["cow", "dog", "cat"], -1) == (["cow", "dog"], ["cat"])
assert split_at([42.3, .428, 13.3], 2) == ([42.3, .428], [13.3])
assert split_at([42.3, .428, 13.3], -3) == ([], [42.3, .428, 13.3])
take : ([A], Int) -> [A]
take(l, n)

Takes n elements from the beginning of l, returning a new list. If l has fewer than n elements, returns l. If n is negative, returns [].

assert take(["cow", "dog", "cat"], 2) == ["cow", "dog"]
assert take(["cow", "dog", "cat"], 10) == ["cow", "dog", "cat"]
assert take(["cow", "dog", "cat"], 0) == []
assert take(["cow", "dog", "cat"], -3) == []
drop : ([A], Int) -> [A]
drop(l, n)

Drops n elements from the beginning of l, returning a new list. If l has fewer than n elements, returns []. If n is negative, returns l.

assert drop(["cow", "dog", "cat"], 2) == ["cat"]
assert drop(["cow", "dog", "cat"], 10) == []
assert drop(["cow", "dog", "cat"], 0) == ["cow", "dog", "cat"]
assert drop(["cow", "dog", "cat"], -3) == ["cow", "dog", "cat"]
take_while : ([A], A -> Bool) -> [A]
take_while(l, f)

Takes elements from l while f returns true, returning a new list. f accepts an element and returns true to take it or false to stop.

assert take_while(["cow", "dog", "cat"], |a| a < "d") == ["cow"]
assert take_while(["cow", "dog", "cat"], |a| a > "e") == []
assert take_while([42.3, .428, 13.3], |a| a < 50) ==
  [42.3, .428, 13.3]
assert take_while([], |_| true) == []
drop_while : ([A], A -> Bool) -> [A]
drop_while(l, f)

Drops elements from l while f returns true, returning a new list. f accepts an element and returns true to take it or false to stop.

assert drop_while(["cow", "dog", "cat"], |a| a < "d") == ["dog", "cat"]
assert drop_while(["cow", "dog", "cat"], |a| a > "e") ==
  ["cow", "dog", "cat"]
assert drop_while([42.3, .428, 13.3], |a| a < 50) == []
assert drop_while([], |_| true) == []
split_while : ([A], A -> Bool) -> ([A], [A])
split_while(l, f)

Takes elements from l while f returns true, and returns two new lists: the elements that were taken, and all remaining elements.

assert split_while(["cow", "dog", "cat"], |a| a < "d") ==
  (["cow"], ["dog", "cat"])
assert split_while(["cow", "dog", "cat"], |a| a > "e") ==
  ([], ["cow", "dog", "cat"])
assert split_while([42.3, .428, 13.3], |a| a < 50) ==
  ([42.3, .428, 13.3], [])
assert split_while([], |_| true) == ([], [])
partition : ([A], A -> Bool) -> ([A], [A])
partition(l, f)

Returns two new lists: one containing elements for which f returns true, and the other containing elements for which f returns false.

assert partition(["cow", "dog", "cat"], |a| a < "d") ==
  (["cow", "cat"], ["dog"])
assert partition(["cow", "dog", "cat"], |a| a > "e") ==
  ([], ["cow", "dog", "cat"])
assert partition([42.3, .428, 13.3], |a| a < 1) ==
  ([.428], [42.3, 13.3])
assert partition([], |a| true) == ([], [])
reverse : [A] -> [A]
reverse(l)

Reverses the list l.

assert reverse(["cow", "dog", "cat"]) == ["cat", "dog", "cow"]
assert reverse([42.3, .428, 13.3]) == [13.3, .428, 42.3]
assert reverse([]) == []
flat_map : ([A], A -> [B]) -> [B]
flat_map(l, f)

Calls f for each element in l, concatenating the resultant lists together into one list. f accepts an element in l and returns a list.

assert flat_map([42.3, .428, 13.3], |a| [a, a + 0.1]) ==
  [42.3, 42.4, .428, .528, 13.3, 13.4]
assert flat_map([42.3, .428, 13.3], |a| [a]) == [42.3, .428, 13.3]
assert flat_map(["cow", "dog", "cat"], String.to_chars) ==
  String.to_chars("cowdogcat")
assert flat_map([], |a| [a]) == []
foldr : ([A], F, (F, A) -> F) -> F
foldr(l, init, f)

Same as fold, expect that it goes through l in the reverse order, calling f with the last element, then the second-to-last element, etc.

assert foldr(["cow", "dog", "cat"], "turtle", |accum, a| accum ++ a) ==
  "turtlecatdogcow"
assert foldr([42.3, .428, 13.3], 5, |accum, a| -accum + a) == 50.172
assert foldr([], true, |accum, a| accum && a) == true
foldr_map : ([A], F, (F, A) -> (F, B)) -> (F, [B])
foldr_map(l, init, f)

Same as fold_map, expect that it goes through l in the reverse order, calling f with the last element, then the second-to-last element, etc. The resulting mapped list is still in the same order as the original list.

let l = ["cow", "dog", "cat"]
assert foldr_map(l, "turtle", |accum, a| (accum ++ "!" ++ a, a ++ "!")) ==
  ("turtle!cat!dog!cow", ["cow!", "dog!", "cat!"])

let l = [42, .4, 13.3]
assert foldr_map(l, 5, |accum, a| (-accum + a, accum)) ==
  (49.9, [-7.9, 8.3, 5])

assert foldr_map([], true, |accum, a| (accum && a, accum)) == (true, [])
sum : [A ~ Num] -> A ~ Num
sum(l)

Returns the sum of all numbers in l.

assert sum([5, 9, -37]) == -23
assert sum([42.3, .4, 13.3]) == 56.0
assert sum([]) == 0
sort : [A ~ Ord] -> [A ~ Ord]
sort(l)

Sorts l in ascending order. l is expected to contain elements that satisfy the Ord interface, meaning they have a defined ordering.

assert sort(["cow", "dog", "cat"]) == ["cat", "cow", "dog"]
assert sort([42.3, .428, 13.3]) == [.428, 13.3, 42.3]
assert sort([]) == []
sort_by : ([A], A -> B ~ Ord) -> [A]
sort_by(l)

Sorts l in ascending order based on the return value of f for each element. The element for which f returns the minimum value is first, and the element for which f returns the maximum value is last. f accepts an element as an argument and returns a value that satisfies the Ord interface, which has a defined ordering.

assert sort_by([42.3, .428, 13.3], |a| -a) == [42.3, 13.3, .428]

let f(a) = String.to_chars(a) |> at(2) |> to_int
assert sort_by(["cow", "dog", "cat"], f) == ["dog", "cat", "cow"]

assert sort_by([], |a| a) == []
sort_cmp : ([A], (A, A) -> Bool) -> [A]
sort_cmp(l, f)

Sorts l in ascending order based on the comparator function f. f is called with two elements a and b, and the comparator should return true if a must be placed before b in the sorted list.

assert sort_cmp([42.3, .428, 13.3], |a, b| a < b) ==
  [.428, 13.3, 42.3]

let key(s) = String.to_chars(s) |> at(2) |> to_int
let f(a, b) = key(a) > key(b)
assert sort_cmp(["cow", "dog", "cat"], f) == ["cow", "cat", "dog"]

assert sort_cmp([], |a, b| a < b) == []
join : ([String], String) -> String
join(l, sep)

Joins the list of strings l with the separator sep, returning a string. sep is placed in-between every two subsequent elements of l.

assert join(["cow", "dog", "cat"], "") == "cowdogcat"
assert join(["cow", "dog", "cat"], ", ") == "cow, dog, cat"
assert join([], ", ") == ""
max_in : [A ~ Ord] -> Option<A ~ Ord>
max_in(l)

Returns the maximum element in l as Some(max), or None if l is empty. l is expected to contain elements that satisfy the Ord interface, meaning they have a defined ordering.

assert max_in(["cow", "dog", "cat"]) == Some("dog")
assert max_in([42.3, .428, 13.3]) == Some(42.3)
assert max_in([]) == None
min_in : [A ~ Ord] -> Option<A ~ Ord>
min_in(l)

Returns the minimum element in l as Some(min), or None if l is empty. l is expected to contain elements that satisfy the Ord interface, meaning they have a defined ordering.

assert min_in(["cow", "dog", "cat"]) == Some("cat")
assert min_in([42.3, .428, 13.3]) == Some(.428)
assert min_in([]) == None
max_by : ([A], A -> B ~ Ord) -> Option<A>
max_by(l, f)

Returns the element in l for which f returns the maximum value. f accepts an element as an argument and returns a value that satisfies the Ord interface, which has a defined ordering.

assert max_by(["cow", "dog", "turtle"], |l| length(l)) == Some("turtle")
assert max_by([42.3, .428, 13.3], |l| -l) == Some(.428)
assert max_by([], |l| l) == None
min_by : ([A], A -> B ~ Ord) -> Option<A>
min_by(l, f)

Returns the element in l for which f returns the minimum value. f accepts an element as an argument and returns a value that satisfies the Ord interface, which has a defined ordering.

assert min_by(["cow", "dog", "turtle"], |l| length(l)) == Some("cow")
assert min_by([42.3, .428, 13.3], |l| -l) == Some(42.3)
assert min_by([], |l| l) == None
seq : (Int, Int) -> [Int]
seq(start, end)

Returns a list containing the sequence of integers from start to end, both inclusive. If start > end, returns [].

assert seq(0, 3) == [0, 1, 2, 3]
assert seq(-3, 1) == [-3, -2, -1, 0, 1]
assert seq(5, 5) == [5]
assert seq(2, 1) == []
repeat : (A, Int) -> [A]
repeat(a, n)

Creates a new list that has the element a repeated n times.

assert repeat("hi", 3) == ["hi", "hi", "hi"]
assert repeat('h', 1) == ['h']
assert repeat(true, 0) == []
zip : ([A], [B]) -> [(A, B)]
zip(l1, l2)

Returns a list where the i-th element is a tuple containing the i-th element of l1 and the i-th element of l2. If l1 and l2 don't have the same length, raises MismatchedLengths. See unzip() for the opposite operation.

assert zip(["cow", "dog", "cat"], [42.3, .428, 13.3]) ==
  [("cow", 42.3), ("dog", .428), ("cat", 13.3)]
assert zip([@hi], ['h']) == [(@hi, 'h')]
assert zip([], []) == []
unzip : [(A, B)] -> ([A], [B])
unzip(l)

Unpacks a list of tuples l into two separate lists. If the i-th element of l is a tuple (a, b), the i-th element of the first result list is a, and the i-th element of the second result list is b. See zip() for the opposite operation.

assert unzip([("cow", 42.3), ("dog", .428), ("cat", 13.3)]) ==
  (["cow", "dog", "cat"], [42.3, .428, 13.3])
assert unzip([(@hi, 'h')]) == ([@hi], ['h'])
assert unzip([]) == ([], [])
group_by : ([A], A -> B) -> Map<B, [A]>
group_by(l, f)

Calls f on each element of l, returning a map from each return value of f to the list of elements that gave that return value.

assert group_by(["turtle", "dog", "cat"], |a| length(a)) ==
  { 6 => ["turtle"], 3 => ["dog", "cat"] }
assert group_by([42.3, .428, 13.3], |a| a > 10) ==
  { true => [42.3, 13.3], false => [.428] }
assert group_by([42.3, .428, 13.3], |a| a) ==
  { 42.3 => [42.3], .428 => [.428], 13.3 => [13.3] }
assert group_by(
  ["cow", "dog", "cat"],
  |a| String.to_chars(a) |> head
) == { 'c' => ["cow", "cat"], 'd' => ["dog"] }
unique : [A] -> [A]
unique(l)

Removes duplicates in l, returning a new list. l is not modified.

assert unique(["cow", "dog", "cat"]) == ["cow", "dog", "cat"]
assert unique(["cow", "dog", "cow"]) == ["cow", "dog"]
assert unique([42.3, .428, 13.3, .428]) == [42.3, .428, 13.3]
assert unique([]) == []
unique_by : ([A], A -> B) -> [A]
unique_by(l, f)

Removes duplicates in l, returning a new list, where two elements are duplicates if f returns the same value for them. l is not modified.

assert unique_by([42.3, .428, 13.3], |a| a > 10) == [42.3, .428]
assert unique_by([42.3, .428, 13.3], |a| true) == [42.3]
assert unique_by(
  ["cow", "dog", "cat"],
  |a| String.to_chars(a) |> head
) == ["cow", "dog"]
assert unique_by([], |a| a) == []
with_index : [A] -> [(A, Int)]
with_index(l)

Pairs each element in l with its index, returning a new list of tuples.

assert with_index(["cow", "dog", "cat"]) ==
  [("cow", 0), ("dog", 1), ("cat", 2)]
assert with_index([42.3]) == [(42.3, 0)]
assert with_index([]) == []
each : ([A], A -> B) -> ()
each(l, f)

Calls f on each element of l. This function rarely needs to be used, and is only necessary when performing a side-effect (e.g. printing, writing to a file, etc.) for each element in a list. In most cases, what you really want is a specific operation like map(), filter(), or fold().

// prints each element
each([1, 2, 3], |e| print("element: " ++ e))

Implementations

impl Sized for [A]

The following functions are from the Sized interface.

length : [A] -> Int
length(sized)

Returns the length of sized. See the full description in the Sized interface.

empty? : [A] -> Bool
empty?(sized)

Returns whether sized is empty. See the full description in the Sized interface.

impl Concat for [A]

The following functions are from the Concat interface.

concat : ([A], [A]) -> [A]
concat(a, b)

Concatenates a and b together, equivalent to a ++ b. See the full description in the Concat interface.

concat_all : [[A]] -> [A]
concat_all(l)

Concatenates all elements in the list l together. See the full description in the Concat interface.

impl Mappable for List

The following functions are from the Mappable interface.

map : ([A], A -> B) -> [B]
map(container, f)

Calls f for each element in container, collecting the results into a new container. See the full description in the Mappable interface.

impl Collection for List

The following functions are from the Collection interface.

fold : ([A], F, (F, A) -> F) -> F
fold(collection, init, f)

Computes a single value by applying the combining function f to each element of the given collection, starting with the initial value init. See the full description in the Collection interface.

filter : ([A], A -> Bool) -> [A]
filter(collection, f)

Calls f for each element in collection, returning a new collection that only contains the elements for which f returns true. See the full description in the Collection interface.

find : ([A], A -> Bool) -> Option<A>
find(collection, f)

Returns the first element in collection for which f returns true. See the full description in the Collection interface.

filter_map : ([A], A -> Option<B>) -> [B]
filter_map(collection, f)

Calls f for each element in collection, returning a new collection that contains every e such that f returned Some(e). See the full description in the Collection interface.

fold_map : ([A], F, (F, A) -> (F, B)) -> (F, [B])
fold_map(collection, init, f)

Computes a new collection and a single value by applying the combining function f to each element of the given collection, starting with the initial value init. See the full description in the Collection interface.

put : ([A], A) -> [A]
put(collection, elem)

Puts elem into collection, returning a new collection. See the full description in the Collection interface.

delete : ([A], A) -> [A]
delete(collection, elem)

Removes elem from collection, returning a new collection. See the full description in the Collection interface.

contains? : ([A], A) -> Bool
contains?(collection, elem)

Returns true if collection contains elem. See the full description in the Collection interface.

all? : ([A], A -> Bool) -> Bool
all?(collection, f)

Calls f for each element in collection, and returns true if f returns true for every element. See the full description in the Collection interface.

any? : ([A], A -> Bool) -> Bool
any?(collection, f)

Calls f for each element in collection, and returns true if f returns true for at least one element. See the full description in the Collection interface.

impl ToSet for List

The following functions are from the ToSet interface.

to_set : [A] -> Set<A>
to_set(a)

Converts a to a set. See the full description in the ToSet interface.

impl ToMap for List

The following functions are from the ToMap interface.

to_map : [(K, V)] -> Map<K, V>
to_map(a)

Converts a to a map. See the full description in the ToMap interface.

Exceptions

exception BadListIndex(Int)

Raised by at(), split_at(), slice(), and range() when accessing an index out of bounds.

exception MismatchedLengths(Int, Int)

Raised by zip() when the two lists don't have the same lengths.