Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(stdlib): Add List.filterMap, List.filterMapi, List.findMap #2201

Merged
merged 4 commits into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions compiler/test/stdlib/list.test.gr
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,19 @@ assert filteri((x, i) => i + x > 2, list) == [2, 3]
assert filteri((x, i) => x == 3, list) == [3]
assert filteri((x, i) => x == 3, []) == []

// List.filterMap

assert List.filterMap(x => Some(x), list) == list
assert List.filterMap(x => if (x == 3) Some(4) else None, list) == [4]
assert List.filterMap(x => None, list) == []

// List.filterMapi
assert List.filterMapi((x, i) => Some(x), list) == list
assert List.filterMapi((x, i) => if (x == 3) Some(4) else None, list) == [4]
assert List.filterMapi((x, i) => None, list) == []
assert List.filterMapi((x, i) => if (i != 0) Some(i) else None, list) == [1, 2]
assert List.filterMapi((x, i) => if (i != 0) Some(x) else None, list) == [2, 3]

// List.reject

assert reject(x => x > 0, list) == []
Expand Down Expand Up @@ -207,6 +220,13 @@ assert findIndex(x => x == 1, list) == Some(0)
assert findIndex(x => x == 2, list) == Some(1)
assert findIndex(x => false, list) == None

// List.findMap
let duplicateList = [(1, 'a'), (2, 'b'), (1, 'c')]
assert List.findMap(((k, v)) => if (k == 1) Some(v) else None, duplicateList) ==
Some('a')
assert List.findMap(x => if (x == 2) Some(x) else None, list) == Some(2)
assert List.findMap(x => None, list) == None

// List.product

let listA = [1, 2]
Expand Down
83 changes: 79 additions & 4 deletions stdlib/list.gr
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,56 @@ provide let mapi = (fn, list) => {
iter(fn, list, 0)
}

/**
* Produces a new list initialized with the results of a mapper function
* called on each element of the input list and its index.
spotandjake marked this conversation as resolved.
Show resolved Hide resolved
* The mapper function can return `None` to exclude the element from the new list.
*
* @param fn: The mapper function to call on each element, where the value returned will be used to initialize the element in the new list
* @param list: The list to iterate
* @returns The new list with filtered mapped values
*
* @example List.filterMap(x => if (x % 2 == 0) Some(toString(x)) else None, [1, 2, 3, 4]) == ["2", "4"]
*
* @since v0.7.0
*/
provide let rec filterMap = (fn, list) => {
match (list) {
[] => [],
[first, ...rest] => match (fn(first)) {
Some(v) => [v, ...filterMap(fn, rest)],
None => filterMap(fn, rest),
},
}
}

/**
* Produces a new list initialized with the results of a mapper function
* called on each element of the input list and its index.
* The mapper function can return `None` to exclude the element from the new list.
*
* @param fn: The mapper function to call on each element, where the value returned will be used to initialize the element in the new list
* @param list: The list to iterate
* @returns The new list with filtered mapped values
*
* @example List.filterMapi((x, i) => if (x % 2 == 0) Some(toString(x)) else None, [1, 2, 3, 4]) == ["2", "4"]
* @example List.filterMapi((x, i) => if (i == 0) Some(toString(x)) else None, [1, 2, 3, 4]) == ["1"]
*
* @since v0.7.0
*/
provide let filterMapi = (fn, list) => {
let rec iter = (fn, list, index) => {
match (list) {
[] => [],
[first, ...rest] => match (fn(first, index)) {
Some(v) => [v, ...iter(fn, rest, index + 1)],
None => iter(fn, rest, index + 1),
},
}
}
iter(fn, list, 0)
}

