Merge pull request #3608 from rtfeldman/more-list-builtins

More `List` builtins
This commit is contained in:
Richard Feldman 2022-07-25 15:23:45 -04:00 committed by GitHub
commit 11ba64c249
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 469 additions and 57 deletions

View file

@ -81,7 +81,7 @@ withCapacity = \n -> @Dict (List.withCapacity n)
get : Dict k v, k -> Result v [KeyNotFound]*
get = \@Dict list, needle ->
when List.find list (\Pair key _ -> key == needle) is
when List.findFirst list (\Pair key _ -> key == needle) is
Ok (Pair _ v) ->
Ok v
@ -94,7 +94,7 @@ walk = \@Dict list, initialState, transform ->
insert : Dict k v, k, v -> Dict k v
insert = \@Dict list, k, v ->
when List.findIndex list (\Pair key _ -> key == k) is
when List.findFirstIndex list (\Pair key _ -> key == k) is
Err NotFound ->
insertFresh (@Dict list) k v
@ -109,7 +109,7 @@ len = \@Dict list ->
remove : Dict k v, k -> Dict k v
remove = \@Dict list, key ->
when List.findIndex list (\Pair k _ -> k == key) is
when List.findFirstIndex list (\Pair k _ -> k == key) is
Err NotFound ->
@Dict list

View file

