diff --git a/README.md b/README.md
index b97a6bc..f548207 100644
--- a/README.md
+++ b/README.md
@@ -91,7 +91,7 @@ await txBuilder
Simply run
```sh
-aiken add sidan-lab/vodka --version 0.0.1-beta
+aiken add sidan-lab/vodka --version 0.1.0-beta
```
or putting the below in you `aiken.toml`
@@ -99,7 +99,7 @@ or putting the below in you `aiken.toml`
```toml
[[dependencies]]
name = "sidan-lab/vodka"
-version = "0.0.1-beta"
+version = "0.1.0-beta"
source = "github"
```
diff --git a/aiken.lock b/aiken.lock
index 62f2265..a7aee96 100644
--- a/aiken.lock
+++ b/aiken.lock
@@ -3,13 +3,14 @@
[[requirements]]
name = "aiken-lang/stdlib"
-version = "1.9.0"
+version = "v2"
source = "github"
[[packages]]
name = "aiken-lang/stdlib"
-version = "1.9.0"
+version = "v2"
requirements = []
source = "github"
[etags]
+"aiken-lang/stdlib@v2" = [{ secs_since_epoch = 1725465191, nanos_since_epoch = 577001000 }, "d79382d2b6ecb3aee9b0755c31d8a5bbafe88a7b3706d7fb8a52fd4d05818501"]
diff --git a/aiken.toml b/aiken.toml
index 560a7c2..98a881c 100644
--- a/aiken.toml
+++ b/aiken.toml
@@ -1,5 +1,7 @@
name = "sidan-lab/vodka"
-version = "0.0.1-beta"
+version = "0.1.0-beta"
+compiler = "v1.1.0"
+plutus = "v3"
license = "Apache-2.0"
description = "Aiken utils for project 'sidan-lab/vodka"
@@ -10,5 +12,7 @@ platform = "github"
[[dependencies]]
name = "aiken-lang/stdlib"
-version = "1.9.0"
+version = "v2"
source = "github"
+
+[config]
diff --git a/build/packages/aiken-lang-stdlib/.github/workflows/continuous-integration.yml b/build/packages/aiken-lang-stdlib/.github/workflows/continuous-integration.yml
index 29d6f49..f4e567e 100644
--- a/build/packages/aiken-lang-stdlib/.github/workflows/continuous-integration.yml
+++ b/build/packages/aiken-lang-stdlib/.github/workflows/continuous-integration.yml
@@ -31,9 +31,7 @@ jobs:
uses: actions/configure-pages@v2
- name: 🧰 Install Aiken
- uses: aiken-lang/setup-aiken@v1
- with:
- version: v1.0.28-alpha
+ run: cargo install --verbose --git https://github.com/aiken-lang/aiken.git
- name: 📝 Run fmt
run: aiken fmt --check
diff --git a/build/packages/aiken-lang-stdlib/CHANGELOG.md b/build/packages/aiken-lang-stdlib/CHANGELOG.md
index 2bec225..ff08aa7 100644
--- a/build/packages/aiken-lang-stdlib/CHANGELOG.md
+++ b/build/packages/aiken-lang-stdlib/CHANGELOG.md
@@ -1,5 +1,587 @@
# Changelog
+## v2.0.0 - UNRELEASED
+
+> [!NOTE]
+> Significant performance improvements (mostly on CPU) across all boards mostly due to the integration of Plutus V3.
+>
+> see benchmarks
+>
+> test | cpu | mem
+> --- | --- | ---
+> aiken/cbor.{serialise_1} | -38.20% | ±0.00%
+> aiken/cbor.{serialise_2} | -38.20% | ±0.00%
+> aiken/cbor.{serialise_3} | -37.25% | ±0.00%
+> aiken/cbor.{serialise_4} | -41.95% | ±0.00%
+> aiken/cbor.{serialise_5} | -42.77% | ±0.00%
+> aiken/cbor.{serialise_6} | -42.63% | ±0.00%
+> aiken/cbor.{serialise_7} | -40.51% | ±0.00%
+> aiken/cbor.{serialise_8} | -37.25% | ±0.00%
+> aiken/cbor.{serialise_9} | -41.95% | ±0.00%
+> aiken/cbor.{diagnostic_1} | -47.62% | -4.35%
+> aiken/cbor.{diagnostic_2} | -45.16% | -2.87%
+> aiken/cbor.{diagnostic_3} | -43.32% | -13.33%
+> aiken/cbor.{diagnostic_4} | -38.28% | -8.03%
+> aiken/cbor.{diagnostic_5} | -44.15% | -14.59%
+> aiken/cbor.{diagnostic_6} | -42.77% | -12.21%
+> aiken/cbor.{diagnostic_7} | -43.87% | -16.87%
+> aiken/cbor.{diagnostic_7_alt} | -42.99% | -11.56%
+> aiken/cbor.{diagnostic_8} | -46.00% | -10.23%
+> aiken/cbor.{diagnostic_9} | -42.81% | -2.81%
+> aiken/cbor.{diagnostic_10} | -38.28% | -8.03%
+> aiken/cbor.{diagnostic_10_alt} | -38.43% | -8.03%
+> aiken/cbor.{diagnostic_11} | -44.00% | -8.51%
+> aiken/cbor.{diagnostic_12} | -45.65% | -11.56%
+> aiken/cbor.{diagnostic_13} | -44.44% | -9.34%
+> aiken/cbor.{diagnostic_14} | -43.59% | -19.77%
+> aiken/cbor.{diagnostic_15} | -46.50% | -3.67%
+> aiken/cbor.{diagnostic_16} | -41.89% | -13.41%
+> aiken/collection/dict.{bench_from_ascending_pairs} | -20.48% | ±0.00%
+> aiken/collection/dict.{from_list_1} | -20.16% | ±0.00%
+> aiken/collection/dict.{from_list_2} | -18.28% | ±0.00%
+> aiken/collection/dict.{from_list_3} | -17.83% | ±0.00%
+> aiken/collection/dict.{from_list_4} | -18.97% | ±0.00%
+> aiken/collection/dict.{bench_from_pairs} | -25.28% | ±0.00%
+> aiken/collection/dict.{find_1} | -20.63% | ±0.00%
+> aiken/collection/dict.{find_2} | -20.43% | ±0.00%
+> aiken/collection/dict.{find_3} | -22.03% | ±0.00%
+> aiken/collection/dict.{find_4} | -22.53% | ±0.00%
+> aiken/collection/dict.{get_1} | -20.63% | ±0.00%
+> aiken/collection/dict.{get_2} | -22.72% | ±0.00%
+> aiken/collection/dict.{get_3} | -23.26% | ±0.00%
+> aiken/collection/dict.{get_4} | -26.91% | ±0.00%
+> aiken/collection/dict.{get_5} | -26.30% | ±0.00%
+> aiken/collection/dict.{has_key_1} | -28.07% | ±0.00%
+> aiken/collection/dict.{has_key_2} | -30.77% | ±0.00%
+> aiken/collection/dict.{has_key_3} | -30.22% | ±0.00%
+> aiken/collection/dict.{has_key_4} | -27.25% | ±0.00%
+> aiken/collection/dict.{is_empty_1} | -27.86% | ±0.00%
+> aiken/collection/dict.{keys_1} | -20.30% | ±0.00%
+> aiken/collection/dict.{keys_2} | -17.48% | ±0.00%
+> aiken/collection/dict.{size_1} | -37.90% | ±0.00%
+> aiken/collection/dict.{size_2} | -32.34% | ±0.00%
+> aiken/collection/dict.{size_3} | -27.97% | ±0.00%
+> aiken/collection/dict.{values_1} | -20.30% | ±0.00%
+> aiken/collection/dict.{values_2} | -17.58% | ±0.00%
+> aiken/collection/dict.{delete_1} | -20.16% | ±0.00%
+> aiken/collection/dict.{delete_2} | -24.29% | ±0.00%
+> aiken/collection/dict.{delete_3} | -21.03% | ±0.00%
+> aiken/collection/dict.{delete_4} | -25.03% | ±0.00%
+> aiken/collection/dict.{delete_5} | -27.22% | ±0.00%
+> aiken/collection/dict.{delete_6} | -25.83% | ±0.00%
+> aiken/collection/dict.{filter_1} | -20.16% | ±0.00%
+> aiken/collection/dict.{filter_2} | -19.61% | ±0.00%
+> aiken/collection/dict.{filter_3} | -20.15% | ±0.00%
+> aiken/collection/dict.{insert_1} | -22.83% | ±0.00%
+> aiken/collection/dict.{insert_2} | -21.77% | ±0.00%
+> aiken/collection/dict.{insert_with_1} | -17.21% | ±0.00%
+> aiken/collection/dict.{insert_with_2} | -22.66% | ±0.00%
+> aiken/collection/dict.{insert_with_3} | -25.81% | ±0.00%
+> aiken/collection/dict.{map_1} | -19.56% | ±0.00%
+> aiken/collection/dict.{map_2} | -23.66% | ±0.00%
+> aiken/collection/dict.{union_1} | -17.91% | ±0.00%
+> aiken/collection/dict.{union_2} | -8.67% | ±0.00%
+> aiken/collection/dict.{union_3} | -22.82% | ±0.00%
+> aiken/collection/dict.{union_4} | -22.77% | ±0.00%
+> aiken/collection/dict.{union_with_1} | -22.90% | ±0.00%
+> aiken/collection/dict.{fold_1} | -35.94% | ±0.00%
+> aiken/collection/dict.{fold_2} | -22.31% | ±0.00%
+> aiken/collection/dict.{foldr_1} | -36.21% | ±0.00%
+> aiken/collection/dict.{foldr_2} | -21.93% | ±0.00%
+> aiken/collection/dict.{to_list_1} | -98.69% | -66.72%
+> aiken/collection/dict.{to_list_2} | -98.91% | -66.72%
+> aiken/collection/list.{push_1} | -8.02% | ±0.00%
+> aiken/collection/list.{push_2} | 1.25% | ±0.00%
+> aiken/collection/list.{range_1} | -27.77% | ±0.00%
+> aiken/collection/list.{range_2} | -27.39% | ±0.00%
+> aiken/collection/list.{repeat_1} | -23.72% | ±0.00%
+> aiken/collection/list.{repeat_2} | -27.96% | ±0.00%
+> aiken/collection/list.{all_1} | -28.36% | ±0.00%
+> aiken/collection/list.{all_2} | -27.59% | ±0.00%
+> aiken/collection/list.{all_3} | -27.94% | ±0.00%
+> aiken/collection/list.{any_1} | -28.23% | ±0.00%
+> aiken/collection/list.{any_2} | -28.09% | ±0.00%
+> aiken/collection/list.{any_3} | -26.95% | ±0.00%
+> aiken/collection/list.{at_1} | -27.60% | ±0.00%
+> aiken/collection/list.{at_2} | -19.96% | ±0.00%
+> aiken/collection/list.{at_3} | -27.60% | ±0.00%
+> aiken/collection/list.{at_4} | -20.77% | ±0.00%
+> aiken/collection/list.{at_5} | -25.75% | ±0.00%
+> aiken/collection/list.{count_empty} | -36.83% | ±0.00%
+> aiken/collection/list.{count_all} | -32.37% | ±0.00%
+> aiken/collection/list.{count_some} | -31.73% | ±0.00%
+> aiken/collection/list.{count_none} | -30.44% | ±0.00%
+> aiken/collection/list.{find_1} | -20.59% | ±0.00%
+> aiken/collection/list.{find_2} | -25.53% | ±0.00%
+> aiken/collection/list.{find_3} | -19.64% | ±0.00%
+> aiken/collection/list.{has_1} | -27.88% | ±0.00%
+> aiken/collection/list.{has_2} | -27.69% | ±0.00%
+> aiken/collection/list.{has_3} | -26.95% | ±0.00%
+> aiken/collection/list.{head_1} | -14.03% | ±0.00%
+> aiken/collection/list.{head_2} | -16.90% | ±0.00%
+> aiken/collection/list.{is_empty_1} | -26.48% | ±0.00%
+> aiken/collection/list.{is_empty_2} | -25.35% | ±0.00%
+> aiken/collection/list.{index_of_1} | -25.62% | ±0.00%
+> aiken/collection/list.{index_of_2} | -27.52% | ±0.00%
+> aiken/collection/list.{index_of_3} | -26.65% | ±0.00%
+> aiken/collection/list.{index_of_4} | -19.96% | ±0.00%
+> aiken/collection/list.{last_1} | -19.18% | ±0.00%
+> aiken/collection/list.{last_2} | -16.26% | ±0.00%
+> aiken/collection/list.{last_3} | -17.13% | ±0.00%
+> aiken/collection/list.{length_1} | -37.90% | ±0.00%
+> aiken/collection/list.{length_2} | -30.89% | ±0.00%
+> aiken/collection/list.{delete_1} | -20.20% | ±0.00%
+> aiken/collection/list.{delete_2} | -15.02% | ±0.00%
+> aiken/collection/list.{delete_3} | -20.55% | ±0.00%
+> aiken/collection/list.{delete_4} | -22.46% | ±0.00%
+> aiken/collection/list.{drop_1} | -24.62% | ±0.00%
+> aiken/collection/list.{drop_2} | -28.08% | ±0.00%
+> aiken/collection/list.{drop_while_1} | -19.79% | ±0.00%
+> aiken/collection/list.{drop_while_2} | -22.25% | ±0.00%
+> aiken/collection/list.{drop_while_3} | 0.86% | ±0.00%
+> aiken/collection/list.{drop_while_4} | -27.26% | ±0.00%
+> aiken/collection/list.{filter_1} | -20.20% | ±0.00%
+> aiken/collection/list.{filter_2} | -32.06% | ±0.00%
+> aiken/collection/list.{filter_3} | -31.39% | ±0.00%
+> aiken/collection/list.{filter_map_1} | -21.10% | ±0.00%
+> aiken/collection/list.{filter_map_2} | -28.74% | ±0.00%
+> aiken/collection/list.{init_1} | -19.64% | ±0.00%
+> aiken/collection/list.{init_2} | -20.01% | ±0.00%
+> aiken/collection/list.{init_3} | -13.72% | ±0.00%
+> aiken/collection/list.{partition_1} | -14.63% | ±0.00%
+> aiken/collection/list.{partition_2} | -16.85% | ±0.00%
+> aiken/collection/list.{partition_3} | -16.63% | ±0.00%
+> aiken/collection/list.{partition_4} | -16.87% | ±0.00%
+> aiken/collection/list.{partition_5} | -22.94% | ±0.00%
+> aiken/collection/list.{slice_1} | -29.08% | -2.81%
+> aiken/collection/list.{slice_2} | -30.11% | -2.25%
+> aiken/collection/list.{slice_3} | -30.29% | -1.46%
+> aiken/collection/list.{slice_4} | -28.53% | -1.48%
+> aiken/collection/list.{slice_5} | -29.73% | -1.64%
+> aiken/collection/list.{slice_6} | -32.01% | -1.80%
+> aiken/collection/list.{span_1} | -15.05% | ±0.00%
+> aiken/collection/list.{span_2} | -18.03% | ±0.00%
+> aiken/collection/list.{span_3} | -12.49% | ±0.00%
+> aiken/collection/list.{span_4} | -18.13% | ±0.00%
+> aiken/collection/list.{tail_1} | -8.88% | ±0.00%
+> aiken/collection/list.{tail_2} | -16.90% | ±0.00%
+> aiken/collection/list.{take_1} | -24.98% | ±0.00%
+> aiken/collection/list.{take_2} | -24.35% | ±0.00%
+> aiken/collection/list.{take_while_1} | -20.20% | ±0.00%
+> aiken/collection/list.{take_while_2} | -21.56% | ±0.00%
+> aiken/collection/list.{take_while_3} | -22.46% | ±0.00%
+> aiken/collection/list.{take_while_4} | -21.02% | ±0.00%
+> aiken/collection/list.{unique_1} | -20.20% | ±0.00%
+> aiken/collection/list.{unique_2} | -24.34% | ±0.00%
+> aiken/collection/list.{flat_map_1} | -19.79% | ±0.00%
+> aiken/collection/list.{flat_map_2} | -13.36% | ±0.00%
+> aiken/collection/list.{indexed_map_1} | -20.10% | ±0.00%
+> aiken/collection/list.{indexed_map_2} | -23.36% | ±0.00%
+> aiken/collection/list.{map_1} | -19.79% | ±0.00%
+> aiken/collection/list.{map_2} | -16.75% | ±0.00%
+> aiken/collection/list.{map2_1} | -20.10% | ±0.00%
+> aiken/collection/list.{map2_2} | -17.46% | ±0.00%
+> aiken/collection/list.{map2_3} | -15.92% | ±0.00%
+> aiken/collection/list.{map3_1} | -20.39% | ±0.00%
+> aiken/collection/list.{map3_2} | -19.22% | ±0.00%
+> aiken/collection/list.{reverse_1} | -20.10% | ±0.00%
+> aiken/collection/list.{reverse_2} | -12.26% | ±0.00%
+> aiken/collection/list.{sort_1} | -22.31% | ±0.00%
+> aiken/collection/list.{sort_2} | -17.93% | ±0.00%
+> aiken/collection/list.{sort_3} | -23.09% | ±0.00%
+> aiken/collection/list.{sort_4} | -20.20% | ±0.00%
+> aiken/collection/list.{unzip_1} | -14.01% | ±0.00%
+> aiken/collection/list.{unzip_2} | -5.48% | ±0.00%
+> aiken/collection/list.{concat_1} | -6.56% | ±0.00%
+> aiken/collection/list.{concat_2} | -11.25% | ±0.00%
+> aiken/collection/list.{concat_3} | -9.35% | ±0.00%
+> aiken/collection/list.{difference_1} | -24.23% | ±0.00%
+> aiken/collection/list.{difference_2} | -22.59% | ±0.00%
+> aiken/collection/list.{difference_3} | -10.64% | ±0.00%
+> aiken/collection/list.{difference_4} | -21.68% | ±0.00%
+> aiken/collection/list.{zip_1} | -20.10% | ±0.00%
+> aiken/collection/list.{zip_2} | -19.17% | ±0.00%
+> aiken/collection/list.{zip_3} | -10.35% | ±0.00%
+> aiken/collection/list.{foldl_1} | -36.95% | ±0.00%
+> aiken/collection/list.{foldl_2} | -26.90% | ±0.00%
+> aiken/collection/list.{foldl_3} | -11.27% | ±0.00%
+> aiken/collection/list.{foldr_1} | -26.68% | ±0.00%
+> aiken/collection/list.{foldr_2} | -38.04% | ±0.00%
+> aiken/collection/list.{foldr_3} | -10.14% | ±0.00%
+> aiken/collection/list.{indexed_foldr_1} | -36.95% | ±0.00%
+> aiken/collection/list.{indexed_foldr_2} | -11.06% | ±0.00%
+> aiken/collection/list.{reduce_1} | -36.95% | ±0.00%
+> aiken/collection/list.{reduce_2} | -27.99% | ±0.00%
+> aiken/collection/list.{reduce_3} | -23.54% | ±0.00%
+> aiken/collection/list.{reduce_4} | -24.84% | ±0.00%
+> aiken/collection/pairs.{get_all_1} | -21.10% | ±0.00%
+> aiken/collection/pairs.{get_all_2} | -18.86% | ±0.00%
+> aiken/collection/pairs.{get_all_3} | -19.53% | ±0.00%
+> aiken/collection/pairs.{get_all_4} | -18.70% | ±0.00%
+> aiken/collection/pairs.{get_all_5} | -21.19% | ±0.00%
+> aiken/collection/pairs.{get_first_1} | -20.63% | ±0.00%
+> aiken/collection/pairs.{get_first_2} | -18.86% | ±0.00%
+> aiken/collection/pairs.{get_first_3} | -18.86% | ±0.00%
+> aiken/collection/pairs.{get_first_4} | -18.86% | ±0.00%
+> aiken/collection/pairs.{get_first_5} | -21.05% | ±0.00%
+> aiken/collection/pairs.{get_last_1} | -20.63% | ±0.00%
+> aiken/collection/pairs.{get_last_2} | -21.13% | ±0.00%
+> aiken/collection/pairs.{get_last_3} | -21.16% | ±0.00%
+> aiken/collection/pairs.{get_last_4} | -21.79% | ±0.00%
+> aiken/collection/pairs.{get_last_5} | -21.05% | ±0.00%
+> aiken/collection/pairs.{find_all_1} | -21.10% | ±0.00%
+> aiken/collection/pairs.{find_all_2} | -18.33% | ±0.00%
+> aiken/collection/pairs.{find_all_3} | -20.51% | ±0.00%
+> aiken/collection/pairs.{find_all_4} | -17.79% | ±0.00%
+> aiken/collection/pairs.{find_first_1} | -20.63% | ±0.00%
+> aiken/collection/pairs.{find_first_2} | -18.28% | ±0.00%
+> aiken/collection/pairs.{find_first_3} | -20.22% | ±0.00%
+> aiken/collection/pairs.{find_first_4} | -18.28% | ±0.00%
+> aiken/collection/pairs.{find_last_1} | -20.63% | ±0.00%
+> aiken/collection/pairs.{find_last_2} | -20.70% | ±0.00%
+> aiken/collection/pairs.{find_last_3} | -20.22% | ±0.00%
+> aiken/collection/pairs.{find_last_4} | -20.98% | ±0.00%
+> aiken/collection/pairs.{has_key_1} | -28.07% | ±0.00%
+> aiken/collection/pairs.{has_key_2} | -25.70% | ±0.00%
+> aiken/collection/pairs.{has_key_3} | -25.80% | ±0.00%
+> aiken/collection/pairs.{has_key_4} | -24.93% | ±0.00%
+> aiken/collection/pairs.{has_key_5} | -25.70% | ±0.00%
+> aiken/collection/pairs.{keys_1} | -20.30% | ±0.00%
+> aiken/collection/pairs.{keys_2} | -13.89% | ±0.00%
+> aiken/collection/pairs.{keys_3} | -10.43% | ±0.00%
+> aiken/collection/pairs.{values_1} | -20.30% | ±0.00%
+> aiken/collection/pairs.{values_2} | -14.02% | ±0.00%
+> aiken/collection/pairs.{values_3} | -10.65% | ±0.00%
+> aiken/collection/pairs.{values_4} | -8.53% | ±0.00%
+> aiken/collection/pairs.{map_1} | -11.17% | ±0.00%
+> aiken/collection/pairs.{map_2} | -12.89% | ±0.00%
+> aiken/collection/pairs.{foldl_1} | -35.94% | ±0.00%
+> aiken/collection/pairs.{foldl_2} | -22.31% | ±0.00%
+> aiken/collection/pairs.{foldr_1} | -36.21% | ±0.00%
+> aiken/collection/pairs.{foldr_2} | -21.93% | ±0.00%
+> aiken/collection/pairs.{foldr_3} | -20.00% | ±0.00%
+> aiken/interval.{contains_1} | -21.08% | -4.01%
+> aiken/interval.{contains_2} | -31.22% | -13.95%
+> aiken/interval.{contains_3} | -26.80% | -10.08%
+> aiken/interval.{contains_4} | -31.02% | -13.67%
+> aiken/interval.{contains_5} | -32.32% | -13.59%
+> aiken/interval.{contains_6} | -28.15% | -9.81%
+> aiken/interval.{contains_7} | -32.11% | -13.32%
+> aiken/interval.{contains_8} | -29.56% | -12.59%
+> aiken/interval.{contains_9} | -29.68% | -12.78%
+> aiken/interval.{contains_10} | -29.68% | -12.78%
+> aiken/interval.{contains_11} | -35.17% | -17.77%
+> aiken/interval.{contains_12} | -21.09% | -3.86%
+> aiken/interval.{is_entirely_after_1} | -29.89% | -13.81%
+> aiken/interval.{is_entirely_after_2} | -29.63% | -13.39%
+> aiken/interval.{is_entirely_after_3} | -29.63% | -13.39%
+> aiken/interval.{is_entirely_after_4} | -29.48% | -11.81%
+> aiken/interval.{is_entirely_after_5} | -29.70% | -12.14%
+> aiken/interval.{is_entirely_after_6} | -36.09% | -19.77%
+> aiken/interval.{is_entirely_after_7} | -24.19% | -3.99%
+> aiken/interval.{is_entirely_after_8} | -24.19% | -3.99%
+> aiken/interval.{is_entirely_after_9} | -24.19% | -3.99%
+> aiken/interval.{is_entirely_before_1} | -28.44% | -13.48%
+> aiken/interval.{is_entirely_before_2} | -28.24% | -13.09%
+> aiken/interval.{is_entirely_before_3} | -28.24% | -13.09%
+> aiken/interval.{is_entirely_before_4} | -28.44% | -11.88%
+> aiken/interval.{is_entirely_before_5} | -28.26% | -11.57%
+> aiken/interval.{is_entirely_before_6} | -34.63% | -19.34%
+> aiken/interval.{is_entirely_before_7} | -22.97% | -4.02%
+> aiken/interval.{is_entirely_before_8} | -22.97% | -4.02%
+> aiken/interval.{is_entirely_before_9} | -22.97% | -4.02%
+> aiken/interval.{hull_1} | -21.51% | -0.73%
+> aiken/interval.{hull_2} | -23.06% | -0.80%
+> aiken/interval.{hull_3} | -22.00% | -0.86%
+> aiken/interval.{intersection_1} | -21.51% | -0.73%
+> aiken/interval.{intersection_2} | -21.51% | -0.73%
+> aiken/interval.{intersection_3} | -26.55% | -4.65%
+> aiken/interval.{intersection_4} | -26.45% | -4.51%
+> aiken/interval.{intersection_5} | -22.87% | -0.76%
+> aiken/interval.{intersection_6} | -19.73% | -0.98%
+> aiken/math.{abs_1} | -61.39% | -21.07%
+> aiken/math.{abs_2} | -70.90% | -34.84%
+> aiken/math.{clamp_1} | -60.95% | -23.55%
+> aiken/math.{clamp_2} | -60.95% | -23.55%
+> aiken/math.{clamp_3} | -59.22% | -18.20%
+> aiken/math.{gcd_test1} | -47.20% | ±0.00%
+> aiken/math.{gcd_test2} | -47.81% | ±0.00%
+> aiken/math.{gcd_test3} | -46.10% | ±0.00%
+> aiken/math.{is_sqrt1} | -87.41% | -68.64%
+> aiken/math.{is_sqrt2} | -87.41% | -68.64%
+> aiken/math.{log_10_2} | -51.35% | -8.40%
+> aiken/math.{log_42_2} | -51.46% | -8.24%
+> aiken/math.{log_42_3} | -51.05% | -7.81%
+> aiken/math.{log_5_0} | -54.05% | -12.92%
+> aiken/math.{log_4_4} | -50.59% | -9.31%
+> aiken/math.{log_4_43} | -49.14% | -7.28%
+> aiken/math.{max_1} | -61.39% | -21.07%
+> aiken/math.{max_2} | -61.39% | -21.07%
+> aiken/math.{max_3} | -61.39% | -21.07%
+> aiken/math.{min_1} | -61.39% | -21.07%
+> aiken/math.{min_2} | -61.39% | -21.07%
+> aiken/math.{min_3} | -61.39% | -21.07%
+> aiken/math.{pow_3_5} | -46.34% | ±0.00%
+> aiken/math.{pow_7_2} | -46.38% | ±0.00%
+> aiken/math.{pow_3__4} | -43.50% | ±0.00%
+> aiken/math.{pow_0_0} | -43.95% | ±0.00%
+> aiken/math.{pow_513_3} | -45.80% | ±0.00%
+> aiken/math.{pow_2_4} | -46.79% | ±0.00%
+> aiken/math.{pow_2_42} | -46.77% | ±0.00%
+> aiken/math.{pow2_neg} | -44.71% | ±0.00%
+> aiken/math.{pow2_0} | -45.00% | ±0.00%
+> aiken/math.{pow2_1} | -45.00% | ±0.00%
+> aiken/math.{pow2_4} | -45.00% | ±0.00%
+> aiken/math.{pow2_42} | -42.01% | ±0.00%
+> aiken/math.{pow2_256} | -41.40% | ±0.00%
+> aiken/math.{sqrt1} | -32.56% | -17.18%
+> aiken/math.{sqrt2} | -32.56% | -17.18%
+> aiken/math.{sqrt3} | -49.99% | -8.90%
+> aiken/math.{sqrt4} | -51.76% | -3.90%
+> aiken/math.{sqrt5} | -52.63% | -1.33%
+> aiken/math.{sqrt6} | -28.16% | -15.41%
+> aiken/math/rational.{from_int_1} | -14.32% | ±0.00%
+> aiken/math/rational.{new_1} | -22.98% | ±0.00%
+> aiken/math/rational.{zero_1} | -8.08% | ±0.00%
+> aiken/math/rational.{denominator_1} | -28.33% | ±0.00%
+> aiken/math/rational.{numerator_1} | -29.34% | ±0.00%
+> aiken/math/rational.{abs_examples} | -18.25% | ±0.00%
+> aiken/math/rational.{negate_1} | -15.39% | ±0.00%
+> aiken/math/rational.{reciprocal_1} | -23.28% | ±0.00%
+> aiken/math/rational.{reduce_1} | -31.89% | ±0.00%
+> aiken/math/rational.{add_1} | -15.11% | ±0.00%
+> aiken/math/rational.{add_2} | -15.11% | ±0.00%
+> aiken/math/rational.{div_1} | -22.31% | -2.75%
+> aiken/math/rational.{div_2} | -22.37% | -2.79%
+> aiken/math/rational.{mul_1} | -13.37% | ±0.00%
+> aiken/math/rational.{mul_2} | -13.37% | ±0.00%
+> aiken/math/rational.{mul_3} | -26.25% | ±0.00%
+> aiken/math/rational.{sub_1} | -15.11% | ±0.00%
+> aiken/math/rational.{sub_2} | -15.11% | ±0.00%
+> aiken/math/rational.{sub_3} | -15.11% | ±0.00%
+> aiken/math/rational.{compare_1} | -21.70% | ±0.00%
+> aiken/math/rational.{compare_with_eq} | -23.05% | ±0.00%
+> aiken/math/rational.{compare_with_neq} | -22.33% | ±0.00%
+> aiken/math/rational.{compare_with_gte} | -22.48% | ±0.00%
+> aiken/math/rational.{compare_with_gt} | -23.18% | ±0.00%
+> aiken/math/rational.{compare_with_lte} | -22.48% | ±0.00%
+> aiken/math/rational.{compare_with_lt} | -23.18% | ±0.00%
+> aiken/math/rational.{arithmetic_mean_1} | -23.31% | ±0.00%
+> aiken/math/rational.{arithmetic_mean_2} | -23.31% | ±0.00%
+> aiken/math/rational.{arithmetic_mean_3} | -20.58% | ±0.00%
+> aiken/math/rational.{geometric_mean1} | -29.87% | ±0.00%
+> aiken/math/rational.{geometric_mean2} | -24.52% | ±0.00%
+> aiken/math/rational.{geometric_mean3} | -24.52% | ±0.00%
+> aiken/math/rational.{geometric_mean4} | -33.55% | ±0.00%
+> aiken/math/rational.{geometric_mean5} | -45.34% | ±0.00%
+> aiken/math/rational.{ceil_1} | -36.26% | ±0.00%
+> aiken/math/rational.{floor_1} | -29.49% | ±0.00%
+> aiken/math/rational.{proper_fraction_1} | -18.44% | ±0.00%
+> aiken/math/rational.{proper_fraction_2} | -18.44% | ±0.00%
+> aiken/math/rational.{proper_fraction_3} | -18.44% | ±0.00%
+> aiken/math/rational.{round_1} | -25.17% | ±0.00%
+> aiken/math/rational.{round_even_1} | -25.91% | ±0.00%
+> aiken/math/rational.{truncate_1} | -29.49% | ±0.00%
+> aiken/option.{is_none_1} | -26.56% | ±0.00%
+> aiken/option.{is_none_2} | -27.52% | ±0.00%
+> aiken/option.{is_some_1} | -27.52% | ±0.00%
+> aiken/option.{is_some_2} | -26.56% | ±0.00%
+> aiken/option.{and_then_1} | -20.19% | ±0.00%
+> aiken/option.{and_then_2} | -22.15% | ±0.00%
+> aiken/option.{and_then_3} | -21.85% | ±0.00%
+> aiken/option.{choice_1} | -17.11% | ±0.00%
+> aiken/option.{choice_2} | -19.75% | ±0.00%
+> aiken/option.{choice_3} | -18.68% | ±0.00%
+> aiken/option.{flatten_1} | -12.25% | ±0.00%
+> aiken/option.{flatten_2} | -15.41% | ±0.00%
+> aiken/option.{flatten_3} | -19.46% | ±0.00%
+> aiken/option.{flatten_4} | -14.31% | ±0.00%
+> aiken/option.{map_1} | -19.89% | ±0.00%
+> aiken/option.{map_2} | -18.18% | ±0.00%
+> aiken/option.{map2_1} | -20.47% | ±0.00%
+> aiken/option.{map2_2} | -19.93% | ±0.00%
+> aiken/option.{map2_3} | -13.64% | ±0.00%
+> aiken/option.{map3_1} | -20.74% | ±0.00%
+> aiken/option.{map3_2} | -20.00% | ±0.00%
+> aiken/option.{map3_3} | -19.90% | ±0.00%
+> aiken/option.{or_try_1} | -14.36% | ±0.00%
+> aiken/option.{or_try_2} | -14.36% | ±0.00%
+> aiken/option.{or_else_1} | -38.16% | ±0.00%
+> aiken/option.{or_else_2} | -27.62% | ±0.00%
+> aiken/primitive/bytearray.{from_string_1} | -62.36% | ±0.00%
+> aiken/primitive/bytearray.{from_string_2} | -41.62% | ±0.00%
+> aiken/primitive/bytearray.{push_1} | -97.51% | -80.06%
+> aiken/primitive/bytearray.{push_2} | -97.51% | -80.06%
+> aiken/primitive/bytearray.{push_3} | -88.82% | -89.83%
+> aiken/primitive/bytearray.{index_of_1} | -39.75% | ±0.00%
+> aiken/primitive/bytearray.{index_of_2} | -43.19% | ±0.00%
+> aiken/primitive/bytearray.{index_of_3} | -41.70% | ±0.00%
+> aiken/primitive/bytearray.{index_of_4} | -37.24% | ±0.00%
+> aiken/primitive/bytearray.{index_of_5} | -26.02% | ±0.00%
+> aiken/primitive/bytearray.{is_empty_1} | -37.52% | ±0.00%
+> aiken/primitive/bytearray.{is_empty_2} | -33.77% | ±0.00%
+> aiken/primitive/bytearray.{length_1} | -49.73% | ±0.00%
+> aiken/primitive/bytearray.{length_2} | -49.73% | ±0.00%
+> aiken/primitive/bytearray.{test_bit_0} | -45.48% | 5.88%
+> aiken/primitive/bytearray.{test_bit_1} | -56.22% | -10.85%
+> aiken/primitive/bytearray.{test_bit_2} | -56.22% | -10.85%
+> aiken/primitive/bytearray.{test_bit_3} | -56.22% | -10.85%
+> aiken/primitive/bytearray.{test_bit_7} | -58.31% | -11.81%
+> aiken/primitive/bytearray.{test_bit_8} | -56.22% | -10.85%
+> aiken/primitive/bytearray.{test_bit_20_21_22_23} | -44.38% | 5.52%
+> aiken/primitive/bytearray.{drop_1} | -58.79% | ±0.00%
+> aiken/primitive/bytearray.{drop_2} | -58.79% | ±0.00%
+> aiken/primitive/bytearray.{drop_3} | -58.79% | ±0.00%
+> aiken/primitive/bytearray.{drop_4} | -58.79% | ±0.00%
+> aiken/primitive/bytearray.{slice_1} | -98.79% | -90.04%
+> aiken/primitive/bytearray.{slice_2} | -98.79% | -90.04%
+> aiken/primitive/bytearray.{slice_3} | -98.79% | -90.04%
+> aiken/primitive/bytearray.{slice_4} | -98.79% | -90.04%
+> aiken/primitive/bytearray.{slice_5} | -98.79% | -90.04%
+> aiken/primitive/bytearray.{take_1} | -97.81% | -83.40%
+> aiken/primitive/bytearray.{take_2} | -97.81% | -83.40%
+> aiken/primitive/bytearray.{take_3} | -97.81% | -83.40%
+> aiken/primitive/bytearray.{take_4} | -97.81% | -83.40%
+> aiken/primitive/bytearray.{concat_1} | -96.22% | -80.06%
+> aiken/primitive/bytearray.{concat_2} | -96.22% | -80.06%
+> aiken/primitive/bytearray.{concat_3} | -96.22% | -80.06%
+> aiken/primitive/bytearray.{concat_4} | -96.22% | -80.06%
+> aiken/primitive/bytearray.{foldl_1} | -40.96% | ±0.00%
+> aiken/primitive/bytearray.{foldl_2} | -40.09% | ±0.00%
+> aiken/primitive/bytearray.{foldl_3} | -40.29% | ±0.00%
+> aiken/primitive/bytearray.{foldl_4} | -44.76% | ±0.00%
+> aiken/primitive/bytearray.{foldr_1} | -42.56% | ±0.00%
+> aiken/primitive/bytearray.{foldr_2} | -40.93% | ±0.00%
+> aiken/primitive/bytearray.{foldr_3} | -45.34% | ±0.00%
+> aiken/primitive/bytearray.{reduce_1} | -42.95% | ±0.00%
+> aiken/primitive/bytearray.{reduce_2} | -44.60% | ±0.00%
+> aiken/primitive/bytearray.{to_string_1} | -69.56% | ±0.00%
+> aiken/primitive/bytearray.{to_string_2} | -53.54% | ±0.00%
+> aiken/primitive/bytearray.{to_hex_1} | -48.15% | ±0.00%
+> aiken/primitive/bytearray.{to_hex_2} | -48.15% | ±0.00%
+> aiken/primitive/int.{from_utf8_1} | -37.06% | ±0.00%
+> aiken/primitive/int.{from_utf8_2} | -33.40% | ±0.00%
+> aiken/primitive/int.{from_utf8_3} | -37.06% | ±0.00%
+> aiken/primitive/int.{from_utf8_4} | -32.78% | ±0.00%
+> aiken/primitive/int.{from_utf8_5} | -32.05% | ±0.00%
+> aiken/primitive/int.{from_utf8_6} | -31.36% | ±0.00%
+> aiken/primitive/string.{from_bytearray_1} | -69.56% | ±0.00%
+> aiken/primitive/string.{from_bytearray_2} | -53.54% | ±0.00%
+> aiken/primitive/string.{from_bytearray_3} | -53.54% | ±0.00%
+> aiken/primitive/string.{from_int_1} | -40.54% | -7.05%
+> aiken/primitive/string.{from_int_2} | -45.93% | -5.30%
+> aiken/primitive/string.{from_int_3} | -47.62% | -4.35%
+> aiken/primitive/string.{from_int_4} | -48.58% | -3.69%
+> aiken/primitive/string.{concat_1} | -92.30% | -80.10%
+> aiken/primitive/string.{concat_2} | -97.34% | -85.87%
+> aiken/primitive/string.{concat_3} | -98.67% | -80.35%
+> aiken/primitive/string.{join_1} | -42.87% | ±0.00%
+> aiken/primitive/string.{join_2} | -37.65% | ±0.00%
+> aiken/primitive/string.{to_bytearray_1} | -62.36% | ±0.00%
+> aiken/primitive/string.{to_bytearray_2} | -41.62% | ±0.00%
+> aiken/primitive/string.{to_bytearray_3} | -41.62% | ±0.00%
+> cardano/assets.{from_asset_list_1} | -20.51% | ±0.00%
+> cardano/assets.{from_asset_list_2} | -10.09% | ±0.00%
+> cardano/assets.{from_asset_list_3} | -12.21% | ±0.00%
+> cardano/assets.{from_asset_list_4} | -16.22% | ±0.00%
+> cardano/assets.{from_asset_list_5} | -14.60% | ±0.00%
+> cardano/assets.{from_asset_list_6} | -20.97% | ±0.00%
+> cardano/assets.{from_asset_list_7} | -20.25% | ±0.00%
+> cardano/assets.{from_asset_list_8} | -14.51% | ±0.00%
+> cardano/assets.{from_asset_list_9} | -16.07% | ±0.00%
+> cardano/assets.{add_1} | -27.84% | ±0.00%
+> cardano/assets.{add_2} | -27.56% | -0.54%
+> cardano/assets.{add_3} | -26.39% | ±0.00%
+> cardano/assets.{add_4} | -29.75% | -10.41%
+> cardano/assets.{add_5} | -27.80% | ±0.00%
+> cardano/assets.{merge_1} | -26.02% | ±0.00%
+> cardano/assets.{merge_2} | -19.60% | ±0.00%
+> cardano/assets.{merge_3} | -23.80% | ±0.00%
+> cardano/assets.{merge_4} | -25.92% | ±0.00%
+> cardano/assets.{merge_5} | -27.61% | -1.98%
+> cardano/assets.{without_lovelace_1} | -28.00% | -2.24%
+> cardano/assets.{without_lovelace_2} | -27.49% | ±0.00%
+> cardano/assets.{without_lovelace_3} | -23.40% | -0.34%
+> cardano/assets.{flatten_with_1} | -21.10% | ±0.00%
+> cardano/assets.{flatten_with_2} | -22.77% | ±0.00%
+> cardano/assets.{reduce_1} | -24.31% | ±0.00%
+> cardano/assets.{reduce_2} | -20.89% | ±0.00%
+> cardano/assets.{reduce_3} | -36.21% | ±0.00%
+>
+
+### Added
+
+- New modules covering Conway-related features (i.e. governance)
+ - [`cardano/governance`](https://aiken-lang.github.io/stdlib/cardano/governance.html)
+ - [`cardano/governance/protocol_parameters`](https://aiken-lang.github.io/stdlib/cardano/governance/protocol_parameters.html)
+
+- New primitives in `aiken/collection/pairs`:
+ - [`insert_by_ascending_key`](https://aiken-lang.github.io/stdlib/aiken/collection/pairs.html#insert_by_ascending_key)
+ - [`repsert_by_ascending_key`](https://aiken-lang.github.io/stdlib/aiken/collection/pairs.html#repsert_by_ascending_key)
+
+- New primitives in `aiken/crypto`:
+ - [`blake2b_224`](https://aiken-lang.github.io/stdlib/aiken/crypto.html#blake2b_224)
+ - [`keccak_256`](https://aiken-lang.github.io/stdlib/aiken/crypto.html#keccak_256)
+
+- New primitives in `aiken/math`:
+ - [`log2`](https://aiken-lang.github.io/stdlib/aiken/math.html#log2)
+
+- New primitives in `aiken/primitive/bytearray`:
+ - [`at`](https://aiken-lang.github.io/stdlib/aiken/primitive/bytearray.html#at)
+ - [`from_int_big_endian`](https://aiken-lang.github.io/stdlib/aiken/primitive/bytearray.html#from_int_big_endian)
+ - [`from_int_little_endian`](https://aiken-lang.github.io/stdlib/aiken/primitive/bytearray.html#from_int_little_endian)
+ - [`to_int_big_endian`](https://aiken-lang.github.io/stdlib/aiken/primitive/bytearray.html#to_int_big_endian)
+ - [`to_int_little_endian`](https://aiken-lang.github.io/stdlib/aiken/primitive/bytearray.html#to_int_little_endian)
+
+- New primitives in `aiken/primitive/int`:
+ - [`from_bytearray_big_endian`](https://aiken-lang.github.io/stdlib/aiken/primitive/int.html#from_bytearray_big_endian)
+ - [`from_bytearray_little_endian`](https://aiken-lang.github.io/stdlib/aiken/primitive/int.html#from_bytearray_little_endian)
+
+- New primitives in `aiken/crypto`:
+ - [`verify_ecdsa_signature`](https://aiken-lang.github.io/stdlib/cardano/credential.html#verify_ecdsa_signature)
+ - [`verify_schnorr_signature`](https://aiken-lang.github.io/stdlib/cardano/credential.html#verify_schnorr_signature)
+
+### Changed
+
+- Few modules have been relocated and better organized:
+ - `aiken/hash` -> [`aiken/crypto`](https://aiken-lang.github.io/stdlib/aiken/crypto.html)
+ - **collections**
+ - `aiken/dict` -> [`aiken/collection/dict`](https://aiken-lang.github.io/stdlib/aiken/collection/dict.html)
+ - `aiken/list` -> [`aiken/collection/list`](https://aiken-lang.github.io/stdlib/aiken/collection/list.html)
+ - `aiken/pairs` -> [`aiken/collection/pairs`](https://aiken-lang.github.io/stdlib/aiken/collection/pairs.html)
+ - **primitive**
+ - `aiken/bytearray` -> [`aiken/primitive/bytearray`](https://aiken-lang.github.io/stdlib/aiken/primitive/bytearray.html)
+ - `aiken/int` -> [`aiken/primitive/int`](https://aiken-lang.github.io/stdlib/aiken/primitive/int.html)
+ - `aiken/string` -> [`aiken/primitive/string`](https://aiken-lang.github.io/stdlib/aiken/primitive/string.html)
+ - **cardano**
+ - `aiken/transaction` -> [`cardano/transaction`](https://aiken-lang.github.io/stdlib/cardano/transaction.html)
+ - `aiken/transaction/certificate` -> [`cardano/certificate`](https://aiken-lang.github.io/stdlib/cardano/certificate.html)
+ - `aiken/transaction/credential` -> [`cardano/address`](https://aiken-lang.github.io/stdlib/cardano/address.html) & `aiken/crypto`
+ - `aiken/transaction/value` -> [`cardano/assets`](https://aiken-lang.github.io/stdlib/cardano/assets.html)
+
+- Several zero-argument functions have been turned into top-level constants
+ - `aiken/dict.new()` -> [`aiken/collection/dict.empty`](https://aiken-lang.github.io/stdlib/aiken/collection/dict.html#empty)
+ - `aiken/interval.empty()` -> [`aiken/interval.empty`](https://aiken-lang.github.io/stdlib/aiken/interval.html#empty)
+ - `aiken/interval.everything()` -> [`aiken/interval.everything`](https://aiken-lang.github.io/stdlib/aiken/interval.html#everything)
+ - `aiken/math/rational.zero()` -> [`aiken/math/rational.zero`](https://aiken-lang.github.io/stdlib/aiken/math/rational.html#zero)
+ - `aiken/transaction/value.zero()` -> [`cardano/assets.zero`](https://aiken-lang.github.io/stdlib/cardano/assets.html#zero)
+
+- The `Transaction` type from [`cardano/transaction`](https://aiken-lang.github.io/stdlib/cardano/transaction.html) (originally `aiken/transaction`) has been greatly reworked to match the new transaction format in Plutus V3.
+
+- The `ScriptContext` type has split from `cardano/transaction` (originally `aiken/transaction`) and moved into its own module [`cardano/script_context`](https://aiken-lang.github.io/stdlib/cardano/script_context.html) and adjusted to its new form as per Plutus V3.
+
+- The constructors of [`Credential`](https://aiken-lang.github.io/stdlib/cardano/address.html#credential) have been renamed from `VerificationKeyCredential` and `ScriptCredential` into `VerificationKey` and `Script` respectively.
+
+- The function `remove_all`, `remove_first` and `remove_last` from [`aiken/collection/pairs`](https://aiken-lang.github.io/stdlib/aiken/collection/pairs.html) (originally `aiken/pairs`) have been renamed to `delete_all`, `delete_first` and `delete_last` respectively.
+
+- The function `verify_signature` from [`aiken/crypto`](https://aiken-lang.github.io/stdlib/aiken/crypto.html) (originally `aiken/credential`) has been renamed to `verify_ed25519_signature`.
+
+### Removed
+
+- The module `aiken/time`. The `PosixTime` alias is no longer used anywhere.
+
+- `MintedValue` (from `aiken/transaction/value` originally) and its associated functions are no longer needed and, therefore, gone.
+
## v1.9.0 - 2024-05-24
### Added
diff --git a/build/packages/aiken-lang-stdlib/README.md b/build/packages/aiken-lang-stdlib/README.md
index b3a4c5f..a33bf3f 100644
--- a/build/packages/aiken-lang-stdlib/README.md
+++ b/build/packages/aiken-lang-stdlib/README.md
@@ -12,8 +12,9 @@
aiken's version | stdlib's version(s)
--- | ---
-`v1.0.28-alpha` | `1.9.0`
-`v1.0.26-alpha` | `1.8.0`
+`v1.0.29-alpha` | `>= 1.9.0`
+`v1.0.28-alpha` | `>= 1.9.0`
+`v1.0.26-alpha` | `<= 1.8.0`
## Overview
diff --git a/build/packages/aiken-lang-stdlib/aiken.toml b/build/packages/aiken-lang-stdlib/aiken.toml
index 5d5dc77..d9881e5 100644
--- a/build/packages/aiken-lang-stdlib/aiken.toml
+++ b/build/packages/aiken-lang-stdlib/aiken.toml
@@ -1,5 +1,5 @@
name = "aiken-lang/stdlib"
-version = "1.9.0"
+version = "main"
licences = ["Apache-2.0"]
description = "The Aiken Standard Library"
diff --git a/build/packages/aiken-lang-stdlib/lib/aiken/cbor.ak b/build/packages/aiken-lang-stdlib/lib/aiken/cbor.ak
index 5a5ec2e..64de402 100644
--- a/build/packages/aiken-lang-stdlib/lib/aiken/cbor.ak
+++ b/build/packages/aiken-lang-stdlib/lib/aiken/cbor.ak
@@ -1,9 +1,5 @@
-use aiken/builtin.{
- append_bytearray, choose_data, cons_bytearray, decode_utf8, index_bytearray,
- length_of_bytearray, quotient_integer, remainder_integer, serialise_data,
- un_b_data, un_constr_data, un_i_data, un_list_data, un_map_data,
-}
-use aiken/list
+use aiken
+use aiken/builtin.{decode_utf8, serialise_data}
/// Serialise any value to binary, encoding using [CBOR](https://www.rfc-editor.org/rfc/rfc8949).
///
@@ -17,14 +13,14 @@ use aiken/list
/// useful for debugging.
///
/// ```aiken
-/// serialise(42) == #"182a"
-/// serialise(#"a1b2") == #"42a1b2"
-/// serialise([]) == #"80"
-/// serialise((1, 2)) == #"9f0102ff"
-/// serialise((1, #"ff", 3)) == #"9f0141ff03ff"
-/// serialise([(1, #"ff")]) == #"a10141ff"
-/// serialise(Some(42)) == #"d8799f182aff"
-/// serialise(None) == #"d87a80"
+/// cbor.serialise(42) == #"182a"
+/// cbor.serialise(#"a1b2") == #"42a1b2"
+/// cbor.serialise([]) == #"80"
+/// cbor.serialise((1, 2)) == #"9f0102ff"
+/// cbor.serialise((1, #"ff", 3)) == #"9f0141ff03ff"
+/// cbor.serialise([(1, #"ff")]) == #"a10141ff"
+/// cbor.serialise(Some(42)) == #"d8799f182aff"
+/// cbor.serialise(None) == #"d87a80"
/// ```
pub fn serialise(self: Data) -> ByteArray {
serialise_data(self)
@@ -77,192 +73,21 @@ test serialise_9() {
/// a good idea in the Cardano world.
///
/// ```aiken
-/// diagnostic(42) == "42"
-/// diagnostic(#"a1b2") == "h'A1B2'"
-/// diagnostic([1, 2, 3]) == "[_ 1, 2, 3]"
-/// diagnostic([]) == "[]"
-/// diagnostic((1, 2)) == "[_ 1, 2]"
-/// diagnostic((1, #"ff", 3)) == "[_ 1, h'FF', 3]"
-/// diagnostic([(1, #"ff")]) == "{_ 1: h'FF' }"
-/// diagnostic(Some(42)) == "121([_ 42])"
-/// diagnostic(None) == "122([])"
+/// cbor.diagnostic(42) == "42"
+/// cbor.diagnostic(#"a1b2") == "h'A1B2'"
+/// cbor.diagnostic([1, 2, 3]) == "[_ 1, 2, 3]"
+/// cbor.diagnostic([]) == "[]"
+/// cbor.diagnostic((1, 2)) == "[_ 1, 2]"
+/// cbor.diagnostic((1, #"ff", 3)) == "[_ 1, h'FF', 3]"
+/// cbor.diagnostic([(1, #"ff")]) == "{_ 1: h'FF' }"
+/// cbor.diagnostic(Some(42)) == "121([_ 42])"
+/// cbor.diagnostic(None) == "122([])"
/// ```
pub fn diagnostic(self: Data) -> String {
- do_diagnostic(self, #"")
+ aiken.diagnostic(self, #"")
|> decode_utf8
}
-/// UTF-8 lookup table. Comes in handy to decipher the code below.
-///
-/// | Symbol | Decimal | Hex |
-/// | --- | --- | --- |
-/// | | 32 | 0x20 |
-/// | ' | 39 | 0x27 |
-/// | ( | 40 | 0x28 |
-/// | ) | 41 | 0x29 |
-/// | , | 44 | 0x2c |
-/// | 0 | 48 | 0x30 |
-/// | : | 58 | 0x3a |
-/// | A | 65 | 0x41 |
-/// | [ | 91 | 0x5b |
-/// | ] | 93 | 0x5d |
-/// | _ | 95 | 0x5f |
-/// | h | 104 | 0x68 |
-/// | { | 123 | 0x7b |
-/// | } | 125 | 0x7d |
-fn do_diagnostic(self: Data, builder: ByteArray) -> ByteArray {
- choose_data(
- self,
- {
- // -------- Constr
- let Pair(constr, fields) = un_constr_data(self)
-
- // NOTE: This is fundamentally the same logic for serializing list. However, the compiler
- // doesn't support mutual recursion just yet, so we can't extract that logic in a separate
- // function.
- //
- // See [aiken-lang/aiken#389](https://github.com/aiken-lang/aiken/pull/389)
- let builder =
- when fields is {
- [] -> append_bytearray(#"5b5d29", builder)
- _ -> {
- let (_, bytes) =
- list.foldr(
- fields,
- (#"5d", append_bytearray(#"29", builder)),
- fn(e: Data, st: (ByteArray, ByteArray)) {
- (#"2c20", do_diagnostic(e, append_bytearray(st.1st, st.2nd)))
- },
- )
- append_bytearray(#"5b5f20", bytes)
- }
- }
-
- let constr_tag =
- if constr < 7 {
- 121 + constr
- } else if constr < 128 {
- 1280 + constr - 7
- } else {
- fail @"What are you doing? No I mean, seriously."
- }
-
- builder
- |> append_bytearray(#"28", _)
- |> from_int(constr_tag, _)
- },
- {
- // -------- Map
-
- let elems = un_map_data(self)
- when elems is {
- [] -> append_bytearray(#"7b7d", builder)
- _ -> {
- let (_, bytes) =
- list.foldr(
- elems,
- (#"207d", builder),
- fn(e: Pair, st: (ByteArray, ByteArray)) {
- let value =
- do_diagnostic(e.2nd, append_bytearray(st.1st, st.2nd))
- (
- #"2c20",
- do_diagnostic(e.1st, append_bytearray(#"3a20", value)),
- )
- },
- )
- append_bytearray(#"7b5f20", bytes)
- }
- }
- },
- {
- // -------- List
-
- let elems = un_list_data(self)
- when elems is {
- [] -> append_bytearray(#"5b5d", builder)
- _ -> {
- let (_, bytes) =
- list.foldr(
- elems,
- (#"5d", builder),
- fn(e: Data, st: (ByteArray, ByteArray)) {
- (#"2c20", do_diagnostic(e, append_bytearray(st.1st, st.2nd)))
- },
- )
- append_bytearray(#"5b5f20", bytes)
- }
- }
- },
- // -------- Integer
- self
- |> un_i_data
- |> from_int(builder),
- {
- // -------- ByteArray
- let bytes = un_b_data(self)
-
- bytes
- |> encode_base16(
- length_of_bytearray(bytes) - 1,
- append_bytearray(#"27", builder),
- )
- |> append_bytearray(#"6827", _)
- },
- )
-}
-
-fn encode_base16(bytes: ByteArray, ix: Int, builder: ByteArray) -> ByteArray {
- if ix < 0 {
- builder
- } else {
- let byte = index_bytearray(bytes, ix)
- let msb = byte / 16
- let lsb = byte % 16
- let builder =
- cons_bytearray(
- msb + if msb < 10 {
- 48
- } else {
- 55
- },
- cons_bytearray(
- lsb + if lsb < 10 {
- 48
- } else {
- 55
- },
- builder,
- ),
- )
- encode_base16(bytes, ix - 1, builder)
- }
-}
-
-fn from_int(i: Int, digits: ByteArray) -> ByteArray {
- if i == 0 {
- append_bytearray(#"30", digits)
- } else if i < 0 {
- append_bytearray(#"2d", from_int(-i, digits))
- } else {
- do_from_int(
- quotient_integer(i, 10),
- cons_bytearray(remainder_integer(i, 10) + 48, digits),
- )
- }
-}
-
-fn do_from_int(i: Int, digits: ByteArray) -> ByteArray {
- if i <= 0 {
- digits
- } else {
- do_from_int(
- quotient_integer(i, 10),
- cons_bytearray(remainder_integer(i, 10) + 48, digits),
- )
- }
-}
-
test diagnostic_1() {
diagnostic(42) == @"42"
}
diff --git a/build/packages/aiken-lang-stdlib/lib/aiken/collection.ak b/build/packages/aiken-lang-stdlib/lib/aiken/collection.ak
new file mode 100644
index 0000000..39a8b23
--- /dev/null
+++ b/build/packages/aiken-lang-stdlib/lib/aiken/collection.ak
@@ -0,0 +1,4 @@
+/// An positive integer, that materializes the position of an element in a
+/// collection.
+pub type Index =
+ Int
diff --git a/build/packages/aiken-lang-stdlib/lib/aiken/dict.ak b/build/packages/aiken-lang-stdlib/lib/aiken/collection/dict.ak
similarity index 86%
rename from build/packages/aiken-lang-stdlib/lib/aiken/dict.ak
rename to build/packages/aiken-lang-stdlib/lib/aiken/collection/dict.ak
index d280443..f2f5705 100644
--- a/build/packages/aiken-lang-stdlib/lib/aiken/dict.ak
+++ b/build/packages/aiken-lang-stdlib/lib/aiken/collection/dict.ak
@@ -1,15 +1,16 @@
//// A module for working with bytearray dictionaries.
////
-//// ### Important
////
-//// Dictionaries are **ordered sets** of key-value pairs, which thus
-//// preserve some invariants. Specifically, each key is only present once in
-//// the dictionary and all keys are stored in ascending lexicographic order.
-////
-//// These invariants allow for more optimized functions to operate on `Dict`,
-//// but as a trade-offs, prevent `Dict` from being serializable. To recover a `Dict`
-//// from an unknown `Data`, you must first recover an `Pairs` and use
-//// `dict.from_ascending_list`.
+//// > [!IMPORTANT]
+//// >
+//// > Dictionaries are **ordered sets** of key-value pairs, which thus
+//// > preserve some invariants. Specifically, each key is only present once in
+//// > the dictionary and all keys are stored in ascending lexicographic order.
+//// >
+//// > These invariants allow for more optimized functions to operate on `Dict`,
+//// > but as a trade-offs, prevent `Dict` from being serializable. To recover a `Dict`
+//// > from an unknown `Data`, you must first recover an `Pairs` and use
+//// > [`dict.from_ascending_list`](#from_ascending_list).
use aiken/builtin
@@ -30,13 +31,13 @@ pub opaque type Dict {
inner: Pairs,
}
-/// Create a new empty Dict
+// ## Constructing
+
+/// An empty dictionnary.
/// ```aiken
-/// dict.to_pairs(dict.new()) == []
+/// dict.to_pairs(dict.empty) == []
/// ```
-pub fn new() -> Dict {
- Dict { inner: [] }
-}
+pub const empty: Dict = Dict { inner: [] }
const foo = #"666f6f"
@@ -44,158 +45,165 @@ const bar = #"626172"
const baz = #"62617a"
-fn fixture_1() {
- new()
+const fixture_1 =
+ empty
|> insert(foo, 42)
|> insert(bar, 14)
-}
-/// Remove a key-value pair from the dictionary. If the key is not found, no changes are made.
+/// Like ['from_pairs'](#from_pairs), but from an already sorted list by ascending
+/// keys. This function fails (i.e. halt the program execution) if the list isn't
+/// sorted.
///
/// ```aiken
+/// let pairs = [Pair("a", 100), Pair("b", 200), Pair("c", 300)]
+///
/// let result =
-/// dict.new()
-/// |> dict.insert(key: "a", value: 100)
-/// |> dict.insert(key: "b", value: 200)
-/// |> dict.delete(key: "a")
+/// dict.from_ascending_pairs(pairs)
/// |> dict.to_pairs()
///
-/// result == [Pair("b", 200)]
+/// result == [Pair("a", 100), Pair("b", 200), Pair("c", 300)]
/// ```
-pub fn delete(self: Dict, key: ByteArray) -> Dict {
- Dict { inner: do_delete(self.inner, key) }
+///
+/// This is meant to be used to turn a list constructed off-chain into a `Dict`
+/// which has taken care of maintaining interval invariants. This function still
+/// performs a sanity check on all keys to avoid silly mistakes. It is, however,
+/// considerably faster than ['from_pairs'](from_pairs)
+pub fn from_ascending_pairs(xs: Pairs) -> Dict {
+ let Void = check_ascending_list(xs)
+ Dict { inner: xs }
}
-fn do_delete(
- self: Pairs,
- key k: ByteArray,
-) -> Pairs {
- when self is {
- [] ->
- []
- [Pair(k2, v2), ..rest] ->
- if builtin.less_than_equals_bytearray(k, k2) {
- if k == k2 {
- rest
- } else {
- self
- }
+fn check_ascending_list(xs: Pairs) {
+ when xs is {
+ [] -> Void
+ [_] -> Void
+ [Pair(x0, _), Pair(x1, _) as e, ..rest] ->
+ if builtin.less_than_bytearray(x0, x1) {
+ check_ascending_list([e, ..rest])
} else {
- [Pair(k2, v2), ..do_delete(rest, k)]
+ fail @"keys in associative list aren't in ascending order"
}
}
}
-test delete_1() {
- delete(new(), foo) == new()
-}
-
-test delete_2() {
- let m =
- new()
- |> insert(foo, 14)
- delete(m, foo) == new()
-}
-
-test delete_3() {
- let m =
- new()
- |> insert(foo, 14)
- delete(m, bar) == m
-}
-
-test delete_4() {
- let m =
- new()
- |> insert(foo, 14)
- |> insert(bar, 14)
- !has_key(delete(m, foo), foo)
+/// Like [`from_ascending_pairs`](#from_ascending_pairs) but fails if **any**
+/// value doesn't satisfy the predicate.
+///
+/// ```aiken
+/// let pairs = [Pair("a", 100), Pair("b", 200), Pair("c", 300)]
+///
+/// dict.from_ascending_pairs_with(pairs, fn(x) { x <= 250 }) // fail
+/// ```
+pub fn from_ascending_pairs_with(
+ xs: Pairs,
+ predicate: fn(value) -> Bool,
+) -> Dict {
+ let Void = check_ascending_pairs_with(xs, predicate)
+ Dict { inner: xs }
}
-test delete_5() {
- let m =
- new()
- |> insert(foo, 14)
- |> insert(bar, 14)
- has_key(delete(m, bar), foo)
+fn check_ascending_pairs_with(
+ xs: Pairs,
+ predicate: fn(value) -> Bool,
+) {
+ when xs is {
+ [] -> Void
+ [Pair(_, v)] ->
+ if predicate(v) {
+ Void
+ } else {
+ fail @"value doesn't satisfy predicate"
+ }
+ [Pair(x0, v0), Pair(x1, _) as e, ..rest] ->
+ if builtin.less_than_bytearray(x0, x1) {
+ if predicate(v0) {
+ check_ascending_pairs_with([e, ..rest], predicate)
+ } else {
+ fail @"value doesn't satisfy predicate"
+ }
+ } else {
+ fail @"keys in pairs aren't in ascending order"
+ }
+ }
}
-test delete_6() {
- let m =
- new()
- |> insert("aaa", 1)
- |> insert("bbb", 2)
- |> insert("ccc", 3)
- |> insert("ddd", 4)
- |> insert("eee", 5)
- |> insert("fff", 6)
- |> insert("ggg", 7)
- |> insert("hhh", 8)
- |> insert("iii", 9)
- |> insert("jjj", 10)
+test bench_from_ascending_pairs() {
+ let dict =
+ from_ascending_pairs(
+ [
+ Pair("aaaa", 1), Pair("aaab", 9), Pair("aaba", 5), Pair("aabb", 13),
+ Pair("abaa", 2), Pair("abab", 10), Pair("abba", 6), Pair("abbb", 14),
+ Pair("baaa", 3), Pair("baab", 11), Pair("baba", 7), Pair("babb", 15),
+ Pair("bbaa", 4), Pair("bbab", 12), Pair("bbba", 8), Pair("bbbb", 16),
+ ],
+ )
- delete(m, "bcd") == m
+ size(dict) == 16
}
-/// Keep only the key-value pairs that pass the given predicate.
+/// Construct a dictionary from a list of key-value pairs. Note that when a key is present
+/// multiple times, the first occurrence prevails.
///
/// ```aiken
+/// let pairs = [Pair("a", 100), Pair("c", 300), Pair("b", 200)]
+///
/// let result =
-/// dict.new()
-/// |> dict.insert(key: "a", value: 100)
-/// |> dict.insert(key: "b", value: 200)
-/// |> dict.insert(key: "c", value: 300)
-/// |> dict.filter(fn(k, _v) { k != "a" })
+/// dict.from_pairs(pairs)
/// |> dict.to_pairs()
///
-/// result == [Pair("b", 200), Pair("c", 300)]
+/// result == [Pair("a", 100), Pair("b", 200), Pair("c", 300)]
/// ```
-pub fn filter(
- self: Dict,
- with: fn(ByteArray, value) -> Bool,
-) -> Dict {
- Dict { inner: do_filter(self.inner, with) }
+pub fn from_pairs(self: Pairs) -> Dict {
+ Dict { inner: do_from_pairs(self) }
}
-fn do_filter(
- self: Pairs,
- with: fn(ByteArray, value) -> Bool,
-) -> Pairs {
- when self is {
+fn do_from_pairs(xs: Pairs) -> Pairs {
+ when xs is {
[] ->
[]
- [Pair(k, v), ..rest] ->
- if with(k, v) {
- [Pair(k, v), ..do_filter(rest, with)]
- } else {
- do_filter(rest, with)
- }
+ [Pair(k, v), ..rest] -> do_insert(do_from_pairs(rest), k, v)
}
}
-test filter_1() {
- filter(new(), fn(_, _) { True }) == new()
+test from_list_1() {
+ from_pairs([]) == empty
}
-test filter_2() {
- let expected =
- new()
- |> insert(foo, 42)
- filter(fixture_1(), fn(_, v) { v > 14 }) == expected
+test from_list_2() {
+ from_pairs([Pair(foo, 42), Pair(bar, 14)]) == from_pairs(
+ [Pair(bar, 14), Pair(foo, 42)],
+ )
}
-test filter_3() {
- let expected =
- new()
- |> insert(bar, 14)
- filter(fixture_1(), fn(k, _) { k == bar }) == expected
+test from_list_3() {
+ from_pairs([Pair(foo, 42), Pair(bar, 14)]) == fixture_1
+}
+
+test from_list_4() {
+ from_pairs([Pair(foo, 42), Pair(bar, 14), Pair(foo, 1337)]) == fixture_1
}
+test bench_from_pairs() {
+ let dict =
+ from_pairs(
+ [
+ Pair("bbba", 8), Pair("bbab", 12), Pair("aabb", 13), Pair("aaab", 9),
+ Pair("bbbb", 16), Pair("aaaa", 1), Pair("aaba", 5), Pair("abab", 10),
+ Pair("baba", 7), Pair("baab", 11), Pair("abaa", 2), Pair("baaa", 3),
+ Pair("bbaa", 4), Pair("babb", 15), Pair("abbb", 14), Pair("abba", 6),
+ ],
+ )
+
+ size(dict) == 16
+}
+
+// ## Inspecting
+
/// Finds a value in the dictionary, and returns the first key found to have that value.
///
/// ```aiken
/// let result =
-/// dict.new()
+/// dict.empty
/// |> dict.insert(key: "a", value: 42)
/// |> dict.insert(key: "b", value: 14)
/// |> dict.insert(key: "c", value: 42)
@@ -220,12 +228,12 @@ fn do_find(self: Pairs, value v: value) -> Option {
}
test find_1() {
- find(new(), foo) == None
+ find(empty, foo) == None
}
test find_2() {
find(
- new()
+ empty
|> insert(foo, 14),
14,
) == Some(foo)
@@ -233,7 +241,7 @@ test find_2() {
test find_3() {
find(
- new()
+ empty
|> insert(foo, 14),
42,
) == None
@@ -241,7 +249,7 @@ test find_3() {
test find_4() {
find(
- new()
+ empty
|> insert(foo, 14)
|> insert(bar, 42)
|> insert(baz, 14),
@@ -249,401 +257,424 @@ test find_4() {
) == Some(baz)
}
-/// Fold over the key-value pairs in a dictionary. The fold direction follows keys
-/// in ascending order and is done from right-to-left.
+/// Get a value in the dict by its key.
///
/// ```aiken
/// let result =
-/// dict.new()
-/// |> dict.insert(key: "a", value: 100)
-/// |> dict.insert(key: "b", value: 200)
-/// |> dict.insert(key: "c", value: 300)
-/// |> dict.foldr(0, fn(_k, v, r) { v + r })
+/// dict.empty
+/// |> dict.insert(key: "a", value: "Aiken")
+/// |> dict.get(key: "a")
///
-/// result == 600
+/// result == Some("Aiken")
/// ```
-pub fn foldr(
- self: Dict,
- zero: result,
- with: fn(ByteArray, value, result) -> result,
-) -> result {
- do_foldr(self.inner, zero, with)
+pub fn get(self: Dict, key: ByteArray) -> Option {
+ do_get(self.inner, key)
}
-fn do_foldr(
- self: Pairs,
- zero: result,
- with: fn(ByteArray, value, result) -> result,
-) -> result {
+fn do_get(self: Pairs, key k: ByteArray) -> Option {
when self is {
- [] -> zero
- [Pair(k, v), ..rest] -> with(k, v, do_foldr(rest, zero, with))
+ [] -> None
+ [Pair(k2, v), ..rest] ->
+ if builtin.less_than_equals_bytearray(k, k2) {
+ if k == k2 {
+ Some(v)
+ } else {
+ None
+ }
+ } else {
+ do_get(rest, k)
+ }
}
}
-test foldr_1() {
- foldr(new(), 14, fn(_, _, _) { 42 }) == 14
+test get_1() {
+ get(empty, foo) == None
}
-test foldr_2() {
- foldr(fixture_1(), zero: 0, with: fn(_, v, total) { v + total }) == 56
+test get_2() {
+ let m =
+ empty
+ |> insert(foo, "Aiken")
+ |> insert(bar, "awesome")
+ get(m, key: foo) == Some("Aiken")
}
-/// Fold over the key-value pairs in a dictionary. The fold direction follows keys
-/// in ascending order and is done from left-to-right.
-///
-/// ```aiken
-/// let result =
-/// dict.new()
-/// |> dict.insert(key: "a", value: 100)
-/// |> dict.insert(key: "b", value: 200)
-/// |> dict.insert(key: "c", value: 300)
-/// |> dict.foldl(0, fn(_k, v, r) { v + r })
-///
-/// result == 600
-/// ```
-pub fn foldl(
- self: Dict,
- zero: result,
- with: fn(ByteArray, value, result) -> result,
-) -> result {
- do_foldl(self.inner, zero, with)
+test get_3() {
+ let m =
+ empty
+ |> insert(foo, "Aiken")
+ |> insert(bar, "awesome")
+ get(m, key: baz) == None
}
-fn do_foldl(
- self: Pairs,
- zero: result,
- with: fn(ByteArray, value, result) -> result,
-) -> result {
- when self is {
- [] -> zero
- [Pair(k, v), ..rest] -> do_foldl(rest, with(k, v, zero), with)
- }
-}
+test get_4() {
+ let m =
+ empty
+ |> insert("aaa", "1")
+ |> insert("bbb", "2")
+ |> insert("ccc", "3")
+ |> insert("ddd", "4")
+ |> insert("eee", "5")
+ |> insert("fff", "6")
+ |> insert("ggg", "7")
+ |> insert("hhh", "8")
+ |> insert("iii", "9")
+ |> insert("jjj", "10")
-test fold_1() {
- foldl(new(), 14, fn(_, _, _) { 42 }) == 14
+ get(m, "bcd") == None
}
-test fold_2() {
- foldl(fixture_1(), zero: 0, with: fn(_, v, total) { v + total }) == 56
+test get_5() {
+ let m =
+ empty
+ |> insert("aaa", "1")
+ |> insert("bbb", "2")
+ |> insert("ccc", "3")
+ |> insert("ddd", "4")
+ |> insert("eee", "5")
+ |> insert("fff", "6")
+ |> insert("ggg", "7")
+ |> insert("hhh", "8")
+ |> insert("iii", "9")
+ |> insert("jjj", "10")
+
+ get(m, "kkk") == None
}
-/// Construct a dictionary from a list of key-value pairs. Note that when a key is present
-/// multiple times, the first occurrence prevails.
+/// Check if a key exists in the dictionary.
///
/// ```aiken
-/// let pairs = [Pair("a", 100), Pair("c", 300), Pair("b", 200)]
-///
/// let result =
-/// dict.from_pairs(pairs)
-/// |> dict.to_pairs()
+/// dict.empty
+/// |> dict.insert(key: "a", value: "Aiken")
+/// |> dict.has_key("a")
///
-/// result == [Pair("a", 100), Pair("b", 200), Pair("c", 300)]
+/// result == True
/// ```
-pub fn from_pairs(self: Pairs) -> Dict {
- Dict { inner: do_from_pairs(self) }
+pub fn has_key(self: Dict, key k: ByteArray) -> Bool {
+ do_has_key(self.inner, k)
}
-fn do_from_pairs(xs: Pairs) -> Pairs {
- when xs is {
- [] ->
- []
- [Pair(k, v), ..rest] -> do_insert(do_from_pairs(rest), k, v)
+fn do_has_key(self: Pairs, key k: ByteArray) -> Bool {
+ when self is {
+ [] -> False
+ [Pair(k2, _), ..rest] ->
+ if builtin.less_than_equals_bytearray(k, k2) {
+ k == k2
+ } else {
+ do_has_key(rest, k)
+ }
}
}
-test from_list_1() {
- from_pairs([]) == new()
+test has_key_1() {
+ !has_key(empty, foo)
}
-test from_list_2() {
- from_pairs([Pair(foo, 42), Pair(bar, 14)]) == from_pairs(
- [Pair(bar, 14), Pair(foo, 42)],
+test has_key_2() {
+ has_key(
+ empty
+ |> insert(foo, 14),
+ foo,
)
}
-test from_list_3() {
- from_pairs([Pair(foo, 42), Pair(bar, 14)]) == fixture_1()
+test has_key_3() {
+ !has_key(
+ empty
+ |> insert(foo, 14),
+ bar,
+ )
}
-test from_list_4() {
- from_pairs([Pair(foo, 42), Pair(bar, 14), Pair(foo, 1337)]) == fixture_1()
+test has_key_4() {
+ has_key(
+ empty
+ |> insert(foo, 14)
+ |> insert(bar, 42),
+ bar,
+ )
}
-test bench_from_pairs() {
- let dict =
- from_pairs(
- [
- Pair("bbba", 8),
- Pair("bbab", 12),
- Pair("aabb", 13),
- Pair("aaab", 9),
- Pair("bbbb", 16),
- Pair("aaaa", 1),
- Pair("aaba", 5),
- Pair("abab", 10),
- Pair("baba", 7),
- Pair("baab", 11),
- Pair("abaa", 2),
- Pair("baaa", 3),
- Pair("bbaa", 4),
- Pair("babb", 15),
- Pair("abbb", 14),
- Pair("abba", 6),
- ],
- )
+/// Efficiently checks whether a dictionary is empty.
+/// ```aiken
+/// dict.is_empty(dict.empty) == True
+/// ```
+pub fn is_empty(self: Dict) -> Bool {
+ when self.inner is {
+ [] -> True
+ _ -> False
+ }
+}
- size(dict) == 16
+test is_empty_1() {
+ is_empty(empty)
}
-/// Like ['from_list'](from_list), but from an already sorted list by ascending
-/// keys. This function fails (i.e. halt the program execution) if the list isn't
-/// sorted.
+/// Extract all the keys present in a given `Dict`.
///
/// ```aiken
-/// let pairs = [Pair("a", 100), Pair("b", 200), Pair("c", 300)]
-///
/// let result =
-/// dict.from_ascending_pairs(pairs)
-/// |> dict.to_pairs()
+/// dict.empty
+/// |> dict.insert("a", 14)
+/// |> dict.insert("b", 42)
+/// |> dict.insert("a", 1337)
+/// |> dict.keys()
///
-/// result == [Pair("a", 100), Pair("b", 200), Pair("c", 300)]
+/// result == ["a", "b"]
/// ```
+pub fn keys(self: Dict) -> List {
+ do_keys(self.inner)
+}
+
+fn do_keys(self: Pairs) -> List {
+ when self is {
+ [] ->
+ []
+ [Pair(k, _), ..rest] ->
+ [k, ..do_keys(rest)]
+ }
+}
+
+test keys_1() {
+ keys(empty) == []
+}
+
+test keys_2() {
+ keys(
+ empty
+ |> insert(foo, 0)
+ |> insert(bar, 0),
+ ) == [bar, foo]
+}
+
+/// Return the number of key-value pairs in the dictionary.
///
-/// This is meant to be used to turn a list constructed off-chain into a `Dict`
-/// which has taken care of maintaining interval invariants. This function still
-/// performs a sanity check on all keys to avoid silly mistakes. It is, however,
-/// considerably faster than ['from_list'](from_list)
-pub fn from_ascending_pairs(xs: Pairs) -> Dict {
- let Void = check_ascending_list(xs)
- Dict { inner: xs }
+/// ```aiken
+/// let result =
+/// dict.empty
+/// |> dict.insert("a", 100)
+/// |> dict.insert("b", 200)
+/// |> dict.insert("c", 300)
+/// |> dict.size()
+///
+/// result == 3
+/// ```
+pub fn size(self: Dict) -> Int {
+ do_size(self.inner)
}
-fn check_ascending_list(xs: Pairs) {
- when xs is {
- [] -> Void
- [_] -> Void
- [Pair(x0, _), Pair(x1, _) as e, ..rest] ->
- if builtin.less_than_bytearray(x0, x1) {
- check_ascending_list([e, ..rest])
- } else {
- fail @"keys in associative list aren't in ascending order"
- }
+fn do_size(self: Pairs) -> Int {
+ when self is {
+ [] -> 0
+ [_, ..rest] -> 1 + do_size(rest)
}
}
-/// Like [`from_ascending_pairs`](#from_ascending_list) but fails if **any**
-/// value doesn't satisfy the predicate.
+test size_1() {
+ size(empty) == 0
+}
+
+test size_2() {
+ size(
+ empty
+ |> insert(foo, 14),
+ ) == 1
+}
+
+test size_3() {
+ size(
+ empty
+ |> insert(foo, 14)
+ |> insert(bar, 42),
+ ) == 2
+}
+
+/// Extract all the values present in a given `Dict`.
///
/// ```aiken
-/// let pairs = [Pair("a", 100), Pair("b", 200), Pair("c", 300)]
+/// let result =
+/// dict.empty
+/// |> dict.insert("a", 14)
+/// |> dict.insert("b", 42)
+/// |> dict.insert("c", 1337)
+/// |> dict.values()
///
-/// dict.from_ascending_pairs_with(pairs, fn(x) { x <= 250 }) // fail
+/// result == [1337, 42]
/// ```
-pub fn from_ascending_pairs_with(
- xs: Pairs,
- predicate: fn(value) -> Bool,
-) -> Dict {
- let Void = check_ascending_pairs_with(xs, predicate)
- Dict { inner: xs }
+pub fn values(self: Dict) -> List {
+ do_values(self.inner)
}
-fn check_ascending_pairs_with(
- xs: Pairs,
- predicate: fn(value) -> Bool,
-) {
- when xs is {
- [] -> Void
- [Pair(_, v)] ->
- if predicate(v) {
- Void
- } else {
- fail @"value doesn't satisfy predicate"
- }
- [Pair(x0, v0), Pair(x1, _) as e, ..rest] ->
- if builtin.less_than_bytearray(x0, x1) {
- if predicate(v0) {
- check_ascending_pairs_with([e, ..rest], predicate)
- } else {
- fail @"value doesn't satisfy predicate"
- }
- } else {
- fail @"keys in pairs aren't in ascending order"
- }
+fn do_values(self: Pairs) -> List {
+ when self is {
+ [] ->
+ []
+ [Pair(_, v), ..rest] ->
+ [v, ..do_values(rest)]
}
}
-test bench_from_ascending_pairs() {
- let dict =
- from_ascending_pairs(
- [
- Pair("aaaa", 1),
- Pair("aaab", 9),
- Pair("aaba", 5),
- Pair("aabb", 13),
- Pair("abaa", 2),
- Pair("abab", 10),
- Pair("abba", 6),
- Pair("abbb", 14),
- Pair("baaa", 3),
- Pair("baab", 11),
- Pair("baba", 7),
- Pair("babb", 15),
- Pair("bbaa", 4),
- Pair("bbab", 12),
- Pair("bbba", 8),
- Pair("bbbb", 16),
- ],
- )
+test values_1() {
+ values(empty) == []
+}
- size(dict) == 16
+test values_2() {
+ values(
+ empty
+ |> insert(foo, 3)
+ |> insert(bar, 4),
+ ) == [4, 3]
}
-/// Get a value in the dict by its key.
+// ## Modifying
+
+/// Remove a key-value pair from the dictionary. If the key is not found, no changes are made.
///
/// ```aiken
/// let result =
-/// dict.new()
-/// |> dict.insert(key: "a", value: "Aiken")
-/// |> dict.get(key: "a")
+/// dict.empty
+/// |> dict.insert(key: "a", value: 100)
+/// |> dict.insert(key: "b", value: 200)
+/// |> dict.delete(key: "a")
+/// |> dict.to_pairs()
///
-/// result == Some("Aiken")
+/// result == [Pair("b", 200)]
/// ```
-pub fn get(self: Dict, key: ByteArray) -> Option {
- do_get(self.inner, key)
+pub fn delete(self: Dict, key: ByteArray) -> Dict {
+ Dict { inner: do_delete(self.inner, key) }
}
-fn do_get(self: Pairs, key k: ByteArray) -> Option {
+fn do_delete(
+ self: Pairs,
+ key k: ByteArray,
+) -> Pairs {
when self is {
- [] -> None
- [Pair(k2, v), ..rest] ->
+ [] ->
+ []
+ [Pair(k2, v2), ..rest] ->
if builtin.less_than_equals_bytearray(k, k2) {
if k == k2 {
- Some(v)
+ rest
} else {
- None
+ self
}
} else {
- do_get(rest, k)
+ [Pair(k2, v2), ..do_delete(rest, k)]
}
}
}
-test get_1() {
- get(new(), foo) == None
+test delete_1() {
+ delete(empty, foo) == empty
}
-test get_2() {
+test delete_2() {
let m =
- new()
- |> insert(foo, "Aiken")
- |> insert(bar, "awesome")
- get(m, key: foo) == Some("Aiken")
+ empty
+ |> insert(foo, 14)
+ delete(m, foo) == empty
}
-test get_3() {
+test delete_3() {
let m =
- new()
- |> insert(foo, "Aiken")
- |> insert(bar, "awesome")
- get(m, key: baz) == None
+ empty
+ |> insert(foo, 14)
+ delete(m, bar) == m
}
-test get_4() {
+test delete_4() {
let m =
- new()
- |> insert("aaa", "1")
- |> insert("bbb", "2")
- |> insert("ccc", "3")
- |> insert("ddd", "4")
- |> insert("eee", "5")
- |> insert("fff", "6")
- |> insert("ggg", "7")
- |> insert("hhh", "8")
- |> insert("iii", "9")
- |> insert("jjj", "10")
+ empty
+ |> insert(foo, 14)
+ |> insert(bar, 14)
+ !has_key(delete(m, foo), foo)
+}
- get(m, "bcd") == None
+test delete_5() {
+ let m =
+ empty
+ |> insert(foo, 14)
+ |> insert(bar, 14)
+ has_key(delete(m, bar), foo)
}
-test get_5() {
+test delete_6() {
let m =
- new()
- |> insert("aaa", "1")
- |> insert("bbb", "2")
- |> insert("ccc", "3")
- |> insert("ddd", "4")
- |> insert("eee", "5")
- |> insert("fff", "6")
- |> insert("ggg", "7")
- |> insert("hhh", "8")
- |> insert("iii", "9")
- |> insert("jjj", "10")
+ empty
+ |> insert("aaa", 1)
+ |> insert("bbb", 2)
+ |> insert("ccc", 3)
+ |> insert("ddd", 4)
+ |> insert("eee", 5)
+ |> insert("fff", 6)
+ |> insert("ggg", 7)
+ |> insert("hhh", 8)
+ |> insert("iii", 9)
+ |> insert("jjj", 10)
- get(m, "kkk") == None
+ delete(m, "bcd") == m
}
-/// Check if a key exists in the dictionary.
+/// Keep only the key-value pairs that pass the given predicate.
///
/// ```aiken
/// let result =
-/// dict.new()
-/// |> dict.insert(key: "a", value: "Aiken")
-/// |> dict.has_key("a")
+/// dict.empty
+/// |> dict.insert(key: "a", value: 100)
+/// |> dict.insert(key: "b", value: 200)
+/// |> dict.insert(key: "c", value: 300)
+/// |> dict.filter(fn(k, _v) { k != "a" })
+/// |> dict.to_pairs()
///
-/// result == True
+/// result == [Pair("b", 200), Pair("c", 300)]
/// ```
-pub fn has_key(self: Dict, key k: ByteArray) -> Bool {
- do_has_key(self.inner, k)
+pub fn filter(
+ self: Dict,
+ with: fn(ByteArray, value) -> Bool,
+) -> Dict {
+ Dict { inner: do_filter(self.inner, with) }
}
-fn do_has_key(self: Pairs, key k: ByteArray) -> Bool {
+fn do_filter(
+ self: Pairs,
+ with: fn(ByteArray, value) -> Bool,
+) -> Pairs {
when self is {
- [] -> False
- [Pair(k2, _), ..rest] ->
- if builtin.less_than_equals_bytearray(k, k2) {
- k == k2
+ [] ->
+ []
+ [Pair(k, v), ..rest] ->
+ if with(k, v) {
+ [Pair(k, v), ..do_filter(rest, with)]
} else {
- do_has_key(rest, k)
+ do_filter(rest, with)
}
}
}
-test has_key_1() {
- !has_key(new(), foo)
-}
-
-test has_key_2() {
- has_key(
- new()
- |> insert(foo, 14),
- foo,
- )
+test filter_1() {
+ filter(empty, fn(_, _) { True }) == empty
}
-test has_key_3() {
- !has_key(
- new()
- |> insert(foo, 14),
- bar,
- )
+test filter_2() {
+ let expected =
+ empty
+ |> insert(foo, 42)
+ filter(fixture_1, fn(_, v) { v > 14 }) == expected
}
-test has_key_4() {
- has_key(
- new()
- |> insert(foo, 14)
- |> insert(bar, 42),
- bar,
- )
+test filter_3() {
+ let expected =
+ empty
+ |> insert(bar, 14)
+ filter(fixture_1, fn(k, _) { k == bar }) == expected
}
/// Insert a value in the dictionary at a given key. If the key already exists, its value is **overridden**. If you need ways to combine keys together, use (`insert_with`)[#insert_with].
///
/// ```aiken
/// let result =
-/// dict.new()
+/// dict.empty
/// |> dict.insert(key: "a", value: 1)
/// |> dict.insert(key: "b", value: 2)
/// |> dict.insert(key: "a", value: 3)
@@ -682,20 +713,20 @@ fn do_insert(
test insert_1() {
let m1 =
- new()
+ empty
|> insert(foo, 42)
let m2 =
- new()
+ empty
|> insert(foo, 14)
insert(m1, foo, 14) == m2
}
test insert_2() {
let m1 =
- new()
+ empty
|> insert(foo, 42)
let m2 =
- new()
+ empty
|> insert(bar, 14)
insert(m1, bar, 14) == insert(m2, foo, 42)
}
@@ -709,7 +740,7 @@ test insert_2() {
/// fn (_k, a, b) { Some(a + b) }
///
/// let result =
-/// dict.new()
+/// dict.empty
/// |> dict.insert_with(key: "a", value: 1, with: sum)
/// |> dict.insert_with(key: "b", value: 2, with: sum)
/// |> dict.insert_with(key: "a", value: 3, with: sum)
@@ -733,7 +764,7 @@ test insert_with_1() {
fn(_k, a, b) { Some(a + b) }
let result =
- new()
+ empty
|> insert_with(key: "foo", value: 1, with: sum)
|> insert_with(key: "bar", value: 2, with: sum)
|> to_pairs()
@@ -746,7 +777,7 @@ test insert_with_2() {
fn(_k, a, b) { Some(a + b) }
let result =
- new()
+ empty
|> insert_with(key: "foo", value: 1, with: sum)
|> insert_with(key: "bar", value: 2, with: sum)
|> insert_with(key: "foo", value: 3, with: sum)
@@ -766,7 +797,7 @@ test insert_with_3() {
}
let result =
- new()
+ empty
|> insert_with(key: "foo", value: 1, with: with)
|> insert_with(key: "bar", value: 2, with: with)
|> insert_with(key: "foo", value: 3, with: with)
@@ -776,63 +807,11 @@ test insert_with_3() {
result == [Pair("foo", 1)]
}
-/// Efficiently checks whether a dictionary is empty.
-/// ```aiken
-/// dict.is_empty(dict.new()) == True
-/// ```
-pub fn is_empty(self: Dict) -> Bool {
- when self.inner is {
- [] -> True
- _ -> False
- }
-}
-
-test is_empty_1() {
- is_empty(new())
-}
-
-/// Extract all the keys present in a given `Dict`.
-///
-/// ```aiken
-/// let result =
-/// dict.new()
-/// |> dict.insert("a", 14)
-/// |> dict.insert("b", 42)
-/// |> dict.insert("a", 1337)
-/// |> dict.keys()
-///
-/// result == ["a", "b"]
-/// ```
-pub fn keys(self: Dict) -> List {
- do_keys(self.inner)
-}
-
-fn do_keys(self: Pairs) -> List {
- when self is {
- [] ->
- []
- [Pair(k, _), ..rest] ->
- [k, ..do_keys(rest)]
- }
-}
-
-test keys_1() {
- keys(new()) == []
-}
-
-test keys_2() {
- keys(
- new()
- |> insert(foo, 0)
- |> insert(bar, 0),
- ) == [bar, foo]
-}
-
/// Apply a function to all key-value pairs in a Dict.
///
/// ```aiken
/// let result =
-/// dict.new()
+/// dict.empty
/// |> dict.insert("a", 100)
/// |> dict.insert("b", 200)
/// |> dict.insert("c", 300)
@@ -859,83 +838,19 @@ fn do_map(
test map_1() {
let result =
- fixture_1()
+ fixture_1
|> map(with: fn(k, _) { k })
get(result, foo) == Some(foo)
}
test map_2() {
let result =
- fixture_1()
+ fixture_1
|> map(with: fn(_, v) { v + 1 })
- get(result, foo) == Some(43) && size(result) == size(fixture_1())
-}
-
-/// Get the inner list holding the dictionary data.
-///
-/// ```aiken
-/// let result =
-/// dict.new()
-/// |> dict.insert("a", 100)
-/// |> dict.insert("b", 200)
-/// |> dict.insert("c", 300)
-/// |> dict.to_pairs()
-///
-/// result == [Pair("a", 100), Pair("b", 200), Pair("c", 300)]
-/// ```
-pub fn to_pairs(self: Dict) -> Pairs {
- self.inner
-}
-
-test to_list_1() {
- to_pairs(new()) == []
-}
-
-test to_list_2() {
- to_pairs(fixture_1()) == [Pair(bar, 14), Pair(foo, 42)]
-}
-
-/// Return the number of key-value pairs in the dictionary.
-///
-/// ```aiken
-/// let result =
-/// dict.new()
-/// |> dict.insert("a", 100)
-/// |> dict.insert("b", 200)
-/// |> dict.insert("c", 300)
-/// |> dict.size()
-///
-/// result == 3
-/// ```
-pub fn size(self: Dict) -> Int {
- do_size(self.inner)
-}
-
-fn do_size(self: Pairs) -> Int {
- when self is {
- [] -> 0
- [_, ..rest] -> 1 + do_size(rest)
- }
-}
-
-test size_1() {
- size(new()) == 0
+ get(result, foo) == Some(43) && size(result) == size(fixture_1)
}
-test size_2() {
- size(
- new()
- |> insert(foo, 14),
- ) == 1
-}
-
-test size_3() {
- size(
- new()
- |> insert(foo, 14)
- |> insert(bar, 42),
- ) == 2
-}
+// ## Combining
/// Combine two dictionaries. If the same key exist in both the left and
/// right dictionary, values from the left are preferred (i.e. left-biaised).
@@ -967,19 +882,19 @@ fn do_union(
}
test union_1() {
- union(fixture_1(), new()) == fixture_1()
+ union(fixture_1, empty) == fixture_1
}
test union_2() {
- union(new(), fixture_1()) == fixture_1()
+ union(empty, fixture_1) == fixture_1
}
test union_3() {
let left =
- new()
+ empty
|> insert(foo, 14)
let right =
- new()
+ empty
|> insert(bar, 42)
|> insert(baz, 1337)
union(left, right) == from_pairs(
@@ -989,10 +904,10 @@ test union_3() {
test union_4() {
let left =
- new()
+ empty
|> insert(foo, 14)
let right =
- new()
+ empty
|> insert(bar, 42)
|> insert(foo, 1337)
union(left, right) == from_pairs([Pair(foo, 14), Pair(bar, 42)])
@@ -1066,11 +981,11 @@ fn do_insert_with(
test union_with_1() {
let left =
- new()
+ empty
|> insert(foo, 14)
let right =
- new()
+ empty
|> insert(bar, 42)
|> insert(foo, 1337)
@@ -1079,39 +994,108 @@ test union_with_1() {
result == from_pairs([Pair(foo, 1351), Pair(bar, 42)])
}
-/// Extract all the values present in a given `Dict`.
+// ## Transforming
+
+/// Fold over the key-value pairs in a dictionary. The fold direction follows keys
+/// in ascending order and is done from left-to-right.
///
/// ```aiken
/// let result =
-/// dict.new()
-/// |> dict.insert("a", 14)
-/// |> dict.insert("b", 42)
-/// |> dict.insert("c", 1337)
-/// |> dict.values()
+/// dict.empty
+/// |> dict.insert(key: "a", value: 100)
+/// |> dict.insert(key: "b", value: 200)
+/// |> dict.insert(key: "c", value: 300)
+/// |> dict.foldl(0, fn(_k, v, r) { v + r })
///
-/// result == [1337, 42]
+/// result == 600
/// ```
-pub fn values(self: Dict) -> List {
- do_values(self.inner)
+pub fn foldl(
+ self: Dict,
+ zero: result,
+ with: fn(ByteArray, value, result) -> result,
+) -> result {
+ do_foldl(self.inner, zero, with)
}
-fn do_values(self: Pairs) -> List {
+fn do_foldl(
+ self: Pairs,
+ zero: result,
+ with: fn(ByteArray, value, result) -> result,
+) -> result {
when self is {
- [] ->
- []
- [Pair(_, v), ..rest] ->
- [v, ..do_values(rest)]
+ [] -> zero
+ [Pair(k, v), ..rest] -> do_foldl(rest, with(k, v, zero), with)
}
}
-test values_1() {
- values(new()) == []
+test fold_1() {
+ foldl(empty, 14, fn(_, _, _) { 42 }) == 14
}
-test values_2() {
- values(
- new()
- |> insert(foo, 3)
- |> insert(bar, 4),
- ) == [4, 3]
+test fold_2() {
+ foldl(fixture_1, zero: 0, with: fn(_, v, total) { v + total }) == 56
+}
+
+/// Fold over the key-value pairs in a dictionary. The fold direction follows keys
+/// in ascending order and is done from right-to-left.
+///
+/// ```aiken
+/// let result =
+/// dict.empty
+/// |> dict.insert(key: "a", value: 100)
+/// |> dict.insert(key: "b", value: 200)
+/// |> dict.insert(key: "c", value: 300)
+/// |> dict.foldr(0, fn(_k, v, r) { v + r })
+///
+/// result == 600
+/// ```
+pub fn foldr(
+ self: Dict,
+ zero: result,
+ with: fn(ByteArray, value, result) -> result,
+) -> result {
+ do_foldr(self.inner, zero, with)
+}
+
+fn do_foldr(
+ self: Pairs,
+ zero: result,
+ with: fn(ByteArray, value, result) -> result,
+) -> result {
+ when self is {
+ [] -> zero
+ [Pair(k, v), ..rest] -> with(k, v, do_foldr(rest, zero, with))
+ }
+}
+
+test foldr_1() {
+ foldr(empty, 14, fn(_, _, _) { 42 }) == 14
+}
+
+test foldr_2() {
+ foldr(fixture_1, zero: 0, with: fn(_, v, total) { v + total }) == 56
+}
+
+/// Get the inner list holding the dictionary data.
+///
+/// ```aiken
+/// let result =
+/// dict.empty
+/// |> dict.insert("a", 100)
+/// |> dict.insert("b", 200)
+/// |> dict.insert("c", 300)
+/// |> dict.to_pairs()
+///
+/// result == [Pair("a", 100), Pair("b", 200), Pair("c", 300)]
+/// ```
+pub fn to_pairs(self: Dict) -> Pairs {
+ self.inner
+}
+
+test to_list_1() {
+ to_pairs(empty) == []
+}
+
+test to_list_2() {
+ to_pairs(fixture_1) == [Pair(bar, 14), Pair(foo, 42)]
}
diff --git a/build/packages/aiken-lang-stdlib/lib/aiken/list.ak b/build/packages/aiken-lang-stdlib/lib/aiken/collection/list.ak
similarity index 98%
rename from build/packages/aiken-lang-stdlib/lib/aiken/list.ak
rename to build/packages/aiken-lang-stdlib/lib/aiken/collection/list.ak
index b8bb4b8..d414a34 100644
--- a/build/packages/aiken-lang-stdlib/lib/aiken/list.ak
+++ b/build/packages/aiken-lang-stdlib/lib/aiken/collection/list.ak
@@ -1,6 +1,71 @@
use aiken/builtin
-use aiken/bytearray
-use aiken/int
+use aiken/primitive/bytearray
+use aiken/primitive/int
+
+// ## Constructing
+
+/// Add an element in front of the list. Sometimes useful when combined with
+/// other functions.
+///
+/// ```aiken
+/// list.push([2, 3], 1) == [1, ..[2, 3]] == [1, 2, 3]
+/// ```
+pub fn push(self: List, elem: a) -> List {
+ [elem, ..self]
+}
+
+test push_1() {
+ push([], 0) == [0]
+}
+
+test push_2() {
+ push([2, 3], 1) == [1, 2, 3]
+}
+
+/// Construct a list of a integer from a given range.
+///
+/// ```aiken
+/// list.range(0, 3) == [0, 1, 2, 3]
+/// list.range(-1, 1) == [-1, 0, 1]
+/// ```
+pub fn range(from: Int, to: Int) -> List {
+ if from > to {
+ []
+ } else {
+ [from, ..range(from + 1, to)]
+ }
+}
+
+test range_1() {
+ range(0, 3) == [0, 1, 2, 3]
+}
+
+test range_2() {
+ range(-1, 1) == [-1, 0, 1]
+}
+
+/// Construct a list filled with n copies of a value.
+///
+/// ```aiken
+/// list.repeat("na", 3) == ["na", "na", "na"]
+/// ```
+pub fn repeat(elem: a, n_times: Int) -> List {
+ if n_times <= 0 {
+ []
+ } else {
+ [elem, ..repeat(elem, n_times - 1)]
+ }
+}
+
+test repeat_1() {
+ repeat(42, 0) == []
+}
+
+test repeat_2() {
+ repeat(14, 3) == [14, 14, 14]
+}
+
+// ## Inspecting
/// Determine if all elements of the list satisfy the given predicate.
///
@@ -59,6 +124,44 @@ test any_3() {
any([], fn(n) { n == 42 }) == False
}
+/// Return Some(item) at the index or None if the index is out of range. The index is 0-based.
+///
+/// ```aiken
+/// list.at([1, 2, 3], 1) == Some(2)
+/// list.at([1, 2, 3], 42) == None
+/// ```
+pub fn at(self: List, index: Int) -> Option {
+ when self is {
+ [] -> None
+ [x, ..xs] ->
+ if index == 0 {
+ Some(x)
+ } else {
+ at(xs, index - 1)
+ }
+ }
+}
+
+test at_1() {
+ at([1, 2, 3], -1) == None
+}
+
+test at_2() {
+ at([], 0) == None
+}
+
+test at_3() {
+ at([1, 2, 3], 3) == None
+}
+
+test at_4() {
+ at([1], 0) == Some(1)
+}
+
+test at_5() {
+ at([1, 2, 3], 2) == Some(3)
+}
+
/// Count how many items in the list satisfy the given predicate.
///
/// ```aiken
@@ -97,134 +200,233 @@ test count_none() {
count([1, 2, 3], fn(a) { a > 5 }) == 0
}
-/// Return Some(item) at the index or None if the index is out of range. The index is 0-based.
+/// Find the first element satisfying the given predicate, if any.
///
/// ```aiken
-/// list.at([1, 2, 3], 1) == Some(2)
-/// list.at([1, 2, 3], 42) == None
+/// list.find([1, 2, 3], fn(x) { x == 2 }) == Some(2)
+/// list.find([4, 5, 6], fn(x) { x == 2 }) == None
/// ```
-pub fn at(self: List, index: Int) -> Option {
+pub fn find(self: List, predicate: fn(a) -> Bool) -> Option {
when self is {
[] -> None
[x, ..xs] ->
- if index == 0 {
+ if predicate(x) {
Some(x)
} else {
- at(xs, index - 1)
+ find(xs, predicate)
}
}
}
-test at_1() {
- at([1, 2, 3], -1) == None
-}
-
-test at_2() {
- at([], 0) == None
-}
-
-test at_3() {
- at([1, 2, 3], 3) == None
+test find_1() {
+ find([1, 2, 3], fn(x) { x == 1 }) == Some(1)
}
-test at_4() {
- at([1], 0) == Some(1)
+test find_2() {
+ find([1, 2, 3], fn(x) { x > 42 }) == None
}
-test at_5() {
- at([1, 2, 3], 2) == Some(3)
+test find_3() {
+ find([], fn(_) { True }) == None
}
-/// Merge two lists together.
+/// Figures out whether a list contain the given element.
///
/// ```aiken
-/// list.concat([], []) == []
-/// list.concat([], [1, 2, 3]) == [1, 2, 3]
-/// list.concat([1, 2, 3], [4, 5, 6]) == [1, 2, 3, 4, 5, 6]
+/// list.has([1, 2, 3], 2) == True
+/// list.has([1, 2, 3], 14) == False
+/// list.has([], 14) == False
/// ```
-pub fn concat(left: List, right: List) -> List {
- when left is {
- [] -> right
+pub fn has(self: List, elem: a) -> Bool {
+ when self is {
+ [] -> False
[x, ..xs] ->
- [x, ..concat(xs, right)]
+ if x == elem {
+ True
+ } else {
+ has(xs, elem)
+ }
}
}
-test concat_1() {
- concat([1, 2, 3], [4, 5, 6]) == [1, 2, 3, 4, 5, 6]
+test has_1() {
+ has([1, 2, 3], 1) == True
}
-test concat_2() {
- concat([1, 2, 3], []) == [1, 2, 3]
+test has_2() {
+ has([1, 2, 3], 14) == False
}
-test concat_3() {
- concat([], [1, 2, 3]) == [1, 2, 3]
+test has_3() {
+ has([], 14) == False
}
-/// Remove the first occurrence of the given element from the list.
+/// Get the first element of a list
///
/// ```aiken
-/// list.delete([1, 2, 3, 1], 1) == [2, 3, 1]
-/// list.delete([1, 2, 3], 14) == [1, 2, 3]
+/// list.head([1, 2, 3]) == Some(1)
+/// list.head([]) == None
/// ```
-pub fn delete(self: List, elem: a) -> List {
+pub fn head(self: List) -> Option {
when self is {
- [] ->
- []
- [x, ..xs] ->
- if x == elem {
- xs
- } else {
- [x, ..delete(xs, elem)]
- }
+ [] -> None
+ _ -> Some(builtin.head_list(self))
}
}
-test delete_1() {
- delete([], 42) == []
+test head_1() {
+ head([1, 2, 3]) == Some(1)
}
-test delete_2() {
- delete([1, 2, 3, 1], 1) == [2, 3, 1]
+test head_2() {
+ head([]) == None
}
-test delete_3() {
- delete([1, 2, 3], 14) == [1, 2, 3]
+/// Checks whether a list is empty.
+///
+/// ```aiken
+/// list.is_empty([]) == True
+/// list.is_empty([1, 2, 3]) == False
+/// ```
+pub fn is_empty(self: List) -> Bool {
+ when self is {
+ [] -> True
+ _ -> False
+ }
}
-test delete_4() {
- delete([2], 2) == []
+test is_empty_1() {
+ is_empty([]) == True
}
-/// Remove the first occurrence of each element of the second list from the first one.
+test is_empty_2() {
+ is_empty([1, 2, 3]) == False
+}
+
+/// Gets the index of an element of a list, if any. Otherwise, returns None.
///
+/// ```aiken
+/// list.index_of([1, 5, 2], 2) == Some(2)
+/// list.index_of([1, 7, 3], 4) == None
+/// list.index_of([1, 0, 9, 6], 6) == 3
+/// list.index_of([], 6) == None
/// ```
-/// list.difference(["h", "e", "l", "l", "o"], ["l", "e", "l"]) == ["h", "o"]
-/// list.difference([1, 2, 3, 4, 5], [1, 1, 2]) == [3, 4, 5]
-/// list.difference([1, 2, 3], []) == [1, 2, 3]
-/// ```
-pub fn difference(self: List, with: List) -> List {
- when with is {
- [] -> self
- [x, ..xs] -> difference(delete(self, x), xs)
+pub fn index_of(self: List, elem: a) -> Option {
+ do_index_of(self, elem, 0)
+}
+
+fn do_index_of(self: List, elem: a, i: Int) -> Option {
+ when self is {
+ [] -> None
+ [x, ..xs] ->
+ if x == elem {
+ Some(i)
+ } else {
+ do_index_of(xs, elem, i + 1)
+ }
}
}
-test difference_1() {
- difference(["h", "e", "l", "l", "o"], ["l", "e", "l"]) == ["h", "o"]
+test index_of_1() {
+ index_of([1, 5, 2], 2) == Some(2)
}
-test difference_2() {
- difference([1, 2, 3, 4, 5], [1, 1, 2]) == [3, 4, 5]
+test index_of_2() {
+ index_of([1, 7, 3], 4) == None
}
-test difference_3() {
- difference([1, 2, 3], []) == [1, 2, 3]
+test index_of_3() {
+ index_of([1, 0, 9, 6], 6) == Some(3)
}
-test difference_4() {
- difference([], [1, 2, 3]) == []
+test index_of_4() {
+ index_of([], 6) == None
+}
+
+/// Get the last in the given list, if any.
+///
+/// ```aiken
+/// list.last([]) == None
+/// list.last([1, 2, 3]) == Some(3)
+/// ```
+pub fn last(self: List) -> Option {
+ when self is {
+ [] -> None
+ [x] -> Some(x)
+ [_, ..xs] -> last(xs)
+ }
+}
+
+test last_1() {
+ last([]) == None
+}
+
+test last_2() {
+ last([1]) == Some(1)
+}
+
+test last_3() {
+ last([1, 2, 3, 4]) == Some(4)
+}
+
+/// Get the number of elements in the given list.
+///
+/// ```aiken
+/// list.length([]) == 0
+/// list.length([1, 2, 3]) == 3
+/// ```
+pub fn length(self: List) -> Int {
+ when self is {
+ [] -> 0
+ [_, ..xs] -> 1 + length(xs)
+ }
+}
+
+test length_1() {
+ length([]) == 0
+}
+
+test length_2() {
+ length([1, 2, 3]) == 3
+}
+
+// ## Modifying
+
+// ### Extracting
+
+/// Remove the first occurrence of the given element from the list.
+///
+/// ```aiken
+/// list.delete([1, 2, 3, 1], 1) == [2, 3, 1]
+/// list.delete([1, 2, 3], 14) == [1, 2, 3]
+/// ```
+pub fn delete(self: List, elem: a) -> List {
+ when self is {
+ [] ->
+ []
+ [x, ..xs] ->
+ if x == elem {
+ xs
+ } else {
+ [x, ..delete(xs, elem)]
+ }
+ }
+}
+
+test delete_1() {
+ delete([], 42) == []
+}
+
+test delete_2() {
+ delete([1, 2, 3, 1], 1) == [2, 3, 1]
+}
+
+test delete_3() {
+ delete([1, 2, 3], 14) == [1, 2, 3]
+}
+
+test delete_4() {
+ delete([2], 2) == []
}
/// Drop the first `n` elements of a list.
@@ -384,113 +586,6 @@ test filter_map_2() {
) == [3, 9, 15]
}
-/// Find the first element satisfying the given predicate, if any.
-///
-/// ```aiken
-/// list.find([1, 2, 3], fn(x) { x == 2 }) == Some(2)
-/// list.find([4, 5, 6], fn(x) { x == 2 }) == None
-/// ```
-pub fn find(self: List, predicate: fn(a) -> Bool) -> Option {
- when self is {
- [] -> None
- [x, ..xs] ->
- if predicate(x) {
- Some(x)
- } else {
- find(xs, predicate)
- }
- }
-}
-
-test find_1() {
- find([1, 2, 3], fn(x) { x == 1 }) == Some(1)
-}
-
-test find_2() {
- find([1, 2, 3], fn(x) { x > 42 }) == None
-}
-
-test find_3() {
- find([], fn(_) { True }) == None
-}
-
-/// Map elements of a list into a new list and flatten the result.
-///
-/// ```aiken
-/// list.flat_map([1, 2, 3], fn(a) { [a, 2*a] }) == [1, 2, 2, 4, 3, 6]
-/// ```
-pub fn flat_map(self: List, with: fn(a) -> List) -> List {
- foldr(self, [], fn(x, xs) { concat(with(x), xs) })
-}
-
-test flat_map_1() {
- flat_map([], fn(a) { [a] }) == []
-}
-
-test flat_map_2() {
- flat_map([1, 2, 3], fn(a) { [a, a] }) == [1, 1, 2, 2, 3, 3]
-}
-
-/// Reduce a list from left to right.
-///
-/// ```aiken
-/// list.foldl([1, 2, 3], 0, fn(n, total) { n + total }) == 6
-/// list.foldl([1, 2, 3], [], fn(x, xs) { [x, ..xs] }) == [3, 2, 1]
-/// ```
-pub fn foldl(self: List, zero: b, with: fn(a, b) -> b) -> b {
- when self is {
- [] -> zero
- [x, ..xs] -> foldl(xs, with(x, zero), with)
- }
-}
-
-test foldl_1() {
- foldl([], 0, fn(_, _) { 1 }) == 0
-}
-
-test foldl_2() {
- foldl([1, 2, 3, 4, 5], 0, fn(n, total) { n + total }) == 15
-}
-
-test foldl_3() {
- foldl([1, 2, 3, 4], [], fn(x, xs) { [x, ..xs] }) == [4, 3, 2, 1]
-}
-
-/// Reduce a list from right to left.
-///
-/// ```aiken
-/// list.foldr([1, 2, 3], 0, fn(n, total) { n + total }) == 6
-/// list.foldr([1, 2, 3], [], fn(x, xs) { [x, ..xs] }) == [1, 2, 3]
-/// ```
-pub fn foldr(self: List, zero: b, with: fn(a, b) -> b) -> b {
- when self is {
- [] -> zero
- [x, ..xs] -> with(x, foldr(xs, zero, with))
- }
-}
-
-test foldr_1() {
- foldr([1, 2, 3, 4, 5], 0, fn(n, total) { n + total }) == 15
-}
-
-test foldr_2() {
- foldr(
- [1, 2, 3],
- "",
- fn(n, _str) {
- if builtin.mod_integer(n, 2) == 0 {
- "foo"
- } else {
- "bar"
- }
- },
- ) == "bar"
-}
-
-test foldr_3() {
- foldr([1, 2, 3, 4], [], fn(x, xs) { [x, ..xs] }) == [1, 2, 3, 4]
-}
-
/// Return all elements except the last one.
///
/// ```aiken
@@ -526,727 +621,481 @@ test init_3() {
init([1, 2, 3, 4]) == Some([1, 2, 3])
}
-/// Figures out whether a list contain the given element.
+/// Returns a tuple with all elements that satisfy the predicate at first
+/// element, and the rest as second element.
///
/// ```aiken
-/// list.has([1, 2, 3], 2) == True
-/// list.has([1, 2, 3], 14) == False
-/// list.has([], 14) == False
+/// list.partition([1, 2, 3, 4], fn(x) { x % 2 == 0 }) == ([2, 4], [1, 3])
/// ```
-pub fn has(self: List, elem: a) -> Bool {
+pub fn partition(self: List, predicate: fn(a) -> Bool) -> (List, List) {
when self is {
- [] -> False
- [x, ..xs] ->
- if x == elem {
- True
+ [] -> ([], [])
+ [x, ..xs] -> {
+ let (left, right) = partition(xs, predicate)
+ if predicate(x) {
+ ([x, ..left], right)
} else {
- has(xs, elem)
+ (left, [x, ..right])
}
+ }
}
}
-test has_1() {
- has([1, 2, 3], 1) == True
+test partition_1() {
+ partition([], fn(x) { x > 2 }) == ([], [])
}
-test has_2() {
- has([1, 2, 3], 14) == False
+test partition_2() {
+ let xs =
+ [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
+ partition(xs, fn(x) { x > 5 }) == ([10, 9, 8, 7, 6], [5, 4, 3, 2, 1])
}
-test has_3() {
- has([], 14) == False
+test partition_3() {
+ let xs =
+ [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
+ partition(xs, fn(x) { x == 42 }) == ([], xs)
}
-/// Gets the index of an element of a list, if any. Otherwise, returns None.
-///
-/// ```aiken
-/// list.index_of([1, 5, 2], 2) == Some(2)
-/// list.index_of([1, 7, 3], 4) == None
-/// list.index_of([1, 0, 9, 6], 6) == 3
-/// list.index_of([], 6) == None
-/// ```
-pub fn index_of(self: List, elem: a) -> Option {
- do_index_of(self, elem, 0)
+test partition_4() {
+ let xs =
+ [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
+ partition(xs, fn(x) { x < 42 }) == (xs, [])
}
-fn do_index_of(self: List, elem: a, i: Int) -> Option {
- when self is {
- [] -> None
- [x, ..xs] ->
- if x == elem {
- Some(i)
- } else {
- do_index_of(xs, elem, i + 1)
- }
- }
+test partition_5() {
+ partition([1, 2, 3, 4], fn(x) { x % 2 == 0 }) == ([2, 4], [1, 3])
}
-test index_of_1() {
- index_of([1, 5, 2], 2) == Some(2)
-}
-
-test index_of_2() {
- index_of([1, 7, 3], 4) == None
-}
-
-test index_of_3() {
- index_of([1, 0, 9, 6], 6) == Some(3)
-}
-
-test index_of_4() {
- index_of([], 6) == None
-}
-
-/// Get the first element of a list
-///
-/// ```aiken
-/// list.head([1, 2, 3]) == Some(1)
-/// list.head([]) == None
-/// ```
-pub fn head(self: List) -> Option {
- when self is {
- [] -> None
- _ -> Some(builtin.head_list(self))
- }
-}
-
-test head_1() {
- head([1, 2, 3]) == Some(1)
-}
-
-test head_2() {
- head([]) == None
-}
-
-/// Like [`foldr`](#foldr), but also provides the position (0-based) of the elements when iterating.
-///
-/// ```aiken
-/// let group = fn(i, x, xs) { [(i, x), ..xs] }
-/// list.indexed_foldr(["a", "b", "c"], [], group) == [
-/// (0, "a"),
-/// (1, "b"),
-/// (2, "c")
-/// ]
-/// ```
-pub fn indexed_foldr(
- self: List,
- zero: result,
- with: fn(Int, a, result) -> result,
-) -> result {
- do_indexed_foldr(0, self, zero, with)
-}
-
-fn do_indexed_foldr(
- n: Int,
- self: List,
- zero: result,
- with: fn(Int, a, result) -> result,
-) -> result {
- when self is {
- [] -> zero
- [x, ..xs] -> with(n, x, do_indexed_foldr(n + 1, xs, zero, with))
- }
-}
-
-test indexed_foldr_1() {
- indexed_foldr([], 0, fn(i, x, xs) { i + x + xs }) == 0
-}
-
-test indexed_foldr_2() {
- let letters =
- ["a", "b", "c"]
- indexed_foldr(letters, [], fn(i, x, xs) { [(i, x), ..xs] }) == [
- (0, "a"),
- (1, "b"),
- (2, "c"),
- ]
-}
-
-/// List [`map`](#map) but provides the position (0-based) of the elements while iterating.
+/// Extract a sublist from the given list using 0-based indexes. Negative
+/// indexes wrap over, so `-1` refers to the last element of the list.
///
/// ```aiken
-/// list.indexed_map([1, 2, 3], fn(i, x) { i + x }) == [1, 3, 5]
+/// list.slice([1, 2, 3, 4, 5, 6], from: 2, to: 4) == [3, 4, 5]
+/// list.slice([1, 2, 3, 4, 5, 6], from: -2, to: -1) == [5, 6]
+/// list.slice([1, 2, 3, 4, 5, 6], from: 1, to: -1) == [2, 3, 4, 5, 6]
/// ```
-pub fn indexed_map(self: List, with: fn(Int, a) -> result) -> List {
- do_indexed_map(0, self, with)
-}
-
-fn do_indexed_map(
- n: Int,
- self: List,
- with: fn(Int, a) -> result,
-) -> List {
- when self is {
- [] ->
- []
- [x, ..xs] ->
- [with(n, x), ..do_indexed_map(n + 1, xs, with)]
- }
-}
-
-test indexed_map_1() {
- indexed_map([], fn(i, _n) { i }) == []
-}
+pub fn slice(self: List, from: Int, to: Int) -> List {
+ let (i, l) =
+ if from >= 0 {
+ (from, None)
+ } else {
+ let l = length(self)
+ (l + from, Some(l))
+ }
-test indexed_map_2() {
- indexed_map(
- [4, 8, 13, 2],
- fn(i, n) {
- if n == 8 {
- n
- } else {
- i
+ let j =
+ if to >= 0 {
+ to - i + 1
+ } else {
+ when l is {
+ Some(l) -> l + to - i + 1
+ None -> length(self) + to - i + 1
}
- },
- ) == [0, 8, 2, 3]
-}
-
-/// Checks whether a list is empty.
-///
-/// ```aiken
-/// list.is_empty([]) == True
-/// list.is_empty([1, 2, 3]) == False
-/// ```
-pub fn is_empty(self: List) -> Bool {
- when self is {
- [] -> True
- _ -> False
- }
-}
-
-test is_empty_1() {
- is_empty([]) == True
-}
-
-test is_empty_2() {
- is_empty([1, 2, 3]) == False
-}
-
-/// Get the last in the given list, if any.
-///
-/// ```aiken
-/// list.last([]) == None
-/// list.last([1, 2, 3]) == Some(3)
-/// ```
-pub fn last(self: List) -> Option {
- when self is {
- [] -> None
- [x] -> Some(x)
- [_, ..xs] -> last(xs)
- }
-}
-
-test last_1() {
- last([]) == None
-}
-
-test last_2() {
- last([1]) == Some(1)
-}
-
-test last_3() {
- last([1, 2, 3, 4]) == Some(4)
-}
-
-/// Get the number of elements in the given list.
-///
-/// ```aiken
-/// list.length([]) == 0
-/// list.length([1, 2, 3]) == 3
-/// ```
-pub fn length(self: List) -> Int {
- when self is {
- [] -> 0
- [_, ..xs] -> 1 + length(xs)
- }
-}
-
-test length_1() {
- length([]) == 0
-}
-
-test length_2() {
- length([1, 2, 3]) == 3
-}
+ }
-/// Apply a function to each element of a list.
-///
-/// ```aiken
-/// list.map([1, 2, 3, 4], fn(n) { n + 1 }) == [2, 3, 4, 5]
-/// ```
-pub fn map(self: List, with: fn(a) -> result) -> List {
- when self is {
- [] ->
- []
- [x, ..xs] ->
- [with(x), ..map(xs, with)]
- }
+ self
+ |> drop(i)
+ |> take(j)
}
-test map_1() {
- map([], fn(n) { n + 1 }) == []
+test slice_1() {
+ slice([1, 2, 3], 0, 2) == [1, 2, 3]
}
-test map_2() {
- map([1, 2, 3, 4], fn(n) { n + 1 }) == [2, 3, 4, 5]
+test slice_2() {
+ slice([1, 2, 3, 4, 5, 6], from: 2, to: 4) == [3, 4, 5]
}
-/// Apply a function of two arguments, combining elements from two lists.
-///
-/// Note: if one list is longer, the extra elements are dropped.
-///
-/// ```aiken
-/// list.map2([1, 2, 3], [1, 2], fn(a, b) { a + b }) == [2, 4]
-/// ```
-pub fn map2(
- self: List,
- bs: List,
- with: fn(a, b) -> result,
-) -> List {
- when self is {
- [] ->
- []
- [x, ..xs] ->
- when bs is {
- [] ->
- []
- [y, ..ys] ->
- [with(x, y), ..map2(xs, ys, with)]
- }
- }
+test slice_3() {
+ slice([1, 2, 3, 4, 5, 6], from: -2, to: -1) == [5, 6]
}
-test map2_1() {
- map2([], [1, 2, 3], fn(a, b) { a + b }) == []
+test slice_4() {
+ slice([1, 2, 3, 4, 5, 6], from: 1, to: -1) == [2, 3, 4, 5, 6]
}
-test map2_2() {
- map2([1, 2, 3], [1, 2], fn(a, b) { a + b }) == [2, 4]
+test slice_5() {
+ slice([1, 2, 3, 4, 5, 6], from: -4, to: -3) == [3, 4]
}
-test map2_3() {
- map2([42], [1, 2, 3], fn(_a, b) { Some(b) }) == [Some(1)]
+test slice_6() {
+ slice([1, 2, 3, 4, 5, 6], from: -2, to: 1) == []
}
-/// Apply a function of three arguments, combining elements from three lists.
+/// Cut a list in two, such that the first list contains the given number of /
+/// elements and the second list contains the rest.
///
-/// Note: if one list is longer, the extra elements are dropped.
+/// Fundamentally equivalent to (but more efficient):
///
/// ```aiken
-/// list.map3([1, 2, 3], [1, 2], [1, 2, 3], fn(a, b, c) { a + b + c }) == [3, 6]
+/// // span(xs, n) == (take(xs, n), drop(xs, n))
+/// span([1, 2, 3, 4, 5], 3) == ([1, 2, 3], [4, 5])
/// ```
-pub fn map3(
- self: List,
- bs: List,
- cs: List,
- with: fn(a, b, c) -> result,
-) -> List {
+pub fn span(self: List, n: Int) -> (List, List) {
when self is {
- [] ->
- []
+ [] -> ([], [])
[x, ..xs] ->
- when bs is {
- [] ->
- []
- [y, ..ys] ->
- when cs is {
- [] ->
- []
- [z, ..zs] ->
- [with(x, y, z), ..map3(xs, ys, zs, with)]
- }
+ if n <= 0 {
+ ([], self)
+ } else {
+ let (left, right) = span(xs, n - 1)
+ ([x, ..left], right)
}
}
}
-test map3_1() {
- map3([], [], [1, 2, 3], fn(a, b, c) { a + b + c }) == []
-}
-
-test map3_2() {
- map3([1, 2, 3], [1, 2], [1, 2, 3], fn(a, b, c) { a + b + c }) == [3, 6]
+test span_1() {
+ span([], 2) == ([], [])
}
-/// Add an element in front of the list. Sometimes useful when combined with
-/// other functions.
-///
-/// ```aiken
-/// list.push([2, 3], 1) == [1, ..[2, 3]] == [1, 2, 3]
-/// ```
-pub fn push(self: List, elem: a) -> List {
- [elem, ..self]
+test span_2() {
+ span([1, 2, 3], 2) == ([1, 2], [3])
}
-test push_1() {
- push([], 0) == [0]
+test span_3() {
+ span([1, 2, 3], -1) == ([], [1, 2, 3])
}
-test push_2() {
- push([2, 3], 1) == [1, 2, 3]
+test span_4() {
+ span([1, 2, 3], 42) == ([1, 2, 3], [])
}
-/// Construct a list of a integer from a given range.
+/// Get elements of a list after the first one, if any.
///
/// ```aiken
-/// list.range(0, 3) == [0, 1, 2, 3]
-/// list.range(-1, 1) == [-1, 0, 1]
+/// list.tail([]) == None
+/// list.tail([1, 2, 3]) == Some([2, 3])
/// ```
-pub fn range(from: Int, to: Int) -> List {
- if from > to {
- []
- } else {
- [from, ..range(from + 1, to)]
+pub fn tail(self: List) -> Option> {
+ when self is {
+ [] -> None
+ [_, ..xs] -> Some(xs)
}
}
-test range_1() {
- range(0, 3) == [0, 1, 2, 3]
+test tail_1() {
+ tail([1, 2, 3]) == Some([2, 3])
}
-test range_2() {
- range(-1, 1) == [-1, 0, 1]
+test tail_2() {
+ tail([]) == None
}
-/// Construct a list filled with n copies of a value.
+/// Get the first `n` elements of a list.
///
/// ```aiken
-/// list.repeat("na", 3) == ["na", "na", "na"]
+/// list.take([1, 2, 3], 2) == [1, 2]
+/// list.take([1, 2, 3], 14) == [1, 2, 3]
/// ```
-pub fn repeat(elem: a, n_times: Int) -> List {
- if n_times <= 0 {
+pub fn take(self: List, n: Int) -> List {
+ if n <= 0 {
[]
} else {
- [elem, ..repeat(elem, n_times - 1)]
+ when self is {
+ [] ->
+ []
+ [x, ..xs] ->
+ [x, ..take(xs, n - 1)]
+ }
}
}
-test repeat_1() {
- repeat(42, 0) == []
-}
-
-test repeat_2() {
- repeat(14, 3) == [14, 14, 14]
-}
-
-/// Return the list with its elements in the reserve order.
-///
-/// ```aiken
-/// list.reverse([1, 2, 3]) == [3, 2, 1]
-/// ```
-pub fn reverse(self: List) -> List {
- foldl(self, [], fn(x, xs) { [x, ..xs] })
-}
-
-test reverse_1() {
- reverse([]) == []
+test take_1() {
+ take([], 42) == []
}
-test reverse_2() {
- reverse([1, 2, 3]) == [3, 2, 1]
+test take_2() {
+ take([1, 2, 3], 2) == [1, 2]
}
-/// Returns a tuple with all elements that satisfy the predicate at first
-/// element, and the rest as second element.
+/// Returns the longest prefix of the given list where all elements satisfy the predicate.
///
/// ```aiken
-/// list.partition([1, 2, 3, 4], fn(x) { x % 2 == 0 }) == ([2, 4], [1, 3])
+/// list.take_while([1, 2, 3], fn(x) { x > 2 }) == []
+/// list.take_while([1, 2, 3], fn(x) { x < 2 }) == [1]
/// ```
-pub fn partition(self: List, predicate: fn(a) -> Bool) -> (List, List) {
+pub fn take_while(self: List, predicate: fn(a) -> Bool) -> List {
when self is {
- [] -> ([], [])
- [x, ..xs] -> {
- let (left, right) = partition(xs, predicate)
+ [] ->
+ []
+ [x, ..xs] ->
if predicate(x) {
- ([x, ..left], right)
+ [x, ..take_while(xs, predicate)]
} else {
- (left, [x, ..right])
+ []
}
- }
}
}
-test partition_1() {
- partition([], fn(x) { x > 2 }) == ([], [])
+test take_while_1() {
+ take_while([], fn(x) { x > 2 }) == []
}
-test partition_2() {
+test take_while_2() {
let xs =
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
- partition(xs, fn(x) { x > 5 }) == ([10, 9, 8, 7, 6], [5, 4, 3, 2, 1])
+ take_while(xs, fn(x) { x > 5 }) == [10, 9, 8, 7, 6]
}
-test partition_3() {
+test take_while_3() {
let xs =
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
- partition(xs, fn(x) { x == 42 }) == ([], xs)
+ take_while(xs, fn(x) { x == 42 }) == []
}
-test partition_4() {
+test take_while_4() {
let xs =
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
- partition(xs, fn(x) { x < 42 }) == (xs, [])
-}
-
-test partition_5() {
- partition([1, 2, 3, 4], fn(x) { x % 2 == 0 }) == ([2, 4], [1, 3])
+ take_while(xs, fn(x) { x < 42 }) == xs
}
-/// Extract a sublist from the given list using 0-based indexes. Negative
-/// indexes wrap over, so `-1` refers to the last element of the list.
+/// Removes duplicate elements from a list.
///
/// ```aiken
-/// list.slice([1, 2, 3, 4, 5, 6], from: 2, to: 4) == [3, 4, 5]
-/// list.slice([1, 2, 3, 4, 5, 6], from: -2, to: -1) == [5, 6]
-/// list.slice([1, 2, 3, 4, 5, 6], from: 1, to: -1) == [2, 3, 4, 5, 6]
+/// list.unique([1, 2, 3, 1]) == [1, 2, 3]
/// ```
-pub fn slice(self: List, from: Int, to: Int) {
- let (i, l) =
- if from >= 0 {
- (from, None)
- } else {
- let l = length(self)
- (l + from, Some(l))
- }
-
- let j =
- if to >= 0 {
- to - i + 1
- } else {
- when l is {
- Some(l) -> l + to - i + 1
- None -> length(self) + to - i + 1
- }
- }
-
- self
- |> drop(i)
- |> take(j)
+pub fn unique(self: List) -> List {
+ when self is {
+ [] ->
+ []
+ [x, ..xs] ->
+ [x, ..unique(filter(xs, fn(y) { y != x }))]
+ }
}
-test slice_1() {
- slice([1, 2, 3], 0, 2) == [1, 2, 3]
+test unique_1() {
+ unique([]) == []
}
-test slice_2() {
- slice([1, 2, 3, 4, 5, 6], from: 2, to: 4) == [3, 4, 5]
+test unique_2() {
+ let xs =
+ [1, 2, 3, 1, 1, 3, 4, 1, 2, 3, 2, 4, 5, 6, 7, 8, 9, 10, 9]
+ unique(xs) == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
}
-test slice_3() {
- slice([1, 2, 3, 4, 5, 6], from: -2, to: -1) == [5, 6]
-}
+// ### Mapping
-test slice_4() {
- slice([1, 2, 3, 4, 5, 6], from: 1, to: -1) == [2, 3, 4, 5, 6]
+/// Map elements of a list into a new list and flatten the result.
+///
+/// ```aiken
+/// list.flat_map([1, 2, 3], fn(a) { [a, 2*a] }) == [1, 2, 2, 4, 3, 6]
+/// ```
+pub fn flat_map(self: List, with: fn(a) -> List) -> List {
+ foldr(self, [], fn(x, xs) { concat(with(x), xs) })
}
-test slice_5() {
- slice([1, 2, 3, 4, 5, 6], from: -4, to: -3) == [3, 4]
+test flat_map_1() {
+ flat_map([], fn(a) { [a] }) == []
}
-test slice_6() {
- slice([1, 2, 3, 4, 5, 6], from: -2, to: 1) == []
+test flat_map_2() {
+ flat_map([1, 2, 3], fn(a) { [a, a] }) == [1, 1, 2, 2, 3, 3]
}
-/// Sort a list in ascending order using the given comparison function.
+/// List [`map`](#map) but provides the position (0-based) of the elements while iterating.
///
/// ```aiken
-/// use aiken/int
-///
-/// sort([3, 1, 4, 0, 2], int.compare) == [0, 1, 2, 3, 4]
-/// sort([1, 2, 3], int.compare) == [1, 2, 3]
+/// list.indexed_map([1, 2, 3], fn(i, x) { i + x }) == [1, 3, 5]
/// ```
-pub fn sort(self: List, compare: fn(a, a) -> Ordering) -> List {
- when self is {
- [] ->
- []
- [x, ..xs] -> insert(sort(xs, compare), x, compare)
- }
+pub fn indexed_map(self: List, with: fn(Int, a) -> result) -> List {
+ do_indexed_map(0, self, with)
}
-fn insert(self: List, e: a, compare: fn(a, a) -> Ordering) -> List {
+fn do_indexed_map(
+ n: Int,
+ self: List,
+ with: fn(Int, a) -> result,
+) -> List {
when self is {
[] ->
- [e]
+ []
[x, ..xs] ->
- if compare(e, x) == Less {
- [e, ..self]
- } else {
- [x, ..insert(xs, e, compare)]
- }
+ [with(n, x), ..do_indexed_map(n + 1, xs, with)]
}
}
-test sort_1() {
- let xs =
- [6, 7, 5, 4, 1, 3, 9, 8, 0, 2]
- sort(xs, int.compare) == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+test indexed_map_1() {
+ indexed_map([], fn(i, _n) { i }) == []
}
-test sort_2() {
- let xs =
- [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
- sort(xs, int.compare) == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+test indexed_map_2() {
+ indexed_map(
+ [4, 8, 13, 2],
+ fn(i, n) {
+ if n == 8 {
+ n
+ } else {
+ i
+ }
+ },
+ ) == [0, 8, 2, 3]
}
-test sort_3() {
- let xs =
- [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
- sort(xs, int.compare) == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+/// Apply a function to each element of a list.
+///
+/// ```aiken
+/// list.map([1, 2, 3, 4], fn(n) { n + 1 }) == [2, 3, 4, 5]
+/// ```
+pub fn map(self: List, with: fn(a) -> result) -> List {
+ when self is {
+ [] ->
+ []
+ [x, ..xs] ->
+ [with(x), ..map(xs, with)]
+ }
}
-test sort_4() {
- sort([], int.compare) == []
+test map_1() {
+ map([], fn(n) { n + 1 }) == []
}
-/// Cut a list in two, such that the first list contains the given number of /
-/// elements and the second list contains the rest.
+test map_2() {
+ map([1, 2, 3, 4], fn(n) { n + 1 }) == [2, 3, 4, 5]
+}
+
+/// Apply a function of two arguments, combining elements from two lists.
///
-/// Fundamentally equivalent to (but more efficient):
+/// Note: if one list is longer, the extra elements are dropped.
///
/// ```aiken
-/// // span(xs, n) == (take(xs, n), drop(xs, n))
-/// span([1, 2, 3, 4, 5], 3) == ([1, 2, 3], [4, 5])
+/// list.map2([1, 2, 3], [1, 2], fn(a, b) { a + b }) == [2, 4]
/// ```
-pub fn span(self: List, n: Int) -> (List, List) {
+pub fn map2(
+ self: List,
+ bs: List,
+ with: fn(a, b) -> result,
+) -> List {
when self is {
- [] -> ([], [])
+ [] ->
+ []
[x, ..xs] ->
- if n <= 0 {
- ([], self)
- } else {
- let (left, right) = span(xs, n - 1)
- ([x, ..left], right)
+ when bs is {
+ [] ->
+ []
+ [y, ..ys] ->
+ [with(x, y), ..map2(xs, ys, with)]
}
}
}
-test span_1() {
- span([], 2) == ([], [])
-}
-
-test span_2() {
- span([1, 2, 3], 2) == ([1, 2], [3])
+test map2_1() {
+ map2([], [1, 2, 3], fn(a, b) { a + b }) == []
}
-test span_3() {
- span([1, 2, 3], -1) == ([], [1, 2, 3])
+test map2_2() {
+ map2([1, 2, 3], [1, 2], fn(a, b) { a + b }) == [2, 4]
}
-test span_4() {
- span([1, 2, 3], 42) == ([1, 2, 3], [])
+test map2_3() {
+ map2([42], [1, 2, 3], fn(_a, b) { Some(b) }) == [Some(1)]
}
-/// Get elements of a list after the first one, if any.
+/// Apply a function of three arguments, combining elements from three lists.
+///
+/// Note: if one list is longer, the extra elements are dropped.
///
/// ```aiken
-/// list.tail([]) == None
-/// list.tail([1, 2, 3]) == Some([2, 3])
+/// list.map3([1, 2, 3], [1, 2], [1, 2, 3], fn(a, b, c) { a + b + c }) == [3, 6]
/// ```
-pub fn tail(self: List) -> Option> {
+pub fn map3(
+ self: List,
+ bs: List,
+ cs: List,
+ with: fn(a, b, c) -> result,
+) -> List {
when self is {
- [] -> None
- [_, ..xs] -> Some(xs)
+ [] ->
+ []
+ [x, ..xs] ->
+ when bs is {
+ [] ->
+ []
+ [y, ..ys] ->
+ when cs is {
+ [] ->
+ []
+ [z, ..zs] ->
+ [with(x, y, z), ..map3(xs, ys, zs, with)]
+ }
+ }
}
}
-test tail_1() {
- tail([1, 2, 3]) == Some([2, 3])
+test map3_1() {
+ map3([], [], [1, 2, 3], fn(a, b, c) { a + b + c }) == []
}
-test tail_2() {
- tail([]) == None
+test map3_2() {
+ map3([1, 2, 3], [1, 2], [1, 2, 3], fn(a, b, c) { a + b + c }) == [3, 6]
}
-/// Get the first `n` elements of a list.
+/// Return the list with its elements in the reserve order.
///
/// ```aiken
-/// list.take([1, 2, 3], 2) == [1, 2]
-/// list.take([1, 2, 3], 14) == [1, 2, 3]
+/// list.reverse([1, 2, 3]) == [3, 2, 1]
/// ```
-pub fn take(self: List, n: Int) -> List {
- if n <= 0 {
- []
- } else {
- when self is {
- [] ->
- []
- [x, ..xs] ->
- [x, ..take(xs, n - 1)]
- }
- }
+pub fn reverse(self: List) -> List {
+ foldl(self, [], fn(x, xs) { [x, ..xs] })
}
-test take_1() {
- take([], 42) == []
+test reverse_1() {
+ reverse([]) == []
}
-test take_2() {
- take([1, 2, 3], 2) == [1, 2]
+test reverse_2() {
+ reverse([1, 2, 3]) == [3, 2, 1]
}
-/// Returns the longest prefix of the given list where all elements satisfy the predicate.
+/// Sort a list in ascending order using the given comparison function.
///
/// ```aiken
-/// list.take_while([1, 2, 3], fn(x) { x > 2 }) == []
-/// list.take_while([1, 2, 3], fn(x) { x < 2 }) == [1]
+/// use aiken/int
+///
+/// sort([3, 1, 4, 0, 2], int.compare) == [0, 1, 2, 3, 4]
+/// sort([1, 2, 3], int.compare) == [1, 2, 3]
/// ```
-pub fn take_while(self: List, predicate: fn(a) -> Bool) -> List {
+pub fn sort(self: List, compare: fn(a, a) -> Ordering) -> List {
when self is {
[] ->
[]
+ [x, ..xs] -> insert(sort(xs, compare), x, compare)
+ }
+}
+
+fn insert(self: List, e: a, compare: fn(a, a) -> Ordering) -> List {
+ when self is {
+ [] ->
+ [e]
[x, ..xs] ->
- if predicate(x) {
- [x, ..take_while(xs, predicate)]
+ if compare(e, x) == Less {
+ [e, ..self]
} else {
- []
+ [x, ..insert(xs, e, compare)]
}
}
}
-test take_while_1() {
- take_while([], fn(x) { x > 2 }) == []
-}
-
-test take_while_2() {
+test sort_1() {
let xs =
- [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
- take_while(xs, fn(x) { x > 5 }) == [10, 9, 8, 7, 6]
+ [6, 7, 5, 4, 1, 3, 9, 8, 0, 2]
+ sort(xs, int.compare) == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
}
-test take_while_3() {
+test sort_2() {
let xs =
- [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
- take_while(xs, fn(x) { x == 42 }) == []
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+ sort(xs, int.compare) == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
}
-test take_while_4() {
+test sort_3() {
let xs =
- [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
- take_while(xs, fn(x) { x < 42 }) == xs
-}
-
-/// Removes duplicate elements from a list.
-///
-/// ```aiken
-/// list.unique([1, 2, 3, 1]) == [1, 2, 3]
-/// ```
-pub fn unique(self: List) -> List {
- when self is {
- [] ->
- []
- [x, ..xs] ->
- [x, ..unique(filter(xs, fn(y) { y != x }))]
- }
-}
-
-test unique_1() {
- unique([]) == []
+ [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
+ sort(xs, int.compare) == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
}
-test unique_2() {
- let xs =
- [1, 2, 3, 1, 1, 3, 4, 1, 2, 3, 2, 4, 5, 6, 7, 8, 9, 10, 9]
- unique(xs) == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+test sort_4() {
+ sort([], int.compare) == []
}
/// Decompose a list of tuples into a tuple of lists.
@@ -1272,6 +1121,65 @@ test unzip_2() {
unzip([(1, "a"), (2, "b")]) == ([1, 2], ["a", "b"])
}
+// ## Combining
+
+/// Merge two lists together.
+///
+/// ```aiken
+/// list.concat([], []) == []
+/// list.concat([], [1, 2, 3]) == [1, 2, 3]
+/// list.concat([1, 2, 3], [4, 5, 6]) == [1, 2, 3, 4, 5, 6]
+/// ```
+pub fn concat(left: List, right: List) -> List {
+ when left is {
+ [] -> right
+ [x, ..xs] ->
+ [x, ..concat(xs, right)]
+ }
+}
+
+test concat_1() {
+ concat([1, 2, 3], [4, 5, 6]) == [1, 2, 3, 4, 5, 6]
+}
+
+test concat_2() {
+ concat([1, 2, 3], []) == [1, 2, 3]
+}
+
+test concat_3() {
+ concat([], [1, 2, 3]) == [1, 2, 3]
+}
+
+/// Remove the first occurrence of each element of the second list from the first one.
+///
+/// ```
+/// list.difference(["h", "e", "l", "l", "o"], ["l", "e", "l"]) == ["h", "o"]
+/// list.difference([1, 2, 3, 4, 5], [1, 1, 2]) == [3, 4, 5]
+/// list.difference([1, 2, 3], []) == [1, 2, 3]
+/// ```
+pub fn difference(self: List, with: List) -> List {
+ when with is {
+ [] -> self
+ [x, ..xs] -> difference(delete(self, x), xs)
+ }
+}
+
+test difference_1() {
+ difference(["h", "e", "l", "l", "o"], ["l", "e", "l"]) == ["h", "o"]
+}
+
+test difference_2() {
+ difference([1, 2, 3, 4, 5], [1, 1, 2]) == [3, 4, 5]
+}
+
+test difference_3() {
+ difference([1, 2, 3], []) == [1, 2, 3]
+}
+
+test difference_4() {
+ difference([], [1, 2, 3]) == []
+}
+
/// Combine two lists together.
///
/// Note: if one list is longer, the extra elements are dropped.
@@ -1305,6 +1213,110 @@ test zip_3() {
zip([1, 2], ["a", "b", "c"]) == [(1, "a"), (2, "b")]
}
+// ## Transforming
+
+/// Reduce a list from left to right.
+///
+/// ```aiken
+/// list.foldl([1, 2, 3], 0, fn(n, total) { n + total }) == 6
+/// list.foldl([1, 2, 3], [], fn(x, xs) { [x, ..xs] }) == [3, 2, 1]
+/// ```
+pub fn foldl(self: List, zero: b, with: fn(a, b) -> b) -> b {
+ when self is {
+ [] -> zero
+ [x, ..xs] -> foldl(xs, with(x, zero), with)
+ }
+}
+
+test foldl_1() {
+ foldl([], 0, fn(_, _) { 1 }) == 0
+}
+
+test foldl_2() {
+ foldl([1, 2, 3, 4, 5], 0, fn(n, total) { n + total }) == 15
+}
+
+test foldl_3() {
+ foldl([1, 2, 3, 4], [], fn(x, xs) { [x, ..xs] }) == [4, 3, 2, 1]
+}
+
+/// Reduce a list from right to left.
+///
+/// ```aiken
+/// list.foldr([1, 2, 3], 0, fn(n, total) { n + total }) == 6
+/// list.foldr([1, 2, 3], [], fn(x, xs) { [x, ..xs] }) == [1, 2, 3]
+/// ```
+pub fn foldr(self: List, zero: b, with: fn(a, b) -> b) -> b {
+ when self is {
+ [] -> zero
+ [x, ..xs] -> with(x, foldr(xs, zero, with))
+ }
+}
+
+test foldr_1() {
+ foldr([1, 2, 3, 4, 5], 0, fn(n, total) { n + total }) == 15
+}
+
+test foldr_2() {
+ foldr(
+ [1, 2, 3],
+ "",
+ fn(n, _str) {
+ if builtin.mod_integer(n, 2) == 0 {
+ "foo"
+ } else {
+ "bar"
+ }
+ },
+ ) == "bar"
+}
+
+test foldr_3() {
+ foldr([1, 2, 3, 4], [], fn(x, xs) { [x, ..xs] }) == [1, 2, 3, 4]
+}
+
+/// Like [`foldr`](#foldr), but also provides the position (0-based) of the elements when iterating.
+///
+/// ```aiken
+/// let group = fn(i, x, xs) { [(i, x), ..xs] }
+/// list.indexed_foldr(["a", "b", "c"], [], group) == [
+/// (0, "a"),
+/// (1, "b"),
+/// (2, "c")
+/// ]
+/// ```
+pub fn indexed_foldr(
+ self: List,
+ zero: result,
+ with: fn(Int, a, result) -> result,
+) -> result {
+ do_indexed_foldr(0, self, zero, with)
+}
+
+fn do_indexed_foldr(
+ n: Int,
+ self: List,
+ zero: result,
+ with: fn(Int, a, result) -> result,
+) -> result {
+ when self is {
+ [] -> zero
+ [x, ..xs] -> with(n, x, do_indexed_foldr(n + 1, xs, zero, with))
+ }
+}
+
+test indexed_foldr_1() {
+ indexed_foldr([], 0, fn(i, x, xs) { i + x + xs }) == 0
+}
+
+test indexed_foldr_2() {
+ let letters =
+ ["a", "b", "c"]
+ indexed_foldr(letters, [], fn(i, x, xs) { [(i, x), ..xs] }) == [
+ (0, "a"), (1, "b"), (2, "c"),
+ ]
+}
+
/// Reduce a list from left to right using the accumulator as left operand.
/// Said differently, this is [`foldl`](#foldl) with callback arguments swapped.
///
diff --git a/build/packages/aiken-lang-stdlib/lib/aiken/pairs.ak b/build/packages/aiken-lang-stdlib/lib/aiken/collection/pairs.ak
similarity index 55%
rename from build/packages/aiken-lang-stdlib/lib/aiken/pairs.ak
rename to build/packages/aiken-lang-stdlib/lib/aiken/collection/pairs.ak
index 61849db..a3aedb9 100644
--- a/build/packages/aiken-lang-stdlib/lib/aiken/pairs.ak
+++ b/build/packages/aiken-lang-stdlib/lib/aiken/collection/pairs.ak
@@ -4,151 +4,188 @@
//// that are specifically tailored to working with associative lists. Fundamentally, a `Pairs` is
//// a type-alias to `List>`.
////
-//// ### Important
-////
-//// Unlike dictionnaries (a.k.a. `Dict`), associative lists make no assumption
-//// about the ordering of elements within the list. As a result, lookup
-//// functions do traverse the entire list when invoked. They are also not _sets_,
-//// and thus allow for duplicate keys. This is reflected in the functions used
-//// to interact with them.
+//// > [!CAUTION]
+//// >
+//// > Unlike dictionnaries (a.k.a. [`Dict`](./dict.html#Dict), associative lists make no assumption
+//// > about the ordering of elements within the list. As a result, lookup
+//// > functions do traverse the entire list when invoked. They are also not _sets_,
+//// > and thus allow for duplicate keys. This is reflected in the functions used
+//// > to interact with them.
-/// Remove a single key-value pair from the `Pairs`. If the key is not found, no changes are made.
-/// Duplicate keys are not removed. Only the **first** key found is removed.
+use aiken/primitive/bytearray
+
+// ## Inspecting
+
+/// Get all values in the alist associated with a given key.
///
/// ```aiken
-/// alist.remove_first([], "a") == []
-/// alist.remove_first([Pair("a", 1)], "a") == []
-/// alist.remove_first([Pair("a", 1), Pair("b", 2)], "a") == [Pair("b", 2)]
-/// alist.remove_first([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == [Pair("b", 2), Pair("a", 3)]
+/// pairs.get_all([], "a") == []
+/// pairs.get_all([Pair("a", 1)], "a") == [1]
+/// pairs.get_all([Pair("a", 1), Pair("b", 2)], "a") == [1]
+/// pairs.get_all([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == [1, 3]
/// ```
-pub fn remove_first(self: Pairs, key k: key) -> Pairs {
+pub fn get_all(self: Pairs, key k: key) -> List {
when self is {
[] ->
[]
- [Pair(k2, v2), ..rest] ->
+ [Pair(k2, v), ..rest] ->
if k == k2 {
- rest
+ [v, ..get_all(rest, k)]
} else {
- [Pair(k2, v2), ..remove_first(rest, k)]
+ get_all(rest, k)
}
}
}
-test remove_first_1() {
- remove_first([], "a") == []
+test get_all_1() {
+ get_all([], "a") == []
}
-test remove_first_2() {
- remove_first([Pair("a", 14)], "a") == []
+test get_all_2() {
+ get_all([Pair("a", 1)], "a") == [1]
}
-test remove_first_3() {
- let fixture =
- [Pair("a", 14)]
- remove_first(fixture, "b") == fixture
+test get_all_3() {
+ get_all([Pair("a", 1), Pair("b", 2)], "a") == [1]
}
-test remove_first_4() {
- let fixture =
- [Pair("a", 1), Pair("b", 2), Pair("a", 3)]
- remove_first(fixture, "a") == [Pair("b", 2), Pair("a", 3)]
+test get_all_4() {
+ get_all([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == [1, 3]
}
-/// Remove a single key-value pair from the Pairs. If the key is not found, no changes are made.
-/// Duplicate keys are not removed. Only the **last** key found is removed.
+test get_all_5() {
+ get_all([Pair("a", 1), Pair("b", 2), Pair("c", 3)], "d") == []
+}
+
+/// Get the value in the alist by its key.
+/// If multiple values with the same key exist, only the first one is returned.
///
/// ```aiken
-/// alist.remove_last([], "a") == []
-/// alist.remove_last([Pair("a", 1)], "a") == []
-/// alist.remove_last([Pair("a", 1), Pair("b", 2)], "a") == [Pair("b", 2)]
-/// alist.remove_last([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == [Pair("a", 1), Pair("b", 2)]
+/// pairs.get_first([], "a") == None
+/// pairs.get_first([Pair("a", 1)], "a") == Some(1)
+/// pairs.get_first([Pair("a", 1), Pair("b", 2)], "a") == Some(1)
+/// pairs.get_first([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == Some(1)
/// ```
-pub fn remove_last(self: Pairs, key k: key) -> Pairs {
+pub fn get_first(self: Pairs, key k: key) -> Option {
when self is {
- [] ->
- []
- [Pair(k2, v2), ..rest] ->
+ [] -> None
+ [Pair(k2, v), ..rest] ->
if k == k2 {
- let tail = remove_last(rest, k)
- if tail == rest {
- rest
- } else {
- [Pair(k2, v2), ..tail]
+ Some(v)
+ } else {
+ get_first(rest, k)
+ }
+ }
+}
+
+test get_first_1() {
+ get_first([], "a") == None
+}
+
+test get_first_2() {
+ get_first([Pair("a", 1)], "a") == Some(1)
+}
+
+test get_first_3() {
+ get_first([Pair("a", 1), Pair("b", 2)], "a") == Some(1)
+}
+
+test get_first_4() {
+ get_first([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == Some(1)
+}
+
+test get_first_5() {
+ get_first([Pair("a", 1), Pair("b", 2), Pair("c", 3)], "d") == None
+}
+
+/// Get the value in the alist by its key.
+/// If multiple values with the same key exist, only the last one is returned.
+///
+/// ```aiken
+/// pairs.get_last([], "a") == None
+/// pairs.get_last([Pair("a", 1)], "a") == Some(1)
+/// pairs.get_last([Pair("a", 1), Pair("b", 2)], "a") == Some(1)
+/// pairs.get_last([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == Some(3)
+/// ```
+pub fn get_last(self: Pairs, key k: key) -> Option {
+ when self is {
+ [] -> None
+ [Pair(k2, v), ..rest] ->
+ if k == k2 {
+ when get_last(rest, k) is {
+ None -> Some(v)
+ some -> some
}
} else {
- [Pair(k2, v2), ..remove_last(rest, k)]
+ get_last(rest, k)
}
}
}
-test remove_last_1() {
- remove_last([], "a") == []
+test get_last_1() {
+ get_last([], "a") == None
}
-test remove_last_2() {
- remove_last([Pair("a", 14)], "a") == []
+test get_last_2() {
+ get_last([Pair("a", 1)], "a") == Some(1)
}
-test remove_last_3() {
- let fixture =
- [Pair("a", 14)]
- remove_last(fixture, "b") == fixture
+test get_last_3() {
+ get_last([Pair("a", 1), Pair("b", 2)], "a") == Some(1)
}
-test remove_last_4() {
- let fixture =
- [Pair("a", 1), Pair("b", 2), Pair("a", 3)]
- remove_last(fixture, "a") == [Pair("a", 1), Pair("b", 2)]
+test get_last_4() {
+ get_last([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == Some(3)
}
-/// Remove all key-value pairs matching the key from the Pairs. If the key is not found, no changes are made.
+test get_last_5() {
+ get_last([Pair("a", 1), Pair("b", 2), Pair("c", 3)], "d") == None
+}
+
+/// Finds all keys in the alist associated with a given value.
///
/// ```aiken
-/// alist.remove_all([], "a") == []
-/// alist.remove_all([Pair("a", 1)], "a") == []
-/// alist.remove_all([Pair("a", 1), Pair("b", 2)], "a") == [Pair("b", 2)]
-/// alist.remove_all([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == [Pair("b", 2)]
+/// pairs.find_all([], 1) == []
+/// pairs.find_all([Pair("a", 1)], 1) == ["a"]
+/// pairs.find_all([Pair("a", 1), Pair("b", 2)], 1) == ["a"]
+/// pairs.find_all([Pair("a", 1), Pair("b", 2), Pair("c", 1)], 1) == ["a", "c"]
/// ```
-pub fn remove_all(self: Pairs, key k: key) -> Pairs {
+pub fn find_all(self: Pairs, v: value) -> List {
when self is {
[] ->
[]
[Pair(k2, v2), ..rest] ->
- if k == k2 {
- remove_all(rest, k)
+ if v == v2 {
+ [k2, ..find_all(rest, v)]
} else {
- [Pair(k2, v2), ..remove_all(rest, k)]
+ find_all(rest, v)
}
}
}
-test remove_all_1() {
- remove_all([], "a") == []
+test find_all_1() {
+ find_all([], "a") == []
}
-test remove_all_2() {
- remove_all([Pair("a", 14)], "a") == []
+test find_all_2() {
+ find_all([Pair("a", 14)], 14) == ["a"]
}
-test remove_all_3() {
- let fixture =
- [Pair("a", 14)]
- remove_all(fixture, "b") == fixture
+test find_all_3() {
+ find_all([Pair("a", 14)], 42) == []
}
-test remove_all_4() {
- let fixture =
- [Pair("a", 1), Pair("b", 2), Pair("a", 3)]
- remove_all(fixture, "a") == [Pair("b", 2)]
+test find_all_4() {
+ find_all([Pair("a", 14), Pair("b", 42), Pair("c", 14)], 14) == ["a", "c"]
}
/// Finds the first key in the alist associated with a given value, if any.
///
/// ```aiken
-/// alist.find_first([], 1) == None
-/// alist.find_first([Pair("a", 1)], 1) == Some("a")
-/// alist.find_first([Pair("a", 1), Pair("b", 2)], 1) == Some("a")
-/// alist.find_first([Pair("a", 1), Pair("b", 2), Pair("c", 1)], 1) == Some("a")
+/// pairs.find_first([], 1) == None
+/// pairs.find_first([Pair("a", 1)], 1) == Some("a")
+/// pairs.find_first([Pair("a", 1), Pair("b", 2)], 1) == Some("a")
+/// pairs.find_first([Pair("a", 1), Pair("b", 2), Pair("c", 1)], 1) == Some("a")
/// ```
pub fn find_first(self: Pairs, v: value) -> Option {
when self is {
@@ -181,10 +218,10 @@ test find_first_4() {
/// Finds the last key in the alist associated with a given value, if any.
///
/// ```aiken
-/// alist.find_last([], 1) == None
-/// alist.find_last([Pair("a", 1)], 1) == Some("a")
-/// alist.find_last([Pair("a", 1), Pair("b", 2)], 1) == Some("a")
-/// alist.find_last([Pair("a", 1), Pair("b", 2), Pair("c", 1)], 1) == Some("c")
+/// pairs.find_last([], 1) == None
+/// pairs.find_last([Pair("a", 1)], 1) == Some("a")
+/// pairs.find_last([Pair("a", 1), Pair("b", 2)], 1) == Some("a")
+/// pairs.find_last([Pair("a", 1), Pair("b", 2), Pair("c", 1)], 1) == Some("c")
/// ```
pub fn find_last(self: Pairs, v: value) -> Option {
when self is {
@@ -217,309 +254,289 @@ test find_last_4() {
find_last([Pair("a", 14), Pair("b", 42), Pair("c", 14)], 14) == Some("c")
}
-/// Finds all keys in the alist associated with a given value.
+/// Check if a key exists in the pairs.
///
/// ```aiken
-/// alist.find_all([], 1) == []
-/// alist.find_all([Pair("a", 1)], 1) == ["a"]
-/// alist.find_all([Pair("a", 1), Pair("b", 2)], 1) == ["a"]
-/// alist.find_all([Pair("a", 1), Pair("b", 2), Pair("c", 1)], 1) == ["a", "c"]
+/// pairs.has_key([], "a") == False
+/// pairs.has_key([Pair("a", 1)], "a") == True
+/// pairs.has_key([Pair("a", 1), Pair("b", 2)], "a") == True
+/// pairs.has_key([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == True
/// ```
-pub fn find_all(self: Pairs, v: value) -> List {
+pub fn has_key(self: Pairs, k: key) -> Bool {
when self is {
- [] ->
- []
- [Pair(k2, v2), ..rest] ->
- if v == v2 {
- [k2, ..find_all(rest, v)]
- } else {
- find_all(rest, v)
- }
+ [] -> False
+ // || is lazy so this is fine
+ [Pair(k2, _), ..rest] -> k == k2 || has_key(rest, k)
}
}
-test find_all_1() {
- find_all([], "a") == []
-}
-
-test find_all_2() {
- find_all([Pair("a", 14)], 14) == ["a"]
-}
-
-test find_all_3() {
- find_all([Pair("a", 14)], 42) == []
-}
-
-test find_all_4() {
- find_all([Pair("a", 14), Pair("b", 42), Pair("c", 14)], 14) == ["a", "c"]
+test has_key_1() {
+ !has_key([], "a")
}
-/// Fold over the key-value pairs in a Pairs. The fold direction follows the
-/// order of elements in the Pairs and is done from right-to-left.
-///
-/// ```aiken
-/// let fixture = [
-/// Pair(1, 100),
-/// Pair(2, 200),
-/// Pair(3, 300),
-/// ]
-///
-/// alist.foldr(fixture, 0, fn(k, v, result) { k * v + result }) == 1400
-/// ```
-pub fn foldr(
- self: Pairs,
- zero: result,
- with: fn(key, value, result) -> result,
-) -> result {
- when self is {
- [] -> zero
- [Pair(k, v), ..rest] -> with(k, v, foldr(rest, zero, with))
- }
+test has_key_2() {
+ has_key([Pair("a", 14)], "a")
}
-test foldr_1() {
- foldr([], 14, fn(_, _, _) { 42 }) == 14
+test has_key_3() {
+ !has_key([Pair("a", 14)], "b")
}
-test foldr_2() {
- foldr(
- [Pair("a", 42), Pair("b", 14)],
- zero: 0,
- with: fn(_, v, total) { v + total },
- ) == 56
+test has_key_4() {
+ has_key([Pair("a", 14), Pair("b", 42)], "b")
}
-test foldr_3() {
- let fixture =
- [Pair(1, 100), Pair(2, 200), Pair(3, 300)]
-
- foldr(fixture, 0, fn(k, v, result) { k * v + result }) == 1400
+test has_key_5() {
+ has_key([Pair("a", 14), Pair("b", 42), Pair("a", 42)], "a")
}
-/// Fold over the key-value pairs in a alist. The fold direction follows keys
-/// in ascending order and is done from left-to-right.
+/// Extract all the keys present in a given `Pairs`.
///
/// ```aiken
-/// let fixture = [
-/// Pair(1, 100),
-/// Pair(2, 200),
-/// Pair(3, 300),
-/// ]
-///
-/// alist.foldl(fixture, 0, fn(k, v, result) { k * v + result }) == 1400
+/// pairs.keys([]) == []
+/// pairs.keys([Pair("a", 1)]) == ["a"]
+/// pairs.keys([Pair("a", 1), Pair("b", 2)]) == ["a", "b"]
+/// pairs.keys([Pair("a", 1), Pair("b", 2), Pair("a", 3)]) == ["a", "b", "a"]
/// ```
-pub fn foldl(
- self: Pairs,
- zero: result,
- with: fn(key, value, result) -> result,
-) -> result {
+pub fn keys(self: Pairs) -> List {
when self is {
- [] -> zero
- [Pair(k, v), ..rest] -> foldl(rest, with(k, v, zero), with)
+ [] ->
+ []
+ [Pair(k, _), ..rest] ->
+ [k, ..keys(rest)]
}
}
-test foldl_1() {
- foldl([], 14, fn(_, _, _) { 42 }) == 14
+test keys_1() {
+ keys([]) == []
}
-test foldl_2() {
- foldl(
- [Pair("a", 42), Pair("b", 14)],
- zero: 0,
- with: fn(_, v, total) { v + total },
- ) == 56
+test keys_2() {
+ keys([Pair("a", 0)]) == ["a"]
}
-/// Get the value in the alist by its key.
-/// If multiple values with the same key exist, only the first one is returned.
+test keys_3() {
+ keys([Pair("a", 0), Pair("b", 0)]) == ["a", "b"]
+}
+
+/// Extract all the values present in a given `Pairs`.
///
/// ```aiken
-/// alist.get_first([], "a") == None
-/// alist.get_first([Pair("a", 1)], "a") == Some(1)
-/// alist.get_first([Pair("a", 1), Pair("b", 2)], "a") == Some(1)
-/// alist.get_first([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == Some(1)
+/// pairs.values([]) == []
+/// pairs.values([Pair("a", 1)]) == [1]
+/// pairs.values([Pair("a", 1), Pair("b", 2)]) == [1, 2]
+/// pairs.values([Pair("a", 1), Pair("b", 2), Pair("a", 3)]) == [1, 2, 3]
/// ```
-pub fn get_first(self: Pairs, key k: key) -> Option {
+pub fn values(self: Pairs) -> List {
when self is {
- [] -> None
- [Pair(k2, v), ..rest] ->
- if k == k2 {
- Some(v)
- } else {
- get_first(rest, k)
- }
+ [] ->
+ []
+ [Pair(_, v), ..rest] ->
+ [v, ..values(rest)]
}
}
-test get_first_1() {
- get_first([], "a") == None
+test values_1() {
+ values([]) == []
}
-test get_first_2() {
- get_first([Pair("a", 1)], "a") == Some(1)
+test values_2() {
+ values([Pair("a", 1)]) == [1]
}
-test get_first_3() {
- get_first([Pair("a", 1), Pair("b", 2)], "a") == Some(1)
+test values_3() {
+ values([Pair("a", 1), Pair("b", 2)]) == [1, 2]
}
-test get_first_4() {
- get_first([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == Some(1)
+test values_4() {
+ values([Pair("a", 1), Pair("b", 2), Pair("a", 3)]) == [1, 2, 3]
}
-test get_first_5() {
- get_first([Pair("a", 1), Pair("b", 2), Pair("c", 3)], "d") == None
-}
+// ## Modifying
-/// Get the value in the alist by its key.
-/// If multiple values with the same key exist, only the last one is returned.
+/// Remove all key-value pairs matching the key from the Pairs. If the key is not found, no changes are made.
///
/// ```aiken
-/// alist.get_last([], "a") == None
-/// alist.get_last([Pair("a", 1)], "a") == Some(1)
-/// alist.get_last([Pair("a", 1), Pair("b", 2)], "a") == Some(1)
-/// alist.get_last([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == Some(3)
+/// pairs.delete_all([], "a") == []
+/// pairs.delete_all([Pair("a", 1)], "a") == []
+/// pairs.delete_all([Pair("a", 1), Pair("b", 2)], "a") == [Pair("b", 2)]
+/// pairs.delete_all([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == [Pair("b", 2)]
/// ```
-pub fn get_last(self: Pairs, key k: key) -> Option {
+pub fn delete_all(self: Pairs, key k: key) -> Pairs {
when self is {
- [] -> None
- [Pair(k2, v), ..rest] ->
+ [] ->
+ []
+ [Pair(k2, v2), ..rest] ->
if k == k2 {
- when get_last(rest, k) is {
- None -> Some(v)
- some -> some
- }
+ delete_all(rest, k)
} else {
- get_last(rest, k)
+ [Pair(k2, v2), ..delete_all(rest, k)]
}
}
}
-test get_last_1() {
- get_last([], "a") == None
+test delete_all_1() {
+ delete_all([], "a") == []
}
-test get_last_2() {
- get_last([Pair("a", 1)], "a") == Some(1)
+test delete_all_2() {
+ delete_all([Pair("a", 14)], "a") == []
}
-test get_last_3() {
- get_last([Pair("a", 1), Pair("b", 2)], "a") == Some(1)
-}
-
-test get_last_4() {
- get_last([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == Some(3)
+test delete_all_3() {
+ let fixture =
+ [Pair("a", 14)]
+ delete_all(fixture, "b") == fixture
}
-test get_last_5() {
- get_last([Pair("a", 1), Pair("b", 2), Pair("c", 3)], "d") == None
+test delete_all_4() {
+ let fixture =
+ [Pair("a", 1), Pair("b", 2), Pair("a", 3)]
+ delete_all(fixture, "a") == [Pair("b", 2)]
}
-/// Get all values in the alist associated with a given key.
+/// Remove a single key-value pair from the `Pairs`. If the key is not found, no changes are made.
+/// Duplicate keys are not deleted. Only the **first** key found is deleted.
///
/// ```aiken
-/// alist.get_all([], "a") == []
-/// alist.get_all([Pair("a", 1)], "a") == [1]
-/// alist.get_all([Pair("a", 1), Pair("b", 2)], "a") == [1]
-/// alist.get_all([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == [1, 3]
+/// pairs.delete_first([], "a") == []
+/// pairs.delete_first([Pair("a", 1)], "a") == []
+/// pairs.delete_first([Pair("a", 1), Pair("b", 2)], "a") == [Pair("b", 2)]
+/// pairs.delete_first([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == [Pair("b", 2), Pair("a", 3)]
/// ```
-pub fn get_all(self: Pairs, key k: key) -> List {
+pub fn delete_first(self: Pairs, key k: key) -> Pairs {
when self is {
[] ->
[]
- [Pair(k2, v), ..rest] ->
+ [Pair(k2, v2), ..rest] ->
if k == k2 {
- [v, ..get_all(rest, k)]
+ rest
} else {
- get_all(rest, k)
+ [Pair(k2, v2), ..delete_first(rest, k)]
}
}
}
-test get_all_1() {
- get_all([], "a") == []
+test delete_first_1() {
+ delete_first([], "a") == []
}
-test get_all_2() {
- get_all([Pair("a", 1)], "a") == [1]
+test delete_first_2() {
+ delete_first([Pair("a", 14)], "a") == []
}
-test get_all_3() {
- get_all([Pair("a", 1), Pair("b", 2)], "a") == [1]
-}
-
-test get_all_4() {
- get_all([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == [1, 3]
+test delete_first_3() {
+ let fixture =
+ [Pair("a", 14)]
+ delete_first(fixture, "b") == fixture
}
-test get_all_5() {
- get_all([Pair("a", 1), Pair("b", 2), Pair("c", 3)], "d") == []
+test delete_first_4() {
+ let fixture =
+ [Pair("a", 1), Pair("b", 2), Pair("a", 3)]
+ delete_first(fixture, "a") == [Pair("b", 2), Pair("a", 3)]
}
-/// Check if a key exists in the alist.
+/// Remove a single key-value pair from the Pairs. If the key is not found, no changes are made.
+/// Duplicate keys are not deleted. Only the **last** key found is deleted.
///
/// ```aiken
-/// alist.has_key([], "a") == False
-/// alist.has_key([Pair("a", 1)], "a") == True
-/// alist.has_key([Pair("a", 1), Pair("b", 2)], "a") == True
-/// alist.has_key([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == True
+/// pairs.delete_last([], "a") == []
+/// pairs.delete_last([Pair("a", 1)], "a") == []
+/// pairs.delete_last([Pair("a", 1), Pair("b", 2)], "a") == [Pair("b", 2)]
+/// pairs.delete_last([Pair("a", 1), Pair("b", 2), Pair("a", 3)], "a") == [Pair("a", 1), Pair("b", 2)]
/// ```
-pub fn has_key(self: Pairs, k: key) -> Bool {
+pub fn delete_last(self: Pairs, key k: key) -> Pairs {
when self is {
- [] -> False
- // || is lazy so this is fine
- [Pair(k2, _), ..rest] -> k == k2 || has_key(rest, k)
+ [] ->
+ []
+ [Pair(k2, v2), ..rest] ->
+ if k == k2 {
+ let tail = delete_last(rest, k)
+ if tail == rest {
+ rest
+ } else {
+ [Pair(k2, v2), ..tail]
+ }
+ } else {
+ [Pair(k2, v2), ..delete_last(rest, k)]
+ }
}
}
-test has_key_1() {
- !has_key([], "a")
-}
-
-test has_key_2() {
- has_key([Pair("a", 14)], "a")
+test delete_last_1() {
+ delete_last([], "a") == []
}
-test has_key_3() {
- !has_key([Pair("a", 14)], "b")
+test delete_last_2() {
+ delete_last([Pair("a", 14)], "a") == []
}
-test has_key_4() {
- has_key([Pair("a", 14), Pair("b", 42)], "b")
+test delete_last_3() {
+ let fixture =
+ [Pair("a", 14)]
+ delete_last(fixture, "b") == fixture
}
-test has_key_5() {
- has_key([Pair("a", 14), Pair("b", 42), Pair("a", 42)], "a")
+test delete_last_4() {
+ let fixture =
+ [Pair("a", 1), Pair("b", 2), Pair("a", 3)]
+ delete_last(fixture, "a") == [Pair("a", 1), Pair("b", 2)]
}
-/// Extract all the keys present in a given `Pairs`.
+/// Insert a value in the `Pairs` at a given key. If the key already exists,
+/// the value is added in front.
///
/// ```aiken
-/// alist.keys([]) == []
-/// alist.keys([Pair("a", 1)]) == ["a"]
-/// alist.keys([Pair("a", 1), Pair("b", 2)]) == ["a", "b"]
-/// alist.keys([Pair("a", 1), Pair("b", 2), Pair("a", 3)]) == ["a", "b", "a"]
+/// use aiken/primitive/bytearray
+///
+/// let result =
+/// []
+/// |> pairs.insert_by_ascending_key(key: "foo", value: 1, compare: bytearray.compare)
+/// |> pairs.insert_by_ascending_key(key: "bar", value: 2, compare: bytearray.compare)
+/// |> pairs.insert_by_ascending_key(key: "foo", value: 3, compare: bytearray.compare)
+///
+/// result == [Pair("bar", 2), Pair("foo", 3), Pair("foo", 1)]
/// ```
-pub fn keys(self: Pairs) -> List {
+pub fn insert_by_ascending_key(
+ self: Pairs,
+ key k: key,
+ value v: value,
+ compare: fn(key, key) -> Ordering,
+) -> Pairs {
when self is {
[] ->
- []
- [Pair(k, _), ..rest] ->
- [k, ..keys(rest)]
+ [Pair(k, v)]
+ [Pair(k2, v2), ..rest] ->
+ if compare(k, k2) == Less {
+ [Pair(k, v), ..self]
+ } else {
+ if k == k2 {
+ [Pair(k, v), ..self]
+ } else {
+ [Pair(k2, v2), ..insert_by_ascending_key(rest, k, v, compare)]
+ }
+ }
}
}
-test keys_1() {
- keys([]) == []
-}
+test insert_by_ascending_key_1() {
+ let m =
+ []
+ |> insert_by_ascending_key("foo", 42, bytearray.compare)
+ |> insert_by_ascending_key("foo", 14, bytearray.compare)
-test keys_2() {
- keys([Pair("a", 0)]) == ["a"]
+ m == [Pair("foo", 14), Pair("foo", 42)]
}
-test keys_3() {
- keys([Pair("a", 0), Pair("b", 0)]) == ["a", "b"]
+test insert_by_ascending_key_2() {
+ let m =
+ []
+ |> insert_by_ascending_key("foo", 42, bytearray.compare)
+ |> insert_by_ascending_key("bar", 14, bytearray.compare)
+ |> insert_by_ascending_key("baz", 1337, bytearray.compare)
+
+ m == [Pair("bar", 14), Pair("baz", 1337), Pair("foo", 42)]
}
/// Apply a function to all key-value pairs in a alist, replacing the values.
@@ -527,7 +544,7 @@ test keys_3() {
/// ```aiken
/// let fixture = [Pair("a", 100), Pair("b", 200)]
///
-/// alist.map(fixture, fn(_k, v) { v * 2 }) == [Pair("a", 200), Pair("b", 400)]
+/// pairs.map(fixture, fn(_k, v) { v * 2 }) == [Pair("a", 200), Pair("b", 400)]
/// ```
pub fn map(
self: Pairs,
@@ -555,35 +572,136 @@ test map_2() {
map(fixture, with: fn(_, v) { v + 1 }) == [Pair("a", 2), Pair("b", 3)]
}
-/// Extract all the values present in a given `Pairs`.
+/// Insert a value in the `Pairs` at a given key. If the key already exists,
+/// its value is replaced.
///
/// ```aiken
-/// alist.values([]) == []
-/// alist.values([Pair("a", 1)]) == [1]
-/// alist.values([Pair("a", 1), Pair("b", 2)]) == [1, 2]
-/// alist.values([Pair("a", 1), Pair("b", 2), Pair("a", 3)]) == [1, 2, 3]
+/// use aiken/primitive/bytearray
+///
+/// let result =
+/// []
+/// |> pairs.repsert_by_ascending_key(key: "foo", value: 1, compare: bytearray.compare)
+/// |> pairs.repsert_by_ascending_key(key: "bar", value: 2, compare: bytearray.compare)
+/// |> pairs.repsert_by_ascending_key(key: "foo", value: 3, compare: bytearray.compare)
+///
+/// result == [Pair("bar", 2), Pair("foo", 3)]
/// ```
-pub fn values(self: Pairs) -> List {
+pub fn repsert_by_ascending_key(
+ self: Pairs,
+ key k: key,
+ value v: value,
+ compare: fn(key, key) -> Ordering,
+) -> Pairs {
when self is {
[] ->
- []
- [Pair(_, v), ..rest] ->
- [v, ..values(rest)]
+ [Pair(k, v)]
+ [Pair(k2, v2), ..rest] ->
+ if compare(k, k2) == Less {
+ [Pair(k, v), ..self]
+ } else {
+ if k == k2 {
+ [Pair(k, v), ..rest]
+ } else {
+ [Pair(k2, v2), ..repsert_by_ascending_key(rest, k, v, compare)]
+ }
+ }
}
}
-test values_1() {
- values([]) == []
+test repsert_by_ascending_key_1() {
+ let m =
+ []
+ |> repsert_by_ascending_key("foo", 42, bytearray.compare)
+ |> repsert_by_ascending_key("foo", 14, bytearray.compare)
+
+ m == [Pair("foo", 14)]
}
-test values_2() {
- values([Pair("a", 1)]) == [1]
+test repsert_by_ascending_key_2() {
+ let m =
+ []
+ |> repsert_by_ascending_key("foo", 42, bytearray.compare)
+ |> repsert_by_ascending_key("bar", 14, bytearray.compare)
+ |> repsert_by_ascending_key("baz", 1337, bytearray.compare)
+
+ m == [Pair("bar", 14), Pair("baz", 1337), Pair("foo", 42)]
}
-test values_3() {
- values([Pair("a", 1), Pair("b", 2)]) == [1, 2]
+// ## Transforming
+
+/// Fold over the key-value pairs in a pairs. The fold direction follows keys
+/// in ascending order and is done from left-to-right.
+///
+/// ```aiken
+/// let fixture = [
+/// Pair(1, 100),
+/// Pair(2, 200),
+/// Pair(3, 300),
+/// ]
+///
+/// pairs.foldl(fixture, 0, fn(k, v, result) { k * v + result }) == 1400
+/// ```
+pub fn foldl(
+ self: Pairs,
+ zero: result,
+ with: fn(key, value, result) -> result,
+) -> result {
+ when self is {
+ [] -> zero
+ [Pair(k, v), ..rest] -> foldl(rest, with(k, v, zero), with)
+ }
}
-test values_4() {
- values([Pair("a", 1), Pair("b", 2), Pair("a", 3)]) == [1, 2, 3]
+test foldl_1() {
+ foldl([], 14, fn(_, _, _) { 42 }) == 14
+}
+
+test foldl_2() {
+ foldl(
+ [Pair("a", 42), Pair("b", 14)],
+ zero: 0,
+ with: fn(_, v, total) { v + total },
+ ) == 56
+}
+
+/// Fold over the key-value pairs in a Pairs. The fold direction follows the
+/// order of elements in the Pairs and is done from right-to-left.
+///
+/// ```aiken
+/// let fixture = [
+/// Pair(1, 100),
+/// Pair(2, 200),
+/// Pair(3, 300),
+/// ]
+///
+/// pairs.foldr(fixture, 0, fn(k, v, result) { k * v + result }) == 1400
+/// ```
+pub fn foldr(
+ self: Pairs,
+ zero: result,
+ with: fn(key, value, result) -> result,
+) -> result {
+ when self is {
+ [] -> zero
+ [Pair(k, v), ..rest] -> with(k, v, foldr(rest, zero, with))
+ }
+}
+
+test foldr_1() {
+ foldr([], 14, fn(_, _, _) { 42 }) == 14
+}
+
+test foldr_2() {
+ foldr(
+ [Pair("a", 42), Pair("b", 14)],
+ zero: 0,
+ with: fn(_, v, total) { v + total },
+ ) == 56
+}
+
+test foldr_3() {
+ let fixture =
+ [Pair(1, 100), Pair(2, 200), Pair(3, 300)]
+
+ foldr(fixture, 0, fn(k, v, result) { k * v + result }) == 1400
}
diff --git a/build/packages/aiken-lang-stdlib/lib/aiken/crypto.ak b/build/packages/aiken-lang-stdlib/lib/aiken/crypto.ak
new file mode 100644
index 0000000..46a7dda
--- /dev/null
+++ b/build/packages/aiken-lang-stdlib/lib/aiken/crypto.ak
@@ -0,0 +1,147 @@
+use aiken/builtin
+
+pub type VerificationKey =
+ ByteArray
+
+pub type VerificationKeyHash =
+ Hash
+
+pub type Script =
+ ByteArray
+
+pub type ScriptHash =
+ Hash
+
+pub type Signature =
+ ByteArray
+
+pub type DataHash =
+ Hash
+
+/// A `Hash` is nothing more than a `ByteArray`, but it carries extra
+/// information for readability.
+///
+/// On-chain, any hash digest value is represented as a plain 'ByteArray'.
+/// Though in practice, hashes come from different sources and have
+/// different semantics.
+///
+/// Hence, while this type-alias doesn't provide any strong type-guarantees,
+/// it helps writing functions signatures with more meaningful types than mere
+/// 'ByteArray'.
+///
+/// Compare for example:
+///
+/// ```aiken
+/// pub type Credential {
+/// VerificationKey(ByteArray)
+/// Script(ByteArray)
+/// }
+/// ```
+///
+/// with
+///
+/// ```aiken
+/// pub type Credential {
+/// VerificationKey(Hash)
+/// Script(Hash)
+/// }
+/// ```
+///
+/// Both are strictly equivalent, but the second reads much better.
+pub type Hash =
+ ByteArray
+
+// ## Hashing
+
+/// A blake2b-224 hash algorithm.
+///
+/// Typically used for:
+///
+/// - [`Credential`](../cardano/address.html#Credential)
+/// - [`PolicyId`](../cardano/assets.html#PolicyId)
+///
+/// Note: there's no function to calculate blake2b-224 hash digests on-chain.
+pub opaque type Blake2b_224 {
+ Blake2b_224
+}
+
+/// Compute the blake2b-224 hash digest (28 bytes) of some data.
+pub fn blake2b_224(bytes: ByteArray) -> Hash {
+ builtin.blake2b_224(bytes)
+}
+
+/// A blake2b-256 hash algorithm.
+///
+/// Typically used for:
+///
+/// - [`TransactionId`](../cardano/transaction.html#TransactionId)
+pub opaque type Blake2b_256 {
+ Blake2b_256
+}
+
+/// Compute the blake2b-256 hash digest (32 bytes) of some data.
+pub fn blake2b_256(bytes: ByteArray) -> Hash {
+ builtin.blake2b_256(bytes)
+}
+
+/// A Keccak-256 hash algorithm.
+pub opaque type Keccak_256 {
+ Keccak_256
+}
+
+/// Compute the keccak-256 hash digest (32 bytes) of some data.
+pub fn keccak_256(bytes: ByteArray) -> Hash {
+ builtin.keccak_256(bytes)
+}
+
+/// A SHA2-256 hash algorithm.
+pub opaque type Sha2_256 {
+ Sha2_256
+}
+
+/// Compute the sha2-256 hash digest (32 bytes) of some data.
+pub fn sha2_256(bytes: ByteArray) -> Hash {
+ builtin.sha2_256(bytes)
+}
+
+/// A SHA3-256 hash algorithm.
+pub opaque type Sha3_256 {
+ Sha3_256
+}
+
+/// Compute the sha3-256 hash digest (32 bytes) of some data.
+pub fn sha3_256(bytes: ByteArray) -> Hash {
+ builtin.sha3_256(bytes)
+}
+
+// ## Verifying signatures
+
+/// Verify an ECDCA signature (over secp256k1) using the given verification key.
+/// Returns `True` when the signature is valid.
+pub fn verify_ecdsa_signature(
+ key: VerificationKey,
+ msg: ByteArray,
+ sig: Signature,
+) -> Bool {
+ builtin.verify_ecdsa_secp256k1_signature(key, msg, sig)
+}
+
+/// Verify an Ed25519 signature using the given verification key.
+/// Returns `True` when the signature is valid.
+pub fn verify_ed25519_signature(
+ key: VerificationKey,
+ msg: ByteArray,
+ sig: Signature,
+) -> Bool {
+ builtin.verify_ed25519_signature(key, msg, sig)
+}
+
+/// Verify a Schnorr signature (over secp256k1) using the given verification key.
+/// Returns `True` when the signature is valid.
+pub fn verify_schnorr_signature(
+ key: VerificationKey,
+ msg: ByteArray,
+ sig: Signature,
+) -> Bool {
+ builtin.verify_schnorr_secp256k1_signature(key, msg, sig)
+}
diff --git a/build/packages/aiken-lang-stdlib/lib/aiken/hash.ak b/build/packages/aiken-lang-stdlib/lib/aiken/hash.ak
deleted file mode 100644
index 4f86027..0000000
--- a/build/packages/aiken-lang-stdlib/lib/aiken/hash.ak
+++ /dev/null
@@ -1,83 +0,0 @@
-//// This module defines `Hash`, a self-documenting type-alias with a
-//// phantom-type for readability.
-////
-//// On-chain, any hash digest value is represented as a plain 'ByteArray'.
-//// Though in practice, hashes come from different sources and have
-//// different semantics.
-////
-//// Hence, while this type-alias doesn't provide any strong type-guarantees,
-//// it helps writing functions signatures with more meaningful types than mere
-//// 'ByteArray'.
-////
-//// Compare for example:
-////
-//// ```aiken
-//// pub type Credential {
-//// VerificationKeyCredential(ByteArray)
-//// ScriptCredential(ByteArray)
-//// }
-//// ```
-////
-//// with
-////
-//// ```aiken
-//// pub type Credential {
-//// VerificationKeyCredential(Hash)
-//// ScriptCredential(Hash)
-//// }
-//// ```
-////
-//// Both are strictly equivalent, but the second reads much better.
-
-use aiken/builtin
-
-/// A `Hash` is nothing more than a `ByteArray`, but it carries extra
-/// information for readability.
-pub type Hash =
- ByteArray
-
-/// A blake2b-224 hash algorithm.
-///
-/// Typically used for:
-///
-/// - [`Credential`](../aiken/transaction/credential.html#Credential)
-/// - [`PolicyId`](../aiken/transaction/value.html#PolicyId)
-///
-/// Note: there's no function to calculate blake2b-224 hash digests on-chain.
-pub opaque type Blake2b_224 {
- Blake2b_224
-}
-
-/// A blake2b-256 hash algorithm.
-///
-/// Typically used for:
-///
-/// - [`TransactionId`](../aiken/transaction.html#TransactionId)
-pub opaque type Blake2b_256 {
- Blake2b_256
-}
-
-/// Compute the blake2b-256 hash digest of some data.
-pub fn blake2b_256(bytes: ByteArray) -> Hash {
- builtin.blake2b_256(bytes)
-}
-
-/// A SHA2-256 hash algorithm.
-pub opaque type Sha2_256 {
- Sha2_256
-}
-
-/// Compute the sha2-256 hash digest of some data.
-pub fn sha2_256(bytes: ByteArray) -> Hash {
- builtin.sha2_256(bytes)
-}
-
-/// A SHA3-256 hash algorithm.
-pub opaque type Sha3_256 {
- Sha3_256
-}
-
-/// Compute the sha3-256 hash digest of some data.
-pub fn sha3_256(bytes: ByteArray) -> Hash {
- builtin.sha3_256(bytes)
-}
diff --git a/build/packages/aiken-lang-stdlib/lib/aiken/int.ak b/build/packages/aiken-lang-stdlib/lib/aiken/int.ak
deleted file mode 100644
index fd602b6..0000000
--- a/build/packages/aiken-lang-stdlib/lib/aiken/int.ak
+++ /dev/null
@@ -1,80 +0,0 @@
-use aiken/bytearray
-use aiken/math
-use aiken/option
-
-/// Compare two integers.
-///
-/// ```aiken
-/// int.compare(14, 42) == Less
-/// int.compare(14, 14) == Equal
-/// int.compare(42, 14) == Greater
-/// ```
-pub fn compare(left: Int, right: Int) -> Ordering {
- if left < right {
- Less
- } else if left > right {
- Greater
- } else {
- Equal
- }
-}
-
-/// Parse an integer from a utf-8 encoded 'ByteArray', when possible.
-///
-/// ```aiken
-/// int.from_utf8("14") == Some(14)
-/// int.from_utf8("-42") == Some(-42)
-/// int.from_utf8("007") == Some(7)
-/// int.from_utf8("foo") == None
-/// int.from_utf8("1.0") == None
-/// int.from_utf8("1-2") == None
-/// ```
-pub fn from_utf8(bytes: ByteArray) -> Option {
- bytes
- |> bytearray.foldr(
- Some((0, 0)),
- fn(byte, st) {
- when st is {
- None -> None
- Some((n, e)) ->
- if byte < 48 || byte > 57 {
- if byte == 45 {
- Some((-n, 0))
- } else {
- None
- }
- } else if n < 0 {
- None
- } else {
- let digit = byte - 48
- Some((n + digit * math.pow(10, e), e + 1))
- }
- }
- },
- )
- |> option.map(fn(tuple) { tuple.1st })
-}
-
-test from_utf8_1() {
- from_utf8("0017") == Some(17)
-}
-
-test from_utf8_2() {
- from_utf8("42") == Some(42)
-}
-
-test from_utf8_3() {
- from_utf8("1337") == Some(1337)
-}
-
-test from_utf8_4() {
- from_utf8("-14") == Some(-14)
-}
-
-test from_utf8_5() {
- from_utf8("foo") == None
-}
-
-test from_utf8_6() {
- from_utf8("1-2") == None
-}
diff --git a/build/packages/aiken-lang-stdlib/lib/aiken/interval.ak b/build/packages/aiken-lang-stdlib/lib/aiken/interval.ak
index 323d5a9..33ddb9f 100644
--- a/build/packages/aiken-lang-stdlib/lib/aiken/interval.ak
+++ b/build/packages/aiken-lang-stdlib/lib/aiken/interval.ak
@@ -12,6 +12,8 @@
//// executed, but we can reason about the interval bounds to validate pieces of
//// logic.
+// TODO: Replace 'Int' with a generic 'a' once we have comparable traits.
+
/// A type to represent intervals of values. Interval are inhabited by a type
/// `a` which is useful for non-infinite intervals that have a finite
/// lower-bound and/or upper-bound.
@@ -48,103 +50,180 @@ pub type IntervalBound {
is_inclusive: Bool,
}
-/// Return the highest bound of the two.
+/// A type of interval bound. Where finite, a value of type `a` must be
+/// provided. `a` will typically be an `Int`, representing a number of seconds or
+/// milliseconds.
+pub type IntervalBoundType {
+ NegativeInfinity
+ Finite(a)
+ PositiveInfinity
+}
+
+// ## Constructing
+
+/// Create an interval that includes all values greater than the given bound. i.e [lower_bound, +INF)
///
/// ```aiken
-/// let ib1 = IntervalBound { bound_type: Finite(0), is_inclusive: False }
-/// let ib2 = IntervalBound { bound_type: Finite(1), is_inclusive: False }
-///
-/// interval.max(ib1, ib2) == ib2
+/// interval.after(10) == Interval {
+/// lower_bound: IntervalBound { bound_type: Finite(10), is_inclusive: True },
+/// upper_bound: IntervalBound { bound_type: PositiveInfinity, is_inclusive: True },
+/// }
/// ```
-pub fn max(
- left: IntervalBound,
- right: IntervalBound,
-) -> IntervalBound {
- when compare_bound(left, right) is {
- Less -> right
- Equal -> left
- Greater -> left
+pub fn after(lower_bound: a) -> Interval {
+ Interval {
+ lower_bound: IntervalBound {
+ bound_type: Finite(lower_bound),
+ is_inclusive: True,
+ },
+ upper_bound: IntervalBound {
+ bound_type: PositiveInfinity,
+ is_inclusive: True,
+ },
}
}
-/// Return the smallest bound of the two.
+/// Create an interval that includes all values after (and not including) the given bound. i.e (lower_bound, +INF)
///
/// ```aiken
-/// let ib1 = IntervalBound { bound_type: Finite(0), is_inclusive: False }
-/// let ib2 = IntervalBound { bound_type: Finite(1), is_inclusive: False }
+/// interval.entirely_after(10) == Interval {
+/// lower_bound: IntervalBound { bound_type: Finite(10), is_inclusive: False },
+/// upper_bound: IntervalBound { bound_type: PositiveInfinity, is_inclusive: True },
+/// }
+/// ```
+pub fn entirely_after(lower_bound: a) -> Interval {
+ Interval {
+ lower_bound: IntervalBound {
+ bound_type: Finite(lower_bound),
+ is_inclusive: False,
+ },
+ upper_bound: IntervalBound {
+ bound_type: PositiveInfinity,
+ is_inclusive: True,
+ },
+ }
+}
+
+/// Create an interval that includes all values before (and including) the given bound. i.e (-INF, upper_bound]
///
-/// interval.min(ib1, ib2) == ib1
+/// ```aiken
+/// interval.before(100) == Interval {
+/// lower_bound: IntervalBound { bound_type: NegativeInfinity, is_inclusive: True },
+/// upper_bound: IntervalBound { bound_type: Finite(100), is_inclusive: True },
+/// }
/// ```
-pub fn min(
- left: IntervalBound,
- right: IntervalBound,
-) -> IntervalBound {
- when compare_bound(left, right) is {
- Less -> left
- Equal -> left
- Greater -> right
+pub fn before(upper_bound: a) -> Interval {
+ Interval {
+ lower_bound: IntervalBound {
+ bound_type: NegativeInfinity,
+ is_inclusive: True,
+ },
+ upper_bound: IntervalBound {
+ bound_type: Finite(upper_bound),
+ is_inclusive: True,
+ },
}
}
-fn compare_bound(
- left: IntervalBound,
- right: IntervalBound,
-) -> Ordering {
- when compare_bound_type(left.bound_type, right.bound_type) is {
- Less -> Less
- Greater -> Greater
- Equal ->
- if left.is_inclusive == right.is_inclusive {
- Equal
- } else if left.is_inclusive {
- Greater
- } else {
- Less
- }
+/// Create an interval that includes all values before (and not including) the given bound. i.e (-INF, upper_bound)
+///
+/// ```aiken
+/// interval.entirely_before(10) == Interval {
+/// lower_bound: IntervalBound { bound_type: NegativeInfinity, is_inclusive: True },
+/// upper_bound: IntervalBound { bound_type: Finite(10), is_inclusive: False },
+/// }
+/// ```
+pub fn entirely_before(upper_bound: a) -> Interval {
+ Interval {
+ lower_bound: IntervalBound {
+ bound_type: NegativeInfinity,
+ is_inclusive: True,
+ },
+ upper_bound: IntervalBound {
+ bound_type: Finite(upper_bound),
+ is_inclusive: False,
+ },
}
}
-/// A type of interval bound. Where finite, a value of type `a` must be
-/// provided. `a` will typically be an `Int`, representing a number of seconds or
-/// milliseconds.
-pub type IntervalBoundType {
- NegativeInfinity
- Finite(a)
- PositiveInfinity
+/// Create an interval that includes all values between two bounds, including the bounds. i.e. [lower_bound, upper_bound]
+///
+/// ```aiken
+/// interval.between(10, 100) == Interval {
+/// lower_bound: IntervalBound { bound_type: Finite(10), is_inclusive: True },
+/// upper_bound: IntervalBound { bound_type: Finite(100), is_inclusive: True },
+/// }
+/// ```
+pub fn between(lower_bound: a, upper_bound: a) -> Interval {
+ Interval {
+ lower_bound: IntervalBound {
+ bound_type: Finite(lower_bound),
+ is_inclusive: True,
+ },
+ upper_bound: IntervalBound {
+ bound_type: Finite(upper_bound),
+ is_inclusive: True,
+ },
+ }
}
-fn compare_bound_type(
- left: IntervalBoundType,
- right: IntervalBoundType,
-) -> Ordering {
- when left is {
- NegativeInfinity ->
- when right is {
- NegativeInfinity -> Equal
- _ -> Less
- }
- PositiveInfinity ->
- when right is {
- PositiveInfinity -> Equal
- _ -> Greater
- }
- Finite(left) ->
- when right is {
- NegativeInfinity -> Greater
- PositiveInfinity -> Less
- Finite(right) ->
- if left < right {
- Less
- } else if left == right {
- Equal
- } else {
- Greater
- }
- }
+/// Create an interval that includes all values between two bounds, excluding the bounds. i.e. (lower_bound, upper_bound)
+///
+/// ```aiken
+/// interval.entirely_between(10, 100) == Interval {
+/// lower_bound: IntervalBound { bound_type: Finite(10), is_inclusive: False },
+/// upper_bound: IntervalBound { bound_type: Finite(100), is_inclusive: False },
+/// }
+/// ```
+pub fn entirely_between(lower_bound: a, upper_bound: a) -> Interval {
+ Interval {
+ lower_bound: IntervalBound {
+ bound_type: Finite(lower_bound),
+ is_inclusive: False,
+ },
+ upper_bound: IntervalBound {
+ bound_type: Finite(upper_bound),
+ is_inclusive: False,
+ },
}
}
-// TODO: Replace 'Int' with a generic 'a' once we have comparable traits.
+/// Create an empty interval that contains no value.
+///
+/// ```aiken
+/// interval.contains(empty, 0) == False
+/// interval.contains(empty, 1000) == False
+/// ```
+pub const empty: Interval =
+ Interval {
+ lower_bound: IntervalBound {
+ bound_type: PositiveInfinity,
+ is_inclusive: True,
+ },
+ upper_bound: IntervalBound {
+ bound_type: NegativeInfinity,
+ is_inclusive: True,
+ },
+ }
+
+/// Create an interval that contains every possible values. i.e. (-INF, +INF)
+///
+/// ```aiken
+/// interval.contains(everything, 0) == True
+/// interval.contains(everything, 1000) == True
+/// ```
+pub const everything: Interval =
+ Interval {
+ lower_bound: IntervalBound {
+ bound_type: NegativeInfinity,
+ is_inclusive: True,
+ },
+ upper_bound: IntervalBound {
+ bound_type: PositiveInfinity,
+ is_inclusive: True,
+ },
+ }
+
+// ## Inspecting
/// Checks whether an element is contained within the interval.
///
@@ -152,11 +231,11 @@ fn compare_bound_type(
/// let iv =
/// Interval {
/// lower_bound: IntervalBound {
-/// bound_type: Finite(14),
+/// bound_type: Finite(14),
/// is_inclusive: True
/// },
/// upper_bound: IntervalBound {
-/// bound_type: Finite(42),
+/// bound_type: Finite(42),
/// is_inclusive: False
/// },
/// }
@@ -195,7 +274,7 @@ pub fn contains(self: Interval, elem: Int) -> Bool {
}
test contains_1() {
- let iv = everything()
+ let iv = everything
contains(iv, 14)
}
@@ -250,113 +329,55 @@ test contains_11() {
}
test contains_12() {
- let iv = empty()
+ let iv = empty
!contains(iv, 14)
}
-/// Create an interval that contains every possible values. i.e. (-INF, +INF)
-///
-/// ```aiken
-/// interval.contains(everything(), 0) == True
-/// interval.contains(everything(), 1000) == True
-/// ```
-pub fn everything() -> Interval {
- Interval {
- lower_bound: IntervalBound {
- bound_type: NegativeInfinity,
- is_inclusive: True,
- },
- upper_bound: IntervalBound {
- bound_type: PositiveInfinity,
- is_inclusive: True,
- },
- }
-}
-
-/// Create an empty interval that contains no value.
-///
-/// ```aiken
-/// interval.contains(empty(), 0) == False
-/// interval.contains(empty(), 1000) == False
-/// ```
-pub fn empty() -> Interval {
- Interval {
- lower_bound: IntervalBound {
- bound_type: PositiveInfinity,
- is_inclusive: True,
- },
- upper_bound: IntervalBound {
- bound_type: NegativeInfinity,
- is_inclusive: True,
- },
- }
-}
-
-/// Create an interval that includes all values between two bounds, including the bounds. i.e. [lower_bound, upper_bound]
+/// Tells whether an interval is empty; i.e. that is contains no value.
///
/// ```aiken
-/// interval.between(10, 100) == Interval {
-/// lower_bound: IntervalBound { bound_type: Finite(10), is_inclusive: True },
-/// upper_bound: IntervalBound { bound_type: Finite(100), is_inclusive: True },
-/// }
-/// ```
-pub fn between(lower_bound: a, upper_bound: a) -> Interval {
- Interval {
- lower_bound: IntervalBound {
- bound_type: Finite(lower_bound),
- is_inclusive: True,
- },
- upper_bound: IntervalBound {
- bound_type: Finite(upper_bound),
- is_inclusive: True,
- },
- }
-}
-
-/// Create an interval that includes all values between two bounds, excluding the bounds. i.e. (lower_bound, upper_bound)
+/// let iv1 = interval.empty
///
-/// ```aiken
-/// interval.entirely_between(10, 100) == Interval {
-/// lower_bound: IntervalBound { bound_type: Finite(10), is_inclusive: False },
-/// upper_bound: IntervalBound { bound_type: Finite(100), is_inclusive: False },
-/// }
-/// ```
-pub fn entirely_between(lower_bound: a, upper_bound: a) -> Interval {
- Interval {
- lower_bound: IntervalBound {
- bound_type: Finite(lower_bound),
- is_inclusive: False,
- },
- upper_bound: IntervalBound {
- bound_type: Finite(upper_bound),
- is_inclusive: False,
- },
- }
-}
-
-/// Create an interval that includes all values greater than the given bound. i.e [lower_bound, +INF)
+/// let iv2 = Interval {
+/// lower_bound: IntervalBound { bound_type: Finite(0), is_inclusive: False },
+/// upper_bound: IntervalBound { bound_type: Finite(0), is_inclusive: False },
+/// }
///
-/// ```aiken
-/// interval.after(10) == Interval {
-/// lower_bound: IntervalBound { bound_type: Finite(10), is_inclusive: True },
-/// upper_bound: IntervalBound { bound_type: PositiveInfinity, is_inclusive: True },
-/// }
-/// ```
-pub fn after(lower_bound: a) -> Interval {
- Interval {
- lower_bound: IntervalBound {
- bound_type: Finite(lower_bound),
- is_inclusive: True,
- },
- upper_bound: IntervalBound {
- bound_type: PositiveInfinity,
- is_inclusive: True,
- },
+/// let iv3 = Interval {
+/// lower_bound: IntervalBound { bound_type: Finite(0), is_inclusive: False },
+/// upper_bound: IntervalBound { bound_type: Finite(100), is_inclusive: False },
+/// }
+///
+/// interval.is_empty(iv1) == True
+/// interval.is_empty(iv2) == True
+/// interval.is_empty(iv3) == False
+///
+/// // Note: Two empty intervals are not necessarily equal.
+/// iv1 != iv2
+/// ```
+pub fn is_empty(self: Interval) -> Bool {
+ let ordering =
+ compare_bound_type(self.lower_bound.bound_type, self.upper_bound.bound_type)
+
+ when ordering is {
+ Greater -> True
+ Equal -> !(self.lower_bound.is_inclusive && self.upper_bound.is_inclusive)
+ Less -> {
+ let is_open_interval =
+ !self.lower_bound.is_inclusive && !self.upper_bound.is_inclusive
+ if is_open_interval {
+ when (self.lower_bound.bound_type, self.upper_bound.bound_type) is {
+ (Finite(lower_bound), Finite(upper_bound)) ->
+ lower_bound + 1 == upper_bound
+ _ -> False
+ }
+ } else {
+ False
+ }
+ }
}
}
-// TODO: Replace 'Int' with a generic 'a' once we have comparable traits.
-
/// Check whether the interval is entirely after the point "a"
///
/// ```aiken
@@ -413,8 +434,6 @@ test is_entirely_after_9() {
!is_entirely_after(entirely_before(10), 5)
}
-// TODO: Replace 'Int' with a generic 'a' once we have comparable traits.
-
/// Check whether the interval is entirely before the point "a"
///
/// ```aiken
@@ -471,112 +490,46 @@ test is_entirely_before_9() {
!is_entirely_before(entirely_after(10), 5)
}
-/// Create an interval that includes all values after (and not including) the given bound. i.e (lower_bound, +INF)
-///
-/// ```aiken
-/// interval.entirely_after(10) == Interval {
-/// lower_bound: IntervalBound { bound_type: Finite(10), is_inclusive: False },
-/// upper_bound: IntervalBound { bound_type: PositiveInfinity, is_inclusive: True },
-/// }
-/// ```
-pub fn entirely_after(lower_bound: a) -> Interval {
- Interval {
- lower_bound: IntervalBound {
- bound_type: Finite(lower_bound),
- is_inclusive: False,
- },
- upper_bound: IntervalBound {
- bound_type: PositiveInfinity,
- is_inclusive: True,
- },
- }
-}
+// ## Combining
-/// Create an interval that includes all values before (and including) the given bound. i.e (-INF, upper_bound]
+/// Computes the smallest interval containing the two given intervals, if any
///
/// ```aiken
-/// interval.before(100) == Interval {
-/// lower_bound: IntervalBound { bound_type: NegativeInfinity, is_inclusive: True },
-/// upper_bound: IntervalBound { bound_type: Finite(100), is_inclusive: True },
-/// }
+/// let iv1 = between(0, 10)
+/// let iv2 = between(2, 14)
+/// hull(iv1, iv2) == between(0, 14)
+///
+/// let iv1 = between(5, 10)
+/// let iv2 = before(0)
+/// hull(iv1, iv2) == before(10)
+///
+/// let iv1 = entirely_after(0)
+/// let iv2 = between(10, 42)
+/// hull(iv1, iv2) = entirely_after(0)
/// ```
-pub fn before(upper_bound: a) -> Interval {
+pub fn hull(iv1: Interval, iv2: Interval) -> Interval {
Interval {
- lower_bound: IntervalBound {
- bound_type: NegativeInfinity,
- is_inclusive: True,
- },
- upper_bound: IntervalBound {
- bound_type: Finite(upper_bound),
- is_inclusive: True,
- },
+ lower_bound: min(iv1.lower_bound, iv2.lower_bound),
+ upper_bound: max(iv1.upper_bound, iv2.upper_bound),
}
}
-/// Create an interval that includes all values before (and not including) the given bound. i.e (-INF, upper_bound)
-///
-/// ```aiken
-/// interval.entirely_before(10) == Interval {
-/// lower_bound: IntervalBound { bound_type: NegativeInfinity, is_inclusive: True },
-/// upper_bound: IntervalBound { bound_type: Finite(10), is_inclusive: False },
-/// }
-/// ```
-pub fn entirely_before(upper_bound: a) -> Interval {
- Interval {
- lower_bound: IntervalBound {
- bound_type: NegativeInfinity,
- is_inclusive: True,
- },
- upper_bound: IntervalBound {
- bound_type: Finite(upper_bound),
- is_inclusive: False,
- },
- }
+test hull_1() {
+ let iv1 = between(0, 10)
+ let iv2 = between(2, 14)
+ hull(iv1, iv2) == between(0, 14)
}
-/// Tells whether an interval is empty; i.e. that is contains no value.
-///
-/// ```aiken
-/// let iv1 = interval.empty()
-///
-/// let iv2 = Interval {
-/// lower_bound: IntervalBound { bound_type: Finite(0), is_inclusive: False },
-/// upper_bound: IntervalBound { bound_type: Finite(0), is_inclusive: False },
-/// }
-///
-/// let iv3 = Interval {
-/// lower_bound: IntervalBound { bound_type: Finite(0), is_inclusive: False },
-/// upper_bound: IntervalBound { bound_type: Finite(100), is_inclusive: False },
-/// }
-///
-/// interval.is_empty(iv1) == True
-/// interval.is_empty(iv2) == True
-/// interval.is_empty(iv3) == False
-///
-/// // Note: Two empty intervals are not necessarily equal.
-/// iv1 != iv2
-/// ```
-pub fn is_empty(self: Interval) -> Bool {
- let ordering =
- compare_bound_type(self.lower_bound.bound_type, self.upper_bound.bound_type)
+test hull_2() {
+ let iv1 = between(5, 10)
+ let iv2 = before(0)
+ hull(iv1, iv2) == before(10)
+}
- when ordering is {
- Greater -> True
- Equal -> !(self.lower_bound.is_inclusive && self.upper_bound.is_inclusive)
- Less -> {
- let is_open_interval =
- !self.lower_bound.is_inclusive && !self.upper_bound.is_inclusive
- if is_open_interval {
- when (self.lower_bound.bound_type, self.upper_bound.bound_type) is {
- (Finite(lower_bound), Finite(upper_bound)) ->
- lower_bound + 1 == upper_bound
- _ -> False
- }
- } else {
- False
- }
- }
- }
+test hull_3() {
+ let iv1 = entirely_after(0)
+ let iv2 = between(10, 42)
+ hull(iv1, iv2) == entirely_after(0)
}
/// Computes the largest interval contains in the two given intervals, if any.
@@ -639,42 +592,89 @@ test intersection_6() {
intersection(iv1, iv2) == entirely_between(0, 10)
}
-/// Computes the smallest interval containing the two given intervals, if any
+/// Return the highest bound of the two.
///
/// ```aiken
-/// let iv1 = between(0, 10)
-/// let iv2 = between(2, 14)
-/// hull(iv1, iv2) == between(0, 14)
-///
-/// let iv1 = between(5, 10)
-/// let iv2 = before(0)
-/// hull(iv1, iv2) == before(10)
+/// let ib1 = IntervalBound { bound_type: Finite(0), is_inclusive: False }
+/// let ib2 = IntervalBound { bound_type: Finite(1), is_inclusive: False }
///
-/// let iv1 = entirely_after(0)
-/// let iv2 = between(10, 42)
-/// hull(iv1, iv2) = entirely_after(0)
+/// interval.max(ib1, ib2) == ib2
/// ```
-pub fn hull(iv1: Interval, iv2: Interval) -> Interval {
- Interval {
- lower_bound: min(iv1.lower_bound, iv2.lower_bound),
- upper_bound: max(iv1.upper_bound, iv2.upper_bound),
+pub fn max(
+ left: IntervalBound,
+ right: IntervalBound,
+) -> IntervalBound {
+ when compare_bound(left, right) is {
+ Less -> right
+ Equal -> left
+ Greater -> left
}
}
-test hull_1() {
- let iv1 = between(0, 10)
- let iv2 = between(2, 14)
- hull(iv1, iv2) == between(0, 14)
+/// Return the smallest bound of the two.
+///
+/// ```aiken
+/// let ib1 = IntervalBound { bound_type: Finite(0), is_inclusive: False }
+/// let ib2 = IntervalBound { bound_type: Finite(1), is_inclusive: False }
+///
+/// interval.min(ib1, ib2) == ib1
+/// ```
+pub fn min(
+ left: IntervalBound,
+ right: IntervalBound,
+) -> IntervalBound {
+ when compare_bound(left, right) is {
+ Less -> left
+ Equal -> left
+ Greater -> right
+ }
}
-test hull_2() {
- let iv1 = between(5, 10)
- let iv2 = before(0)
- hull(iv1, iv2) == before(10)
+fn compare_bound(
+ left: IntervalBound,
+ right: IntervalBound,
+) -> Ordering {
+ when compare_bound_type(left.bound_type, right.bound_type) is {
+ Less -> Less
+ Greater -> Greater
+ Equal ->
+ if left.is_inclusive == right.is_inclusive {
+ Equal
+ } else if left.is_inclusive {
+ Greater
+ } else {
+ Less
+ }
+ }
}
-test hull_3() {
- let iv1 = entirely_after(0)
- let iv2 = between(10, 42)
- hull(iv1, iv2) == entirely_after(0)
+fn compare_bound_type(
+ left: IntervalBoundType,
+ right: IntervalBoundType,
+) -> Ordering {
+ when left is {
+ NegativeInfinity ->
+ when right is {
+ NegativeInfinity -> Equal
+ _ -> Less
+ }
+ PositiveInfinity ->
+ when right is {
+ PositiveInfinity -> Equal
+ _ -> Greater
+ }
+ Finite(left) ->
+ when right is {
+ NegativeInfinity -> Greater
+ PositiveInfinity -> Less
+ Finite(right) ->
+ if left < right {
+ Less
+ } else if left == right {
+ Equal
+ } else {
+ Greater
+ }
+ }
+ }
}
diff --git a/build/packages/aiken-lang-stdlib/lib/aiken/math.ak b/build/packages/aiken-lang-stdlib/lib/aiken/math.ak
index 764152b..dd575e7 100644
--- a/build/packages/aiken-lang-stdlib/lib/aiken/math.ak
+++ b/build/packages/aiken-lang-stdlib/lib/aiken/math.ak
@@ -71,6 +71,152 @@ test clamp_3() {
clamp(7, min: 10, max: 100) == 10
}
+/// The greatest common divisor of two integers.
+///
+/// ```aiken
+/// math.gcd(42, 14) == 14
+/// math.gcd(14, 42) == 14
+/// math.gcd(0, 0) == 0
+/// ```
+pub fn gcd(x: Int, y: Int) -> Int {
+ abs(do_gcd(x, y))
+}
+
+fn do_gcd(x: Int, y: Int) -> Int {
+ when y is {
+ 0 -> x
+ _ -> do_gcd(y, x % y)
+ }
+}
+
+test gcd_test1() {
+ gcd(10, 300) == 10
+}
+
+test gcd_test2() {
+ gcd(-10, 300) == 10
+}
+
+test gcd_test3() {
+ gcd(42, 14) == 14
+}
+
+/// Checks if an integer has a given integer square root x.
+/// The check has constant time complexity $O(1)$.
+///
+/// ```aiken
+/// math.is_sqrt(0, 0)
+/// math.is_sqrt(25, 5)
+/// !math.is_sqrt(25, -5)
+/// math.is_sqrt(44203, 210)
+/// ```
+pub fn is_sqrt(self: Int, x: Int) -> Bool {
+ x * x <= self && ( x + 1 ) * ( x + 1 ) > self
+}
+
+test is_sqrt1() {
+ is_sqrt(44203, 210)
+}
+
+test is_sqrt2() {
+ is_sqrt(975461057789971041, 987654321)
+}
+
+/// The logarithm in base `b` of an element using integer divisions.
+///
+/// ```aiken
+/// math.log(10, base: 2) == 3
+/// math.log(42, base: 2) == 5
+/// math.log(42, base: 3) == 3
+/// math.log(5, base: 0) == 0
+/// math.log(4, base: 4) == 1
+/// math.log(4, base: 42) == 0
+/// ```
+pub fn log(self: Int, base: Int) -> Int {
+ if base <= 0 {
+ 0
+ } else if self == base {
+ 1
+ } else if self < base {
+ 0
+ } else {
+ 1 + log(self / base, base)
+ }
+}
+
+test log_10_2() {
+ log(10, base: 2) == 3
+}
+
+test log_42_2() {
+ log(42, base: 2) == 5
+}
+
+test log_42_3() {
+ log(42, base: 3) == 3
+}
+
+test log_5_0() {
+ log(5, base: 0) == 0
+}
+
+test log_4_4() {
+ log(4, base: 4) == 1
+}
+
+test log_4_43() {
+ log(4, base: 43) == 0
+}
+
+/// The integer logarithm in base 2. Faster than [`log`](#log) in this particular case.
+///
+/// ```aiken
+/// math.log2(1) == 0
+/// math.log2(2) == 1
+/// math.log2(3) == 1
+/// math.log2(4) == 2
+/// math.log2(256) == 8
+/// math.log2(257) == 8
+/// math.log2(511) == 8
+/// math.log2(1025) == 10
+/// ```
+pub fn log2(x: Int) -> Int {
+ expect x > 0
+ let s = builtin.integer_to_bytearray(True, 0, x)
+ let len = builtin.length_of_bytearray(s)
+ let b = builtin.index_bytearray(s, 0)
+ len * 8 - if b < 2 {
+ 8
+ } else if b < 4 {
+ 7
+ } else if b < 8 {
+ 6
+ } else if b < 16 {
+ 5
+ } else if b < 32 {
+ 4
+ } else if b < 64 {
+ 3
+ } else if b < 128 {
+ 2
+ } else {
+ 1
+ }
+}
+
+test log2_matrix() {
+ and {
+ log2(1) == 0,
+ log2(2) == 1,
+ log2(3) == 1,
+ log2(4) == 2,
+ log2(256) == 8,
+ log2(257) == 8,
+ log2(511) == 8,
+ log2(1025) == 10,
+ }
+}
+
/// Return the maximum of two integers.
pub fn max(a: Int, b: Int) -> Int {
if a > b {
@@ -166,7 +312,7 @@ test pow_2_42() {
}
/// Calculates the power of 2 for a given exponent `e`. Much cheaper than
-/// using `pow(2, _)` for small exponents (0 < e < 256).
+/// using `pow(2, _)` for small exponents $0 < e < 256$.
///
/// ```aiken
/// math.pow2(-2) == 0
@@ -214,82 +360,6 @@ test pow2_256() {
pow2(256) == 115792089237316195423570985008687907853269984665640564039457584007913129639936
}
-/// The logarithm in base `b` of an element using integer divisions.
-///
-/// ```aiken
-/// math.log(10, base: 2) == 3
-/// math.log(42, base: 2) == 5
-/// math.log(42, base: 3) == 3
-/// math.log(5, base: 0) == 0
-/// math.log(4, base: 4) == 1
-/// math.log(4, base: 42) == 0
-/// ```
-pub fn log(self: Int, base: Int) -> Int {
- if base <= 0 {
- 0
- } else if self == base {
- 1
- } else if self < base {
- 0
- } else {
- 1 + log(self / base, base)
- }
-}
-
-test log_10_2() {
- log(10, base: 2) == 3
-}
-
-test log_42_2() {
- log(42, base: 2) == 5
-}
-
-test log_42_3() {
- log(42, base: 3) == 3
-}
-
-test log_5_0() {
- log(5, base: 0) == 0
-}
-
-test log_4_4() {
- log(4, base: 4) == 1
-}
-
-test log_4_43() {
- log(4, base: 43) == 0
-}
-
-/// The greatest common divisor of two integers.
-///
-/// ```aiken
-/// math.gcd(42, 14) == 14
-/// math.gcd(14, 42) == 14
-/// math.gcd(0, 0) == 0
-/// ```
-pub fn gcd(x: Int, y: Int) -> Int {
- abs(do_gcd(x, y))
-}
-
-fn do_gcd(x: Int, y: Int) -> Int {
- when y is {
- 0 -> x
- _ -> do_gcd(y, x % y)
- }
-}
-
-test gcd_test1() {
- gcd(10, 300) == 10
-}
-
-test gcd_test2() {
- gcd(-10, 300) == 10
-}
-
-test gcd_test3() {
- gcd(42, 14) == 14
-}
-
/// Calculates the square root of an integer using the [Babylonian
/// method](https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method). This returns either the exact result or the smallest integer
/// nearest to the square root.
@@ -302,6 +372,9 @@ test gcd_test3() {
/// math.sqrt(44203) == Some(210)
/// math.sqrt(-42) == None
/// ```
+///
+/// > [!TIP]
+/// > This function can be quite expensive to perform on-chain. Prefer using [`is_sqrt`](#is_sqrt) whenever possible.
pub fn sqrt(self: Int) -> Option {
if self < 0 {
None
@@ -349,24 +422,3 @@ test sqrt5() {
test sqrt6() {
sqrt(-42) == None
}
-
-/// Checks if an integer has a given integer square root x.
-/// The check has constant time complexity (O(1)).
-///
-/// ```aiken
-/// math.is_sqrt(0, 0)
-/// math.is_sqrt(25, 5)
-/// ! math.is_sqrt(25, -5)
-/// math.is_sqrt(44203, 210)
-/// ```
-pub fn is_sqrt(self: Int, x: Int) -> Bool {
- x * x <= self && ( x + 1 ) * ( x + 1 ) > self
-}
-
-test is_sqrt1() {
- is_sqrt(44203, 210)
-}
-
-test is_sqrt2() {
- is_sqrt(975461057789971041, 987654321)
-}
diff --git a/build/packages/aiken-lang-stdlib/lib/aiken/math/rational.ak b/build/packages/aiken-lang-stdlib/lib/aiken/math/rational.ak
index 6bca643..73c80a7 100644
--- a/build/packages/aiken-lang-stdlib/lib/aiken/math/rational.ak
+++ b/build/packages/aiken-lang-stdlib/lib/aiken/math/rational.ak
@@ -1,16 +1,17 @@
-//// This module implements operations between rational numbers. Internally, rational aren't
-//// automatically reduced as this is **only done on-demand**.
+//// This module implements operations between rational numbers.
////
-//// Thus, for example:
-////
-//// ```aiken
-//// rational.new(2, 3) != rational.new(4, 6)
-//// ```
-////
-//// Comparing rational values should, therefore, only happen after reduction (see [reduce](#reduce)) or via the [compare](#compare) method.
+//// > [!CAUTION] Internally, rational aren't automatically reduced as this is **only done on-demand**.
+//// >
+//// > Thus, for example:
+//// >
+//// > ```aiken
+//// > rational.new(2, 3) != rational.new(4, 6)
+//// > ```
+//// >
+//// > Comparing rational values should, therefore, only happen after reduction (see [reduce](#reduce)) or via the [compare](#compare) method.
use aiken/builtin
-use aiken/list
+use aiken/collection/list
use aiken/math
use aiken/option
@@ -20,6 +21,27 @@ pub opaque type Rational {
denominator: Int,
}
+// ## Constructing
+
+/// Create a new `Rational` from an `Int`.
+///
+/// ```aiken
+/// Some(rational.from_int(14)) == rational.new(14, 1)
+/// Some(rational.from_int(-5)) == rational.new(-5, 1)
+/// Some(rational.from_int(0)) == rational.new(0, 1)
+/// ```
+pub fn from_int(numerator: Int) -> Rational {
+ Rational { numerator, denominator: 1 }
+}
+
+test from_int_1() {
+ and {
+ (from_int(14) == ratio(14, 1))?,
+ (from_int(-5) == ratio(-5, 1))?,
+ (from_int(0) == ratio(0, 1))?,
+ }
+}
+
/// An unsafe constructor for `Rational` values. Assumes that the following invariants are
/// enforced:
///
@@ -61,30 +83,15 @@ test new_1() {
}
}
-/// Get the numerator of a rational value.
-///
-/// ```aiken
-/// expect Some(x) = rational.new(2, 3)
-/// rational.numerator(x) == 2
-/// ```
-pub fn numerator(self: Rational) -> Int {
- self.numerator
-}
-
-test numerator_1() {
- expect Some(x) = new(2, 3)
- expect Some(y) = new(-2, 3)
- expect Some(z) = new(2, -3)
- expect Some(w) = new(-2, -3)
+/// A null `Rational`.
+pub const zero: Rational = Rational { numerator: 0, denominator: 1 }
- and {
- (numerator(x) == 2)?,
- (numerator(y) == -2)?,
- (numerator(z) == -2)?,
- (numerator(w) == 2)?,
- }
+test zero_1() {
+ zero == ratio(0, 1)
}
+// ## Inspecting
+
/// Get the denominator of a rational value.
///
/// ```aiken
@@ -108,469 +115,240 @@ test denominator_1() {
}
}
-/// A null `Rational`.
-pub fn zero() -> Rational {
- Rational { numerator: 0, denominator: 1 }
-}
-
-test zero_1() {
- zero() == ratio(0, 1)
-}
-
-/// Multiplication: the product of two rational values.
-///
-/// ```aiken
-/// expect Some(x) = rational.new(2, 3)
-/// expect Some(y) = rational.new(3, 4)
-///
-/// Some(rational.mul(x, y)) == rational.new(6, 12)
-/// ```
-pub fn mul(left: Rational, right: Rational) -> Rational {
- let Rational { numerator: a_n, denominator: a_d } = left
- let Rational { numerator: b_n, denominator: b_d } = right
- Rational { numerator: a_n * b_n, denominator: a_d * b_d }
-}
-
-test mul_1() {
- mul(ratio(2, 3), ratio(3, 4)) == ratio(6, 12)
-}
-
-test mul_2() {
- mul(ratio(-2, 3), ratio(-3, 4)) == ratio(6, 12)
-}
-
-test mul_3() {
- let result =
- ratio(2, 5)
- |> mul(ratio(1, 8))
- |> mul(ratio(3, 10))
- |> mul(ratio(21, 100))
- |> mul(ratio(3, 5))
- |> mul(ratio(2, 8))
- |> mul(ratio(4, 10))
- |> mul(ratio(22, 100))
- |> reduce
-
- result == ratio(2079, 50000000)
-}
-
-/// Division: quotient of two rational values. Returns `None` when the second
-/// value is null.
-///
-/// ```aiken
-/// expect Some(x) = rational.new(2, 3)
-/// expect Some(y) = rational.new(3, 4)
-///
-/// rational.div(x, y) == rational.new(8, 9)
-/// ```
-pub fn div(left: Rational, right: Rational) -> Option {
- reciprocal(right) |> option.map(mul(left, _))
-}
-
-test div_1() {
- div(ratio(2, 3), ratio(3, 4)) == new(8, 9)
-}
-
-test div_2() {
- div(ratio(2, 3), ratio(-3, 4)) == new(-8, 9)
-}
-
-/// Addition: sum of two rational values
-///
-/// ```aiken
-/// expect Some(x) = rational.new(2, 3)
-/// expect Some(y) = rational.new(3, 4)
-///
-/// Some(rational.add(x, y)) == rational.new(17, 12)
-/// ```
-pub fn add(left: Rational, right: Rational) -> Rational {
- let Rational { numerator: a_n, denominator: a_d } = left
- let Rational { numerator: b_n, denominator: b_d } = right
- Rational { numerator: a_n * b_d + b_n * a_d, denominator: a_d * b_d }
-}
-
-test add_1() {
- add(ratio(2, 3), ratio(3, 4)) == ratio(17, 12)
-}
-
-test add_2() {
- add(ratio(-2, 3), ratio(3, 4)) == ratio(1, 12)
-}
-
-/// Subtraction: difference of two rational values
+/// Get the numerator of a rational value.
///
/// ```aiken
/// expect Some(x) = rational.new(2, 3)
-/// expect Some(y) = rational.new(3, 4)
-///
-/// Some(rational.sub(x, y)) == rational.new(-1, 12)
+/// rational.numerator(x) == 2
/// ```
-pub fn sub(left: Rational, right: Rational) -> Rational {
- let Rational { numerator: a_n, denominator: a_d } = left
- let Rational { numerator: b_n, denominator: b_d } = right
- Rational { numerator: a_n * b_d - b_n * a_d, denominator: a_d * b_d }
-}
-
-test sub_1() {
- sub(ratio(2, 3), ratio(3, 4)) == ratio(-1, 12)
-}
-
-test sub_2() {
- sub(ratio(2, 3), ratio(-3, 4)) == ratio(17, 12)
-}
-
-test sub_3() {
- sub(ratio(-2, 3), ratio(3, 4)) == ratio(-17, 12)
+pub fn numerator(self: Rational) -> Int {
+ self.numerator
}
-/// Create a new `Rational` from an `Int`.
-///
-/// ```aiken
-/// Some(rational.from_int(14)) == rational.new(14, 1)
-/// Some(rational.from_int(-5)) == rational.new(-5, 1)
-/// Some(rational.from_int(0)) == rational.new(0, 1)
-/// ```
-pub fn from_int(numerator: Int) -> Rational {
- Rational { numerator, denominator: 1 }
-}
+test numerator_1() {
+ expect Some(x) = new(2, 3)
+ expect Some(y) = new(-2, 3)
+ expect Some(z) = new(2, -3)
+ expect Some(w) = new(-2, -3)
-test from_int_1() {
and {
- (from_int(14) == ratio(14, 1))?,
- (from_int(-5) == ratio(-5, 1))?,
- (from_int(0) == ratio(0, 1))?,
+ (numerator(x) == 2)?,
+ (numerator(y) == -2)?,
+ (numerator(z) == -2)?,
+ (numerator(w) == 2)?,
}
}
-/// Returns the nearest `Int` between zero and a given `Rational`.
+// ## Modifying
+
+/// Absolute value of a `Rational`.
///
/// ```aiken
-/// expect Some(x) = rational.new(2, 3)
-/// rational.truncate(x) == 0
-///
-/// expect Some(y) = rational.new(44, 14)
-/// rational.truncate(y) == 3
+/// expect Some(x) = rational.new(3, 2)
+/// expect Some(y) = rational.new(-3, 2)
///
-/// expect Some(z) = rational.new(-14, 3)
-/// rational.truncate(z) == -4
+/// rational.abs(x) == x
+/// rational.abs(y) == x
/// ```
-pub fn truncate(self: Rational) -> Int {
+pub fn abs(self: Rational) -> Rational {
let Rational { numerator: a_n, denominator: a_d } = self
- builtin.quotient_integer(a_n, a_d)
+ Rational { numerator: math.abs(a_n), denominator: a_d }
}
-test truncate_1() {
+test abs_examples() {
and {
- (truncate(ratio(5, 2)) == 2)?,
- (truncate(ratio(5, 3)) == 1)?,
- (truncate(ratio(5, 4)) == 1)?,
- (truncate(ratio(5, 5)) == 1)?,
- (truncate(ratio(5, 6)) == 0)?,
- (truncate(ratio(8, 3)) == 2)?,
- (truncate(ratio(-14, 3)) == -4)?,
+ (abs(ratio(5, 2)) == ratio(5, 2))?,
+ (abs(ratio(-5, 2)) == ratio(5, 2))?,
+ (abs(ratio(5, 2)) == abs(ratio(-5, 2)))?,
}
}
-/// Returns the greatest `Int` no greater than a given `Rational`
+/// Change the sign of a `Rational`.
///
/// ```aiken
-/// expect Some(x) = rational.new(2, 3)
-/// rational.floor(x) == 0
-///
-/// expect Some(y) = rational.new(44, 14)
-/// rational.floor(y) == 3
+/// expect Some(x) = rational.new(3, 2)
+/// expect Some(y) = rational.new(-3, 2)
///
-/// expect Some(z) = rational.new(-14, 3)
-/// rational.floor(z) == -5
+/// rational.negate(x) == y
+/// rational.negate(y) == x
/// ```
-pub fn floor(self: Rational) -> Int {
- let Rational { numerator: a_n, denominator: a_d } = self
- a_n / a_d
+pub fn negate(a: Rational) -> Rational {
+ let Rational { numerator: a_n, denominator: a_d } = a
+ Rational { numerator: -a_n, denominator: a_d }
}
-test floor_1() {
+test negate_1() {
and {
- (floor(ratio(5, 2)) == 2)?,
- (floor(ratio(5, 3)) == 1)?,
- (floor(ratio(5, 4)) == 1)?,
- (floor(ratio(5, 5)) == 1)?,
- (floor(ratio(5, 6)) == 0)?,
- (floor(ratio(8, 3)) == 2)?,
- (floor(ratio(-14, 3)) == -5)?,
+ (negate(ratio(5, 2)) == ratio(-5, 2))?,
+ (negate(ratio(-5, 2)) == ratio(5, 2))?,
+ (negate(negate(ratio(5, 2))) == ratio(5, 2))?,
}
}
-/// Returns the smallest `Int` not less than a given `Rational`
+/// Reciprocal of a `Rational` number. That is, a new `Rational` where the
+/// numerator and denominator have been swapped.
///
/// ```aiken
-/// expect Some(x) = rational.new(2, 3)
-/// rational.ceil(x) == 1
-///
-/// expect Some(y) = rational.new(44, 14)
-/// rational.ceil(y) == 4
+/// expect Some(x) = rational.new(2, 5)
+/// rational.reciprocal(x) == rational.new(5, 2)
///
-/// expect Some(z) = rational.new(-14, 3)
-/// rational.ceil(z) == -4
+/// let y = rational.zero
+/// rational.reciprocal(y) == None
/// ```
-pub fn ceil(self: Rational) -> Int {
- let Rational { numerator, denominator } = self
- if builtin.remainder_integer(numerator, denominator) > 0 {
- builtin.quotient_integer(numerator, denominator) + 1
+pub fn reciprocal(self: Rational) -> Option {
+ let Rational { numerator: a_n, denominator: a_d } = self
+ if a_n < 0 {
+ Some(Rational { numerator: -a_d, denominator: -a_n })
+ } else if a_n > 0 {
+ Some(Rational { numerator: a_d, denominator: a_n })
} else {
- builtin.quotient_integer(numerator, denominator)
+ None
}
}
-test ceil_1() {
+test reciprocal_1() {
and {
- (ceil(ratio(13, 5)) == 3)?,
- (ceil(ratio(15, 5)) == 3)?,
- (ceil(ratio(16, 5)) == 4)?,
- (ceil(ratio(-3, 5)) == 0)?,
- (ceil(ratio(-5, 5)) == -1)?,
- (ceil(ratio(-14, 3)) == -4)?,
- (ceil(ratio(-14, 6)) == -2)?,
- (ceil(ratio(44, 14)) == 4)?,
+ (reciprocal(ratio(5, 2)) == new(2, 5))?,
+ (reciprocal(ratio(-5, 2)) == new(-2, 5))?,
+ (reciprocal(ratio(0, 2)) == None)?,
+ (reciprocal(ratio(2, 3)) == new(3, 2))?,
+ (reciprocal(ratio(-2, 3)) == new(-3, 2))?,
}
}
-/// Returns the proper fraction of a given `Rational` `r`. That is, a 2-tuple of
-/// an `Int` and `Rational` (n, f) such that:
+/// Reduce a rational to its irreducible form. This operation makes the
+/// numerator and denominator coprime.
///
-/// - `r = n + f`;
-/// - `n` and `f` have the same sign as `r`;
-/// - `f` has an absolute value less than 1.
-pub fn proper_fraction(self: Rational) -> (Int, Rational) {
- let Rational { numerator, denominator } = self
- (
- builtin.quotient_integer(numerator, denominator),
- Rational {
- numerator: builtin.remainder_integer(numerator, denominator),
- denominator,
- },
- )
+/// ```aiken
+/// expect Some(x) = rational.new(80, 200)
+/// Some(rational.reduce(x)) == rational.new(2, 5)
+/// ```
+pub fn reduce(self: Rational) -> Rational {
+ let Rational { numerator: a_n, denominator: a_d } = self
+ let d = math.gcd(a_n, a_d)
+ Rational { numerator: a_n / d, denominator: a_d / d }
}
-test proper_fraction_1() {
- let r = ratio(10, 7)
- let (n, f) = proper_fraction(r)
+test reduce_1() {
and {
- (n == 1)?,
- (f == ratio(3, 7))?,
- (r == add(from_int(n), f))?,
+ (reduce(ratio(80, 200)) == ratio(2, 5))?,
+ (reduce(ratio(-5, 1)) == ratio(-5, 1))?,
+ (reduce(ratio(0, 3)) == ratio(0, 1))?,
}
}
-test proper_fraction_2() {
- let r = ratio(-10, 7)
- let (n, f) = proper_fraction(r)
- and {
- (n == -1)?,
- (f == ratio(-3, 7))?,
- (r == add(from_int(n), f))?,
- }
-}
+// ## Combining
-test proper_fraction_3() {
- let r = ratio(4, 2)
- let (n, f) = proper_fraction(r)
- and {
- (n == 2)?,
- (f == ratio(0, 2))?,
- (r == add(from_int(n), f))?,
- }
-}
+// ### Arithmetic operations
-/// Round the argument to the nearest whole number. If the argument is
-/// equidistant between two values, the greater value is returned (it
-/// rounds half towards positive infinity).
+/// Addition: sum of two rational values
///
/// ```aiken
/// expect Some(x) = rational.new(2, 3)
-/// rational.round(x) == 1
-///
-/// expect Some(y) = rational.new(3, 2)
-/// rational.round(y) == 2
+/// expect Some(y) = rational.new(3, 4)
///
-/// expect Some(z) = rational.new(-3, 2)
-/// rational.round(z) == -1
+/// Some(rational.add(x, y)) == rational.new(17, 12)
/// ```
-///
-/// ⚠️ This behaves differently than _Haskell_. If you're coming from
-/// `PlutusTx`, beware that in Haskell, rounding on equidistant values depends on the
-/// whole number being odd or even. If you need this behaviour, use [`round_even`](#round_even).
-pub fn round(self: Rational) -> Int {
- let (n, f) = proper_fraction(self)
-
- let is_negative = f.numerator < 0
+pub fn add(left: Rational, right: Rational) -> Rational {
+ let Rational { numerator: a_n, denominator: a_d } = left
+ let Rational { numerator: b_n, denominator: b_d } = right
+ Rational { numerator: a_n * b_d + b_n * a_d, denominator: a_d * b_d }
+}
- when compare(abs(f), ratio(1, 2)) is {
- Less -> n
- Equal if is_negative -> n
- Equal -> n + 1
- Greater if is_negative -> n - 1
- Greater -> n + 1
- }
+test add_1() {
+ add(ratio(2, 3), ratio(3, 4)) == ratio(17, 12)
}
-test round_1() {
- and {
- (round(ratio(10, 7)) == 1)?,
- (round(ratio(11, 7)) == 2)?,
- (round(ratio(3, 2)) == 2)?,
- (round(ratio(5, 2)) == 3)?,
- (round(ratio(-3, 2)) == -1)?,
- (round(ratio(-2, 3)) == -1)?,
- (round(ratio(-10, 7)) == -1)?,
- (round(ratio(4, 2)) == 2)?,
- }
+test add_2() {
+ add(ratio(-2, 3), ratio(3, 4)) == ratio(1, 12)
}
-/// Round the argument to the nearest whole number. If the argument is
-/// equidistant between two values, it returns the value that is even (it
-/// rounds half to even, also known as 'banker's rounding').
+/// Division: quotient of two rational values. Returns `None` when the second
+/// value is null.
///
/// ```aiken
-/// expect Some(w) = rational.new(2, 3)
-/// rational.round_even(w) == 1
-///
-/// expect Some(x) = rational.new(3, 2)
-/// rational.round_even(x) == 2
-///
-/// expect Some(y) = rational.new(5, 2)
-/// rational.round_even(y) == 2
+/// expect Some(x) = rational.new(2, 3)
+/// expect Some(y) = rational.new(3, 4)
///
-/// expect Some(y) = rational.new(-3, 2)
-/// rational.round_even(y) == -2
+/// rational.div(x, y) == rational.new(8, 9)
/// ```
-pub fn round_even(self: Rational) -> Int {
- let (n, f) = proper_fraction(self)
-
- let m =
- when compare(f, ratio(0, 1)) is {
- Less -> -1
- _ -> 1
- }
-
- let is_even = n % 2 == 0
+pub fn div(left: Rational, right: Rational) -> Option {
+ reciprocal(right) |> option.map(mul(left, _))
+}
- when compare(abs(f), ratio(1, 2)) is {
- Less -> n
- Equal if is_even -> n
- Equal -> n + m
- Greater -> n + m
- }
+test div_1() {
+ div(ratio(2, 3), ratio(3, 4)) == new(8, 9)
}
-test round_even_1() {
- and {
- (round_even(ratio(10, 7)) == 1)?,
- (round_even(ratio(11, 7)) == 2)?,
- (round_even(ratio(3, 2)) == 2)?,
- (round_even(ratio(5, 2)) == 2)?,
- (round_even(ratio(-3, 2)) == -2)?,
- (round_even(ratio(-2, 3)) == -1)?,
- (round_even(ratio(-10, 7)) == -1)?,
- (round_even(ratio(4, 2)) == 2)?,
- }
+test div_2() {
+ div(ratio(2, 3), ratio(-3, 4)) == new(-8, 9)
}
-/// Change the sign of a `Rational`.
+/// Multiplication: the product of two rational values.
///
/// ```aiken
-/// expect Some(x) = rational.new(3, 2)
-/// expect Some(y) = rational.new(-3, 2)
+/// expect Some(x) = rational.new(2, 3)
+/// expect Some(y) = rational.new(3, 4)
///
-/// rational.negate(x) == y
-/// rational.negate(y) == x
+/// Some(rational.mul(x, y)) == rational.new(6, 12)
/// ```
-pub fn negate(a: Rational) -> Rational {
- let Rational { numerator: a_n, denominator: a_d } = a
- Rational { numerator: -a_n, denominator: a_d }
+pub fn mul(left: Rational, right: Rational) -> Rational {
+ let Rational { numerator: a_n, denominator: a_d } = left
+ let Rational { numerator: b_n, denominator: b_d } = right
+ Rational { numerator: a_n * b_n, denominator: a_d * b_d }
}
-test negate_1() {
- and {
- (negate(ratio(5, 2)) == ratio(-5, 2))?,
- (negate(ratio(-5, 2)) == ratio(5, 2))?,
- (negate(negate(ratio(5, 2))) == ratio(5, 2))?,
- }
+test mul_1() {
+ mul(ratio(2, 3), ratio(3, 4)) == ratio(6, 12)
}
-/// Absolute value of a `Rational`.
-///
-/// ```aiken
-/// expect Some(x) = rational.new(3, 2)
-/// expect Some(y) = rational.new(-3, 2)
-///
-/// rational.abs(x) == x
-/// rational.abs(y) == x
-/// ```
-pub fn abs(self: Rational) -> Rational {
- let Rational { numerator: a_n, denominator: a_d } = self
- Rational { numerator: math.abs(a_n), denominator: a_d }
+test mul_2() {
+ mul(ratio(-2, 3), ratio(-3, 4)) == ratio(6, 12)
}
-test abs_examples() {
- and {
- (abs(ratio(5, 2)) == ratio(5, 2))?,
- (abs(ratio(-5, 2)) == ratio(5, 2))?,
- (abs(ratio(5, 2)) == abs(ratio(-5, 2)))?,
- }
+test mul_3() {
+ let result =
+ ratio(2, 5)
+ |> mul(ratio(1, 8))
+ |> mul(ratio(3, 10))
+ |> mul(ratio(21, 100))
+ |> mul(ratio(3, 5))
+ |> mul(ratio(2, 8))
+ |> mul(ratio(4, 10))
+ |> mul(ratio(22, 100))
+ |> reduce
+
+ result == ratio(2079, 50000000)
}
-/// Reciprocal of a `Rational` number. That is, a new `Rational` where the
-/// numerator and denominator have been swapped.
+/// Subtraction: difference of two rational values
///
/// ```aiken
-/// expect Some(x) = rational.new(2, 5)
-/// rational.reciprocal(x) == rational.new(5, 2)
+/// expect Some(x) = rational.new(2, 3)
+/// expect Some(y) = rational.new(3, 4)
///
-/// let y = rational.zero()
-/// rational.reciprocal(y) == None
+/// Some(rational.sub(x, y)) == rational.new(-1, 12)
/// ```
-pub fn reciprocal(self: Rational) -> Option {
- let Rational { numerator: a_n, denominator: a_d } = self
- if a_n < 0 {
- Some(Rational { numerator: -a_d, denominator: -a_n })
- } else if a_n > 0 {
- Some(Rational { numerator: a_d, denominator: a_n })
- } else {
- None
- }
-}
-
-test reciprocal_1() {
- and {
- (reciprocal(ratio(5, 2)) == new(2, 5))?,
- (reciprocal(ratio(-5, 2)) == new(-2, 5))?,
- (reciprocal(ratio(0, 2)) == None)?,
- (reciprocal(ratio(2, 3)) == new(3, 2))?,
- (reciprocal(ratio(-2, 3)) == new(-3, 2))?,
- }
+pub fn sub(left: Rational, right: Rational) -> Rational {
+ let Rational { numerator: a_n, denominator: a_d } = left
+ let Rational { numerator: b_n, denominator: b_d } = right
+ Rational { numerator: a_n * b_d - b_n * a_d, denominator: a_d * b_d }
}
-/// Reduce a rational to its irreducible form. This operation makes the
-/// numerator and denominator coprime.
-///
-/// ```aiken
-/// expect Some(x) = rational.new(80, 200)
-/// Some(rational.reduce(x)) == rational.new(2, 5)
-/// ```
-pub fn reduce(self: Rational) -> Rational {
- let Rational { numerator: a_n, denominator: a_d } = self
- let d = math.gcd(a_n, a_d)
- Rational { numerator: a_n / d, denominator: a_d / d }
+test sub_1() {
+ sub(ratio(2, 3), ratio(3, 4)) == ratio(-1, 12)
}
-test reduce_1() {
- and {
- (reduce(ratio(80, 200)) == ratio(2, 5))?,
- (reduce(ratio(-5, 1)) == ratio(-5, 1))?,
- (reduce(ratio(0, 3)) == ratio(0, 1))?,
- }
+test sub_2() {
+ sub(ratio(2, 3), ratio(-3, 4)) == ratio(17, 12)
+}
+
+test sub_3() {
+ sub(ratio(-2, 3), ratio(3, 4)) == ratio(-17, 12)
}
+// ### Ordering
+
/// Compare two rationals for an ordering. This is safe to use even for
/// non-reduced rationals.
///
@@ -697,6 +475,8 @@ test compare_with_lt() {
lt(x, y)? && !lt(y, x)? && !lt(x, x)?
}
+// ### Means
+
/// Calculate the arithmetic mean between two `Rational` values.
///
/// ```aiken
@@ -709,7 +489,7 @@ test compare_with_lt() {
/// rational.compare(result, y) == Equal
/// ```
pub fn arithmetic_mean(self: List) -> Option