/**
* Produces a new list by calling a function on each element
* of the input list. Each iteration produces an intermediate
Expand All @@ -242,7 +292,7 @@ provide let rec flatMap = (fn, list) => {
*
* @param fn: The function to call on each element, where the returned value indicates if the element satisfies the condition
* @param list: The list to check
* @returns `true` if all elements satify the condition or `false` otherwise
* @returns `true` if all elements satisfy the condition or `false` otherwise
*
* @since v0.1.0
*/
Expand All @@ -260,7 +310,7 @@ provide let rec every = (fn, list) => {
*
* @param fn: The function to call on each element, where the returned value indicates if the element satisfies the condition
* @param list: The list to iterate
* @returns `true` if one or more elements satify the condition or `false` otherwise
* @returns `true` if one or more elements satisfy the condition or `false` otherwise
*
* @since v0.1.0
*/
Expand Down Expand Up @@ -769,7 +819,7 @@ provide let rec takeWhile = (fn, list) => {
}

/**
* Finds the first element in a list that satifies the given condition.
* Finds the first element in a list that satisfies the given condition.
*
* @param fn: The function to call on each element, where the returned value indicates if the element satisfies the condition
* @param list: The list to search
Expand All @@ -787,7 +837,7 @@ provide let rec find = (fn, list) => {
}

/**
* Finds the first index in a list where the element satifies the given condition.
* Finds the first index in a list where the element satisfies the given condition.
*
* @param fn: The function to call on each element, where the returned value indicates if the element satisfies the condition
* @param list: The list to search
Expand All @@ -808,6 +858,31 @@ provide let findIndex = (fn, list) => {
findItemIndex(list, 0)
}

/**
* Finds the first element in a list that satisfies the given condition and
* returns the result of applying a mapper function to it.
*
* @param fn: The function to call on each element, where the returned value indicates if the element satisfies the condition
* @param list: The list to search
* @returns `Some(mapped)` containing the first value found with the given mapping or `None` otherwise
*
* @example
* let jsonObject = [(1, 'a'), (2, 'b'), (1, 'c')]
* let getItem = (key, obj) => List.findMap(((k, v)) => if (k == key) Some(v) else None, obj)
* assert getItem(1, jsonObject) == Some('a')
*
* @since v0.7.0
*/
provide let rec findMap = (fn, list) => {
match (list) {
[] => None,
[first, ...rest] => match (fn(first)) {
None => findMap(fn, rest),
Some(v) => Some(v),
},
}
}

/**
* Combines two lists into a Cartesian product of tuples containing
* all ordered pairs `(a, b)`.
Expand Down
115 changes: 111 additions & 4 deletions stdlib/list.md
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,78 @@ Returns:
|----|-----------|
|`List<b>`|The new list with mapped values|

### List.**filterMap**

<details disabled>
<summary tabindex="-1">Added in <code>next</code></summary>
No other changes yet.
</details>

```grain
filterMap : (fn: (a => Option<b>), list: List<a>) => List<b>
```

Produces a new list initialized with the results of a mapper function
called on each element of the input list and its index.
The mapper function can return `None` to exclude the element from the new list.

Parameters:

|param|type|description|
|-----|----|-----------|
|`fn`|`a => Option<b>`|The mapper function to call on each element, where the value returned will be used to initialize the element in the new list|
|`list`|`List<a>`|The list to iterate|

Returns:

|type|description|
|----|-----------|
|`List<b>`|The new list with filtered mapped values|

Examples:

```grain
List.filterMap(x => if (x % 2 == 0) Some(toString(x)) else None, [1, 2, 3, 4]) == ["2", "4"]
```

### List.**filterMapi**

<details disabled>
<summary tabindex="-1">Added in <code>next</code></summary>
No other changes yet.
</details>

```grain
filterMapi : (fn: ((a, Number) => Option<b>), list: List<a>) => List<b>
```

Produces a new list initialized with the results of a mapper function
called on each element of the input list and its index.
The mapper function can return `None` to exclude the element from the new list.

Parameters:

|param|type|description|
|-----|----|-----------|
|`fn`|`(a, Number) => Option<b>`|The mapper function to call on each element, where the value returned will be used to initialize the element in the new list|
|`list`|`List<a>`|The list to iterate|

Returns:

|type|description|
|----|-----------|
|`List<b>`|The new list with filtered mapped values|

Examples:

```grain
List.filterMapi((x, i) => if (x % 2 == 0) Some(toString(x)) else None, [1, 2, 3, 4]) == ["2", "4"]
```

```grain
List.filterMapi((x, i) => if (i == 0) Some(toString(x)) else None, [1, 2, 3, 4]) == ["1"]
```

### List.**flatMap**

<details disabled>
Expand Down Expand Up @@ -397,7 +469,7 @@ Returns:

|type|description|
|----|-----------|
|`Bool`|`true` if all elements satify the condition or `false` otherwise|
|`Bool`|`true` if all elements satisfy the condition or `false` otherwise|

### List.**some**

Expand All @@ -424,7 +496,7 @@ Returns:

|type|description|
|----|-----------|
|`Bool`|`true` if one or more elements satify the condition or `false` otherwise|
|`Bool`|`true` if one or more elements satisfy the condition or `false` otherwise|

### List.**forEach**

Expand Down Expand Up @@ -1124,7 +1196,7 @@ Returns:
find : (fn: (a => Bool), list: List<a>) => Option<a>
```

Finds the first element in a list that satifies the given condition.
Finds the first element in a list that satisfies the given condition.

Parameters:

Expand Down Expand Up @@ -1158,7 +1230,7 @@ Returns:
findIndex : (fn: (a => Bool), list: List<a>) => Option<Number>
```

Finds the first index in a list where the element satifies the given condition.
Finds the first index in a list where the element satisfies the given condition.

Parameters:

Expand All @@ -1173,6 +1245,41 @@ Returns:
|----|-----------|
|`Option<Number>`|`Some(index)` containing the index of the first element found or `None` otherwise|

### List.**findMap**

<details disabled>
<summary tabindex="-1">Added in <code>next</code></summary>
No other changes yet.
</details>

```grain
findMap : (fn: (a => Option<b>), list: List<a>) => Option<b>
```

Finds the first element in a list that satisfies the given condition and
returns the result of applying a mapper function to it.

Parameters:

|param|type|description|
|-----|----|-----------|
|`fn`|`a => Option<b>`|The function to call on each element, where the returned value indicates if the element satisfies the condition|
|`list`|`List<a>`|The list to search|

Returns:

|type|description|
|----|-----------|
|`Option<b>`|`Some(mapped)` containing the first value found with the given mapping or `None` otherwise|

Examples:

```grain
let jsonObject = [(1, 'a'), (2, 'b'), (1, 'c')]
let getItem = (key, obj) => List.findMap(((k, v)) => if (k == key) Some(v) else None, obj)
assert getItem(1, jsonObject) == Some('a')
```

### List.**product**

<details disabled>
Expand Down
Loading