@ -44,11 +44,17 @@ interface List
any,
takeFirst,
takeLast,
find,
findIndex,
findFirst,
findLast,
findFirstIndex,
findLastIndex,
sublist,
intersperse,
split,
splitFirst,
splitLast,
startsWith,
endsWith,
all,
dropIf,
sortAsc,
@ -770,30 +776,41 @@ maxHelp = \list, initial ->
## You may know a similar function named `concatMap` in other languages.
joinMap : List a, (a -> List b) -> List b
joinMap = \list, mapper ->
List.walk list [] (\state, elem -> List.concat state (mapper elem))
List.walk list [] \state, elem -> List.concat state (mapper elem)
## Returns the first element of the list satisfying a predicate function.
## If no satisfying element is found, an `Err NotFound` is returned.
find : List elem, (elem -> Bool) -> Result elem [NotFound]*
find = \array, pred ->
findFirst : List elem, (elem -> Bool) -> Result elem [NotFound]*
findFirst = \list, pred ->
callback = \_, elem ->
if pred elem then
Break elem
else
Continue {}
when List.iterate array {} callback is
Continue {} ->
Err NotFound
when List.iterate list {} callback is
Continue {} -> Err NotFound
Break found -> Ok found
Break found ->
Ok found
## Returns the last element of the list satisfying a predicate function.
## If no satisfying element is found, an `Err NotFound` is returned.
findLast : List elem, (elem -> Bool) -> Result elem [NotFound]*
findLast = \list, pred ->
callback = \_, elem ->
if pred elem then
Break elem
else
Continue {}
when List.iterateBackwards list {} callback is
Continue {} -> Err NotFound
Break found -> Ok found
## Returns the index at which the first element in the list
## satisfying a predicate function can be found.
## If no satisfying element is found, an `Err NotFound` is returned.
findIndex : List elem, (elem -> Bool) -> Result Nat [NotFound]*
findIndex = \list, matcher ->
findFirstIndex : List elem, (elem -> Bool) -> Result Nat [NotFound]*
findFirstIndex = \list, matcher ->
foundIndex = List.iterate list 0 \index, elem ->
if matcher elem then
Break index
@ -804,6 +821,21 @@ findIndex = \list, matcher ->
Break index -> Ok index
Continue _ -> Err NotFound
## Returns the last index at which the first element in the list
## satisfying a predicate function can be found.
## If no satisfying element is found, an `Err NotFound` is returned.
findLastIndex : List elem, (elem -> Bool) -> Result Nat [NotFound]*
findLastIndex = \list, matches ->
foundIndex = List.iterateBackwards list (List.len list) \prevIndex, elem ->
if matches elem then
Break (prevIndex - 1)
else
Continue (prevIndex - 1)
when foundIndex is
Break index -> Ok index
Continue _ -> Err NotFound
## Returns a subsection of the given list, beginning at the `start` index and
## including a total of `len` elements.
##
@ -843,6 +875,33 @@ intersperse = \list, sep ->
List.dropLast newList
## Returns `True` if the first list starts with the second list.
##
## If the second list is empty, this always returns `True`; every list
## is considered to "start with" an empty list.
##
## If the first list is empty, this only returns `True` if the second list is empty.
startsWith : List elem, List elem -> Bool
startsWith = \list, prefix ->
# TODO once we have seamless slices, verify that this wouldn't
# have better performance with a function like List.compareSublists
prefix == List.sublist list { start: 0, len: List.len prefix }
## Returns `True` if the first list ends with the second list.
##
## If the second list is empty, this always returns `True`; every list
## is considered to "end with" an empty list.
##
## If the first list is empty, this only returns `True` if the second list is empty.
endsWith : List elem, List elem -> Bool
endsWith = \list, suffix ->
# TODO once we have seamless slices, verify that this wouldn't
# have better performance with a function like List.compareSublists
length = List.len suffix
start = Num.subSaturated (List.len list) length
suffix == List.sublist list { start, len: length }
## Splits the list into two lists, around the given index.
##
## The returned lists are labeled `before` and `others`. The `before` list will
@ -859,6 +918,36 @@ split = \elements, userSplitIndex ->
{ before, others }
## Returns the elements before the first occurrence of a delimiter, as well as the
## remaining elements after that occurrence. If the delimiter is not found, returns `Err`.
##
## List.splitFirst [Foo, Z, Bar, Z, Baz] Z == Ok { before: [Foo], after: [Bar, Baz] }
splitFirst : List elem, elem -> Result { before : List elem, after : List elem } [NotFound]*
splitFirst = \list, delimiter ->
when List.findFirstIndex list (\elem -> elem == delimiter) is
Ok index ->
before = List.sublist list { start: 0, len: index }
after = List.sublist list { start: index + 1, len: List.len list - index - 1 }
Ok { before, after }
Err NotFound -> Err NotFound
## Returns the elements before the last occurrence of a delimiter, as well as the
## remaining elements after that occurrence. If the delimiter is not found, returns `Err`.
##
## List.splitLast [Foo, Z, Bar, Z, Baz] Z == Ok { before: [Foo, Bar], after: [Baz] }
splitLast : List elem, elem -> Result { before : List elem, after : List elem } [NotFound]*
splitLast = \list, delimiter ->
when List.findLastIndex list (\elem -> elem == delimiter) is
Ok index ->
before = List.sublist list { start: 0, len: index }
after = List.sublist list { start: index + 1, len: List.len list - index - 1 }
Ok { before, after }
Err NotFound -> Err NotFound
## Like [List.map], except the transformation function returns a [Result].
## If that function ever returns `Err`, [mapTry] immediately returns that `Err`.
## If it returns `Ok` for every element, [mapTry] returns `Ok` with the transformed list.
@ -895,11 +984,26 @@ iterHelp : List elem, s, (s, elem -> [Continue s, Break b]), Nat, Nat -> [Contin
iterHelp = \list, state, f, index, length ->
if index < length then
when f state (List.getUnsafe list index) is
Continue nextState ->
iterHelp list nextState f (index + 1) length
Continue nextState -> iterHelp list nextState f (index + 1) length
Break b -> Break b
else
Continue state
Break b ->
Break b
## Primitive for iterating over a List from back to front, being able to decide at every
## element whether to continue
iterateBackwards : List elem, s, (s, elem -> [Continue s, Break b]) -> [Continue s, Break b]
iterateBackwards = \list, init, func ->
iterBackwardsHelp list init func (List.len list)
## internal helper
iterBackwardsHelp : List elem, s, (s, elem -> [Continue s, Break b]), Nat -> [Continue s, Break b]
iterBackwardsHelp = \list, state, f, prevIndex ->
if prevIndex > 0 then
index = prevIndex - 1
when f state (List.getUnsafe list index) is
Continue nextState -> iterBackwardsHelp list nextState f index
Break b -> Break b
else
Continue state

View file

@ -1265,32 +1265,37 @@ define_builtins! {
41 LIST_ANY: "any"
42 LIST_TAKE_FIRST: "takeFirst"
43 LIST_TAKE_LAST: "takeLast"
44 LIST_FIND: "find"
45 LIST_FIND_RESULT: "#find_result" // symbol used in the definition of List.find
46 LIST_SUBLIST: "sublist"
47 LIST_INTERSPERSE: "intersperse"
48 LIST_INTERSPERSE_CLOS: "#intersperseClos"
49 LIST_SPLIT: "split"
50 LIST_SPLIT_CLOS: "#splitClos"
51 LIST_ALL: "all"
52 LIST_DROP_IF: "dropIf"
53 LIST_DROP_IF_PREDICATE: "#dropIfPred"
54 LIST_SORT_ASC: "sortAsc"
55 LIST_SORT_DESC: "sortDesc"
56 LIST_SORT_DESC_COMPARE: "#sortDescCompare"
57 LIST_REPLACE: "replace"
58 LIST_IS_UNIQUE: "#isUnique"
59 LIST_FIND_INDEX: "findIndex"
60 LIST_GET_UNSAFE: "getUnsafe"
61 LIST_REPLACE_UNSAFE: "replaceUnsafe"
62 LIST_WITH_CAPACITY: "withCapacity"
63 LIST_ITERATE: "iterate"
64 LIST_UNREACHABLE: "unreachable"
65 LIST_RESERVE: "reserve"
66 LIST_APPEND_UNSAFE: "appendUnsafe"
67 LIST_SUBLIST_LOWLEVEL: "sublistLowlevel"
68 LIST_CAPACITY: "capacity"
69 LIST_MAP_TRY: "mapTry"
44 LIST_FIND_FIRST: "findFirst"
45 LIST_FIND_LAST: "findLast"
46 LIST_FIND_FIRST_INDEX: "findFirstIndex"
47 LIST_FIND_LAST_INDEX: "findLastIndex"
48 LIST_FIND_RESULT: "#find_result" // symbol used in the definition of List.findFirst
49 LIST_SUBLIST: "sublist"
50 LIST_INTERSPERSE: "intersperse"
51 LIST_INTERSPERSE_CLOS: "#intersperseClos"
52 LIST_SPLIT: "split"
53 LIST_SPLIT_FIRST: "splitFirst"
54 LIST_SPLIT_LAST: "splitLast"
55 LIST_SPLIT_CLOS: "#splitClos"
56 LIST_ALL: "all"
57 LIST_DROP_IF: "dropIf"
58 LIST_DROP_IF_PREDICATE: "#dropIfPred"
59 LIST_SORT_ASC: "sortAsc"
60 LIST_SORT_DESC: "sortDesc"
61 LIST_SORT_DESC_COMPARE: "#sortDescCompare"
62 LIST_STARTS_WITH: "startsWith"
63 LIST_ENDS_WITH: "endsWith"
64 LIST_REPLACE: "replace"
65 LIST_IS_UNIQUE: "#isUnique"
66 LIST_GET_UNSAFE: "getUnsafe"
67 LIST_REPLACE_UNSAFE: "replaceUnsafe"
68 LIST_WITH_CAPACITY: "withCapacity"
69 LIST_UNREACHABLE: "unreachable"
70 LIST_RESERVE: "reserve"
71 LIST_APPEND_UNSAFE: "appendUnsafe"
72 LIST_SUBLIST_LOWLEVEL: "sublistLowlevel"
73 LIST_CAPACITY: "capacity"
74 LIST_MAP_TRY: "mapTry"
}
7 RESULT: "Result" => {
0 RESULT_RESULT: "Result" // the Result.Result type alias

View file

@ -360,6 +360,72 @@ fn list_split() {
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_split_first() {
assert_evals_to!(
r#"
List.splitFirst [2, 3, 0, 4, 0, 6, 0, 8, 9] 0
|> Result.map .before
"#,
RocResult::ok(RocList::<i64>::from_slice(&[2, 3])),
RocResult<RocList<i64>, ()>
);
assert_evals_to!(
r#"
List.splitFirst [2, 3, 0, 4, 0, 6, 0, 8, 9] 0
|> Result.map .after
"#,
RocResult::ok(RocList::<i64>::from_slice(&[4, 0, 6, 0, 8, 9])),
RocResult<RocList<i64>, ()>
);
assert_evals_to!(
"List.splitFirst [1, 2, 3] 0",
RocResult::err(()),
RocResult<(RocList<i64>, RocList<i64>), ()>
);
assert_evals_to!(
"List.splitFirst [] 1",
RocResult::err(()),
RocResult<(RocList<i64>, RocList<i64>), ()>
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_split_last() {
assert_evals_to!(
r#"
List.splitLast [2, 3, 0, 4, 0, 6, 0, 8, 9] 0
|> Result.map .before
"#,
RocResult::ok(RocList::<i64>::from_slice(&[2, 3, 0, 4, 0, 6])),
RocResult<RocList<i64>, ()>
);
assert_evals_to!(
r#"
List.splitLast [2, 3, 0, 4, 0, 6, 0, 8, 9] 0
|> Result.map .after
"#,
RocResult::ok(RocList::<i64>::from_slice(&[8, 9])),
RocResult<RocList<i64>, ()>
);
assert_evals_to!(
"List.splitLast [1, 2, 3] 0",
RocResult::err(()),
RocResult<(RocList<i64>, RocList<i64>), ()>
);
assert_evals_to!(
"List.splitLast [] 1",
RocResult::err(()),
RocResult<(RocList<i64>, RocList<i64>), ()>
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_drop() {
@ -2846,7 +2912,7 @@ fn list_find() {
assert_evals_to!(
indoc!(
r#"
when List.find ["a", "bc", "def"] (\s -> Str.countGraphemes s > 1) is
when List.findFirst ["a", "bc", "def", "g"] (\s -> Str.countGraphemes s > 1) is
Ok v -> v
Err _ -> "not found"
"#
@ -2854,6 +2920,18 @@ fn list_find() {
RocStr::from("bc"),
RocStr
);
assert_evals_to!(
indoc!(
r#"
when List.findLast ["a", "bc", "def", "g"] (\s -> Str.countGraphemes s > 1) is
Ok v -> v
Err _ -> "not found"
"#
),
RocStr::from("def"),
RocStr
);
}
#[test]
@ -2862,7 +2940,19 @@ fn list_find_not_found() {
assert_evals_to!(
indoc!(
r#"
when List.find ["a", "bc", "def"] (\s -> Str.countGraphemes s > 5) is
when List.findFirst ["a", "bc", "def", "g"] (\s -> Str.countGraphemes s > 5) is
Ok v -> v
Err _ -> "not found"
"#
),
RocStr::from("not found"),
RocStr
);
assert_evals_to!(
indoc!(
r#"
when List.findLast ["a", "bc", "def", "g"] (\s -> Str.countGraphemes s > 5) is
Ok v -> v
Err _ -> "not found"
"#
@ -2878,7 +2968,19 @@ fn list_find_empty_typed_list() {
assert_evals_to!(
indoc!(
r#"
when List.find [] (\s -> Str.countGraphemes s > 5) is
when List.findFirst [] (\s -> Str.countGraphemes s > 5) is
Ok v -> v
Err _ -> "not found"
"#
),
RocStr::from("not found"),
RocStr
);
assert_evals_to!(
indoc!(
r#"
when List.findLast [] (\s -> Str.countGraphemes s > 5) is
Ok v -> v
Err _ -> "not found"
"#
@ -2890,16 +2992,25 @@ fn list_find_empty_typed_list() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[ignore = "Fails because monomorphization can't be done if we don't have a concrete element type!"]
fn list_find_empty_layout() {
assert_evals_to!(
indoc!(
r#"
List.find [] (\_ -> True)
List.findFirst [] \_ -> True
"#
),
0,
i64
RocResult::err(()),
RocResult<(), ()>
);
assert_evals_to!(
indoc!(
r#"
List.findLast [] \_ -> True
"#
),
RocResult::err(()),
RocResult<(), ()>
);
}
@ -2909,7 +3020,7 @@ fn list_find_index() {
assert_evals_to!(
indoc!(
r#"
when List.findIndex ["a", "bc", "def"] (\s -> Str.countGraphemes s > 1) is
when List.findFirstIndex ["a", "bc", "def", "g"] (\s -> Str.countGraphemes s > 1) is
Ok v -> v
Err _ -> 999
"#
@ -2917,6 +3028,18 @@ fn list_find_index() {
1,
usize
);
assert_evals_to!(
indoc!(
r#"
when List.findLastIndex ["a", "bc", "def", "g"] (\s -> Str.countGraphemes s > 1) is
Ok v -> v
Err _ -> 999
"#
),
2,
usize
);
}
#[test]
@ -2925,7 +3048,19 @@ fn list_find_index_not_found() {
assert_evals_to!(
indoc!(
r#"
when List.findIndex ["a", "bc", "def"] (\s -> Str.countGraphemes s > 5) is
when List.findFirstIndex ["a", "bc", "def", "g"] (\s -> Str.countGraphemes s > 5) is
Ok v -> v
Err _ -> 999
"#
),
999,
usize
);
assert_evals_to!(
indoc!(
r#"
when List.findLastIndex ["a", "bc", "def"] (\s -> Str.countGraphemes s > 5) is
Ok v -> v
Err _ -> 999
"#
@ -2941,7 +3076,7 @@ fn list_find_index_empty_typed_list() {
assert_evals_to!(
indoc!(
r#"
when List.findIndex [] (\s -> Str.countGraphemes s > 5) is
when List.findFirstIndex [] (\s -> Str.countGraphemes s > 5) is
Ok v -> v
Err _ -> 999
"#
@ -2949,6 +3084,174 @@ fn list_find_index_empty_typed_list() {
999,
usize
);
assert_evals_to!(
indoc!(
r#"
when List.findLastIndex [] (\s -> Str.countGraphemes s > 5) is
Ok v -> v
Err _ -> 999
"#
),
999,
usize
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_ends_with_empty() {
assert_evals_to!(
indoc!(
r#"
List.endsWith [] []
"#
),
true,
bool
);
assert_evals_to!(
indoc!(
r#"
List.endsWith ["a"] []
"#
),
true,
bool
);
assert_evals_to!(
indoc!(
r#"
List.endsWith [] ["a"]
"#
),
false,
bool
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_ends_with_nonempty() {
assert_evals_to!(
indoc!(
r#"
List.endsWith ["a", "bc", "def"] ["def"]
"#
),
true,
bool
);
assert_evals_to!(
indoc!(
r#"
List.endsWith ["a", "bc", "def"] ["bc", "def"]
"#
),
true,
bool
);
assert_evals_to!(
indoc!(
r#"
List.endsWith ["a", "bc", "def"] ["a"]
"#
),
false,
bool
);
assert_evals_to!(
indoc!(
r#"
List.endsWith ["a", "bc", "def"] [""]
"#
),
false,
bool
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_starts_with_empty() {
assert_evals_to!(
indoc!(
r#"
List.startsWith [] []
"#
),
true,
bool
);
assert_evals_to!(
indoc!(
r#"
List.startsWith ["a"] []
"#
),
true,
bool
);
assert_evals_to!(
indoc!(
r#"
List.startsWith [] ["a"]
"#
),
false,
bool
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_starts_with_nonempty() {
assert_evals_to!(
indoc!(
r#"
List.startsWith ["a", "bc", "def"] ["a"]
"#
),
true,
bool
);
assert_evals_to!(
indoc!(
r#"
List.startsWith ["a", "bc", "def"] ["a", "bc"]
"#
),
true,
bool
);
assert_evals_to!(
indoc!(
r#"
List.startsWith ["a", "bc", "def"] ["def"]
"#
),
false,
bool
);
assert_evals_to!(
indoc!(
r#"
List.startsWith ["a", "bc", "def"] [""]
"#
),
false,
bool
);
}
#[test]

View file

@ -1613,7 +1613,7 @@ fn format_category<'b>(
alloc.text(" which was of type:"),
),
Character => (
alloc.concat([this_is, alloc.text(" a character")]),
alloc.concat([this_is, alloc.text(" a Unicode scalar value")]),
alloc.text(" of type:"),
),
Lambda => (

View file

@ -436,8 +436,8 @@ mod test_reporting {
List.isEmpty
List.set
List.iterate
List.get
List.keepIf
"###
);