From fbb08bf28683b05fb77a57af6a787907da070ef7 Mon Sep 17 00:00:00 2001 From: Spotandjake <40705786+spotandjake@users.noreply.github.com> Date: Thu, 2 Jan 2025 19:50:15 -0500 Subject: [PATCH] feat(stdlib): `List.Associative` Submodule (#2202) Co-authored-by: Oscar Spencer --- compiler/test/stdlib/list.test.gr | 28 +++++ stdlib/list.gr | 126 ++++++++++++++++++++ stdlib/list.md | 187 ++++++++++++++++++++++++++++++ 3 files changed, 341 insertions(+) diff --git a/compiler/test/stdlib/list.test.gr b/compiler/test/stdlib/list.test.gr index 69b35a22e..ead785712 100644 --- a/compiler/test/stdlib/list.test.gr +++ b/compiler/test/stdlib/list.test.gr @@ -289,3 +289,31 @@ assert sort(compare=compareLengths, list) == ["a", "a", "ab", "abc", "abcd", "abcde"] assert sort(compare=compareLengths, ["a", "a", "a", "a"]) == ["a", "a", "a", "a"] + +// List.Associative +module Associative { + let data = [("name", "Alice"), ("name", "Bob"), ("age", "30")] + + // List.Associative.has + assert List.Associative.has("name", data) + assert !List.Associative.has("age", []) + + // List.Associative.get + assert List.Associative.get("name", data) == Some("Alice") + assert List.Associative.get("age", []) == None + assert List.Associative.get("age", data) == Some("30") + + // List.Associative.set + assert List.Associative.set("name", "Charlie", data) == + [("name", "Charlie"), ("name", "Bob"), ("age", "30")] + assert List.Associative.set("age", "31", data) == + [("name", "Alice"), ("name", "Bob"), ("age", "31")] + assert List.Associative.set("age", "31", []) == [("age", "31")] + + // List.Associative.remove + assert List.Associative.remove("name", data) == + [("name", "Bob"), ("age", "30")] + assert List.Associative.remove("age", data) == + [("name", "Alice"), ("name", "Bob")] + assert List.Associative.remove("age", []) == [] +} diff --git a/stdlib/list.gr b/stdlib/list.gr index f469e08a2..177943bdf 100644 --- a/stdlib/list.gr +++ b/stdlib/list.gr @@ -1047,3 +1047,129 @@ provide let sort = (compare=compare, list) => { mergesort(list) } + +/** + * Utilities for working with lists of key-key value pairs. + * + * @example + * let data = [ + * ("name", "Alice"), + * ("age", "30"), + * ] + * assert List.Associative.get("name", data) == Some("Alice") + * + * @since v0.7.0 + */ +provide module Associative { + /** + * Checks if the given key is present in the list of key-value pairs. + * + * @param key: The key to search for + * @param list: The list of key-value pairs + * + * @returns `true` if the key is found or `false` otherwise + * + * @example + * let data = [ + * ("name", "Alice"), + * ("age", "30"), + * ] + * assert List.Associative.has("name", data) == true + * @example List.Associative.has("age", []) == false + * + * @since v0.7.0 + */ + provide let rec has = (key, list) => { + match (list) { + [] => false, + [(k, _), ...rest] when key == k => true, + [_, ...rest] => has(key, rest), + } + } + /** + * Retrieves the first value in the list of key-value pairs that matches the given key. + * + * @param key: The key to search for + * @param list: The list of key-value pairs + * + * @returns `Some(value)` if the key is found or `None` otherwise + * + * @example + * let data = [ + * ("name", "Alice"), + * ("name", "Bob"), + * ("age", "30"), + * ] + * assert List.Associative.get("name", data) == Some("Alice") + * + * @example List.Associative.get("age", []) == None + * + * @since v0.7.0 + */ + provide let rec get = (key, list) => { + match (list) { + [] => None, + [(k, v), ...rest] when key == k => Some(v), + [_, ...rest] => get(key, rest), + } + } + + /** + * Creates a new list with the first value in the list of key-value pairs that matches the key replaced. + * If the key is not found the item is appended to the list. + * + * @param key: The key to replace + * @param value: The new value to set + * @param list: The list of key-value pairs + * + * @returns A new list with the key-value pair replaced + * + * @example + * let data = [ + * ("name", "Alice"), + * ("name", "Bob"), + * ("age", "30"), + * ] + * assert List.Associative.set("name", "Charlie", data) == [("name", "Charlie"), ("name", "Bob"), ("age", "30")] + * + * @example List.Associative.set("age", "30", [("name", "Alice")]) == [("name", "Alice"), ("age", "30")] + * + * @since v0.7.0 + */ + provide let rec set = (key, value, list) => { + match (list) { + [] => [(key, value)], + [(k, _), ...rest] when key == k => [(k, value), ...rest], + [first, ...rest] => [first, ...set(key, value, rest)], + } + } + + /** + * Creates a new list with the first value in the list of key-value pairs that matches the key removed. + * If the key is not found, the list is returned unchanged. + * + * @param key: The key to remove + * @param list: The list of key-value pairs + * + * @returns The new list with the key-value pair removed + * + * @example + * let data = [ + * ("name", "Alice"), + * ("name", "Bob"), + * ("age", "30"), + * ] + * assert List.Associative.remove("name", data) == [("name", "Bob"), ("age", "30")] + * + * @example List.Associative.remove("age", [("name", "Alice")]) == [] + * + * @since v0.7.0 + */ + provide let rec remove = (key, list) => { + match (list) { + [] => [], + [(k, v), ...rest] when key == k => rest, + [first, ...rest] => [first, ...remove(key, rest)], + } + } +} diff --git a/stdlib/list.md b/stdlib/list.md index e5a48b070..e483de60b 100644 --- a/stdlib/list.md +++ b/stdlib/list.md @@ -1430,3 +1430,190 @@ Returns: |----|-----------| |`List`|The sorted list| +## List.Associative + +Utilities for working with lists of key-key value pairs. + +
+Added in next +No other changes yet. +
+ +```grain +let data = [ + ("name", "Alice"), + ("age", "30"), +] +assert List.Associative.get("name", data) == Some("Alice") +``` + +### Values + +Functions and constants included in the List.Associative module. + +#### List.Associative.**has** + +
+Added in next +No other changes yet. +
+ +```grain +has : (key: a, list: List<(a, b)>) => Bool +``` + +Checks if the given key is present in the list of key-value pairs. + +Parameters: + +|param|type|description| +|-----|----|-----------| +|`key`|`a`|The key to search for| +|`list`|`List<(a, b)>`|The list of key-value pairs| + +Returns: + +|type|description| +|----|-----------| +|`Bool`|`true` if the key is found or `false` otherwise| + +Examples: + +```grain +let data = [ + ("name", "Alice"), + ("age", "30"), +] +assert List.Associative.has("name", data) == true +``` + +```grain +List.Associative.has("age", []) == false +``` + +#### List.Associative.**get** + +
+Added in next +No other changes yet. +
+ +```grain +get : (key: a, list: List<(a, b)>) => Option +``` + +Retrieves the first value in the list of key-value pairs that matches the given key. + +Parameters: + +|param|type|description| +|-----|----|-----------| +|`key`|`a`|The key to search for| +|`list`|`List<(a, b)>`|The list of key-value pairs| + +Returns: + +|type|description| +|----|-----------| +|`Option`|`Some(value)` if the key is found or `None` otherwise| + +Examples: + +```grain +let data = [ + ("name", "Alice"), + ("name", "Bob"), + ("age", "30"), +] +assert List.Associative.get("name", data) == Some("Alice") +``` + +```grain +List.Associative.get("age", []) == None +``` + +#### List.Associative.**set** + +
+Added in next +No other changes yet. +
+ +```grain +set : (key: a, value: b, list: List<(a, b)>) => List<(a, b)> +``` + +Creates a new list with the first value in the list of key-value pairs that matches the key replaced. +If the key is not found the item is appended to the list. + +Parameters: + +|param|type|description| +|-----|----|-----------| +|`key`|`a`|The key to replace| +|`value`|`b`|The new value to set| +|`list`|`List<(a, b)>`|The list of key-value pairs| + +Returns: + +|type|description| +|----|-----------| +|`List<(a, b)>`|A new list with the key-value pair replaced| + +Examples: + +```grain +let data = [ + ("name", "Alice"), + ("name", "Bob"), + ("age", "30"), +] +assert List.Associative.set("name", "Charlie", data) == [("name", "Charlie"), ("name", "Bob"), ("age", "30")] +``` + +```grain +List.Associative.set("age", "30", [("name", "Alice")]) == [("name", "Alice"), ("age", "30")] +``` + +#### List.Associative.**remove** + +
+Added in next +No other changes yet. +
+ +```grain +remove : (key: a, list: List<(a, b)>) => List<(a, b)> +``` + +Creates a new list with the first value in the list of key-value pairs that matches the key removed. +If the key is not found, the list is returned unchanged. + +Parameters: + +|param|type|description| +|-----|----|-----------| +|`key`|`a`|The key to remove| +|`list`|`List<(a, b)>`|The list of key-value pairs| + +Returns: + +|type|description| +|----|-----------| +|`List<(a, b)>`|The new list with the key-value pair removed| + +Examples: + +```grain +let data = [ + ("name", "Alice"), + ("name", "Bob"), + ("age", "30"), +] +assert List.Associative.remove("name", data) == [("name", "Bob"), ("age", "30")] +``` + +```grain +List.Associative.remove("age", [("name", "Alice")]) == [] +``` +