From 4ad661a82b09a47ff68fc68c4832d99b792414c6 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 2 Jun 2024 10:22:01 +0200 Subject: [PATCH 01/25] Optimize the lazy Sequence::takeEnd() --- CHANGELOG.md | 6 ++++++ src/Sequence/Lazy.php | 24 +++++++++++++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af94f56..84a9701 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [Unreleased] + +### Changed + +- A lazy `Sequence::takeEnd()` no longer loads the whole sequence in memory, only the number of elements taken + 1. + ## 5.4.0 - 2024-05-29 ### Added diff --git a/src/Sequence/Lazy.php b/src/Sequence/Lazy.php index 914a994..e02654e 100644 --- a/src/Sequence/Lazy.php +++ b/src/Sequence/Lazy.php @@ -469,9 +469,27 @@ static function(RegisterCleanup $register) use ($values, $size): \Generator { */ public function takeEnd(int $size): Implementation { - // this cannot be optimised as the whole generator needs to be loaded - // in order to know the elements to drop - return $this->load()->takeEnd($size); + $values = $this->values; + + return new self( + static function(RegisterCleanup $register) use ($values, $size): \Generator { + $buffer = []; + $count = 0; + + foreach ($values($register) as $value) { + $buffer[] = $value; + ++$count; + + if ($count > $size) { + \array_shift($buffer); + } + } + + foreach ($buffer as $value) { + yield $value; + } + }, + ); } /** From ffb8ba934939454e54233dc6b94d479fc3f1625d Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 2 Jun 2024 10:51:21 +0200 Subject: [PATCH 02/25] add mkdocs --- .github/workflows/documentation.yml | 27 +++++ .gitignore | 1 + Makefile | 6 + docs/assets/favicon.png | Bin 0 -> 2319 bytes docs/assets/fonts/MonaspaceNeon-Regular.woff | Bin 0 -> 47308 bytes docs/assets/logo.svg | 24 ++++ docs/assets/stylesheets/extra.css | 113 +++++++++++++++++++ mkdocs.yml | 109 ++++++++++++++++++ 8 files changed, 280 insertions(+) create mode 100644 .github/workflows/documentation.yml create mode 100644 Makefile create mode 100644 docs/assets/favicon.png create mode 100644 docs/assets/fonts/MonaspaceNeon-Regular.woff create mode 100644 docs/assets/logo.svg create mode 100644 docs/assets/stylesheets/extra.css create mode 100644 mkdocs.yml diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 0000000..ac9da56 --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,27 @@ +name: ci +on: + push: + branches: [master] +permissions: + contents: write +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Configure Git Credentials + run: | + git config user.name github-actions[bot] + git config user.email 41898282+github-actions[bot]@users.noreply.github.com + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV + - uses: actions/cache@v4 + with: + key: mkdocs-material-${{ env.cache_id }} + path: .cache + restore-keys: | + mkdocs-material- + - run: pip install mkdocs-material + - run: mkdocs gh-deploy --force diff --git a/.gitignore b/.gitignore index 987e2a2..a696500 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ composer.lock vendor +.cache diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6b5c6f1 --- /dev/null +++ b/Makefile @@ -0,0 +1,6 @@ +# This command is intended to be run on your computer +serve-doc: + docker run --rm -it -p 8000:8000 -v ${PWD}:/docs squidfunk/mkdocs-material + +build-doc: + docker run --rm -it -v ${PWD}:/docs squidfunk/mkdocs-material build diff --git a/docs/assets/favicon.png b/docs/assets/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..08dee3a38bab0f4b6161a9ea6c575a347daaa737 GIT binary patch literal 2319 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

m)-Jo-MAPW+|=Jl-g@ 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 z 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 literal 0 HcmV?d00001 diff --git a/docs/assets/logo.svg b/docs/assets/logo.svg new file mode 100644 index 0000000..6a5d322 --- /dev/null +++ b/docs/assets/logo.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/stylesheets/extra.css b/docs/assets/stylesheets/extra.css new file mode 100644 index 0000000..e4aa2d4 --- /dev/null +++ b/docs/assets/stylesheets/extra.css @@ -0,0 +1,113 @@ +@font-face { + font-family: "Monaspace Neon"; + font-weight: normal; + font-style: normal; + src: url("../fonts/MonaspaceNeon-Regular.woff"); +} + +:root { + --md-code-font: "Monaspace Neon"; +} + +:root { + --light-md-code-hl-number-color: #f76d47; + --light-md-code-hl-function-color: #6384b9; + --light-md-code-hl-operator-color: #39adb5; + --light-md-code-hl-constant-color: #7c4dff; + --light-md-code-hl-string-color: #9fc06f; + --light-md-code-hl-punctuation-color: #39adb5; + --light-md-code-hl-keyword-color: #7c4dff; + --light-md-code-hl-variable-color: #80cbc4; + --light-md-code-hl-comment-color: #ccd7da; + --light-md-code-bg-color: #fafafa; + --light-md-code-fg-color: #ffb62c; + --light-md-code-hl-variable-color: #6384b9; + --dark-md-code-hl-number-color: #f78c6c; + --dark-md-code-hl-function-color: #82aaff; + --dark-md-code-hl-operator-color: #89ddff; + --dark-md-code-hl-constant-color: #c792ea; + --dark-md-code-hl-string-color: #c3e88d; + --dark-md-code-hl-punctuation-color: #89ddff; + --dark-md-code-hl-keyword-color: #c792ea; + --dark-md-code-hl-variable-color: #e8f9f9; + --dark-md-code-hl-comment-color: #546e7a; + --dark-md-code-bg-color: #263238; + --dark-md-code-fg-color: #ffcb6b; + --dark-md-code-hl-variable-color: #82aaff; +} + +@media (prefers-color-scheme: light) { + .language-php > * { + --md-code-hl-number-color: var(--light-md-code-hl-number-color); + --md-code-hl-function-color: var(--light-md-code-hl-function-color); + --md-code-hl-operator-color: var(--light-md-code-hl-operator-color); + --md-code-hl-constant-color: var(--light-md-code-hl-constant-color); + --md-code-hl-string-color: var(--light-md-code-hl-string-color); + --md-code-hl-punctuation-color: var(--light-md-code-hl-punctuation-color); + --md-code-hl-keyword-color: var(--light-md-code-hl-keyword-color); + --md-code-hl-variable-color: var(--light-md-code-hl-variable-color); + --md-code-hl-comment-color: var(--light-md-code-hl-comment-color); + --md-code-bg-color: var(--light-md-code-bg-color); + --md-code-fg-color: var(--light-md-code-fg-color); + } + + .language-php .na { + --md-code-hl-variable-color: var(--light-md-code-hl-variable-color); + } +} + +[data-md-color-media="(prefers-color-scheme: light)"] .language-php > * { + --md-code-hl-number-color: var(--light-md-code-hl-number-color); + --md-code-hl-function-color: var(--light-md-code-hl-function-color); + --md-code-hl-operator-color: var(--light-md-code-hl-operator-color); + --md-code-hl-constant-color: var(--light-md-code-hl-constant-color); + --md-code-hl-string-color: var(--light-md-code-hl-string-color); + --md-code-hl-punctuation-color: var(--light-md-code-hl-punctuation-color); + --md-code-hl-keyword-color: var(--light-md-code-hl-keyword-color); + --md-code-hl-variable-color: var(--light-md-code-hl-variable-color); + --md-code-hl-comment-color: var(--light-md-code-hl-comment-color); + --md-code-bg-color: var(--light-md-code-bg-color); + --md-code-fg-color: var(--light-md-code-fg-color); +} + +[data-md-color-media="(prefers-color-scheme: light)"] .language-php .na { + --md-code-hl-variable-color: var(--light-md-code-hl-variable-color); +} + +@media (prefers-color-scheme: dark) { + .language-php > * { + --md-code-hl-number-color: var(--dark-md-code-hl-number-color); + --md-code-hl-function-color: var(--dark-md-code-hl-function-color); + --md-code-hl-operator-color: var(--dark-md-code-hl-operator-color); + --md-code-hl-constant-color: var(--dark-md-code-hl-constant-color); + --md-code-hl-string-color: var(--dark-md-code-hl-string-color); + --md-code-hl-punctuation-color: var(--dark-md-code-hl-punctuation-color); + --md-code-hl-keyword-color: var(--dark-md-code-hl-keyword-color); + --md-code-hl-variable-color: var(--dark-md-code-hl-variable-color); + --md-code-hl-comment-color: var(--dark-md-code-hl-comment-color); + --md-code-bg-color: var(--dark-md-code-bg-color); + --md-code-fg-color: var(--dark-md-code-fg-color); + } + + .language-php .na { + --md-code-hl-variable-color: var(--dark-md-code-hl-variable-color); + } +} + +[data-md-color-media="(prefers-color-scheme: dark)"] .language-php > * { + --md-code-hl-number-color: var(--dark-md-code-hl-number-color); + --md-code-hl-function-color: var(--dark-md-code-hl-function-color); + --md-code-hl-operator-color: var(--dark-md-code-hl-operator-color); + --md-code-hl-constant-color: var(--dark-md-code-hl-constant-color); + --md-code-hl-string-color: var(--dark-md-code-hl-string-color); + --md-code-hl-punctuation-color: var(--dark-md-code-hl-punctuation-color); + --md-code-hl-keyword-color: var(--dark-md-code-hl-keyword-color); + --md-code-hl-variable-color: var(--dark-md-code-hl-variable-color); + --md-code-hl-comment-color: var(--dark-md-code-hl-comment-color); + --md-code-bg-color: var(--dark-md-code-bg-color); + --md-code-fg-color: var(--dark-md-code-fg-color); +} + +[data-md-color-media="(prefers-color-scheme: dark)"] .language-php .na { + --md-code-hl-variable-color: var(--dark-md-code-hl-variable-color); +} diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..ec71040 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,109 @@ +site_name: Innmind +repo_name: innmind/immutable + +nav: + - Getting Started: README.md + - Philosophy: PHILOSOPHY.md + - Structures: + - structures/index.md + - Sequence: structures/sequence.md + - Set: structures/set.md + - Map: structures/map.md + - Str: structures/str.md + - RegExp: structures/regExp.md + - Maybe: structures/maybe.md + - Either: structures/either.md + - Validation: structures/validation.md + - State: structures/state.md + - Fold: structures/fold.md + - Monoids: MONOIDS.md + - Use cases: + - use-cases/index.md + - How to read a file: use-cases/lazy-file.md + - Parsing strings: use-cases/parsing.md + - Testing: BLACKBOX.md + +theme: + name: material + logo: assets/logo.svg + favicon: assets/favicon.png + font: false + features: + - content.code.copy + - content.code.annotate + - navigation.tracking + - navigation.tabs + - navigation.tabs.sticky + - navigation.sections + - navigation.expand + - navigation.indexes + - navigation.top + - navigation.footer + - search.suggest + - search.highlight + - content.action.edit + palette: + # Palette toggle for automatic mode + - media: "(prefers-color-scheme)" + toggle: + icon: material/brightness-auto + name: Switch to light mode + primary: blue + accent: deep orange + # Palette toggle for light mode + - media: "(prefers-color-scheme: light)" + scheme: default + toggle: + icon: material/brightness-7 + name: Switch to dark mode + primary: blue + accent: deep orange + # Palette toggle for dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + toggle: + icon: material/brightness-4 + name: Switch to system preference + primary: blue + accent: deep orange + +markdown_extensions: + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + extend_pygments_lang: + - name: php + lang: php + options: + startinline: true + - pymdownx.inlinehilite + - pymdownx.snippets + - attr_list + - md_in_html + - pymdownx.superfences + - abbr + - admonition + - pymdownx.details: + - pymdownx.tabbed: + alternate_style: true + - toc: + permalink: true + - footnotes + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + +extra_css: + - assets/stylesheets/extra.css + +plugins: + - search + - privacy + +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/Innmind/immutable + - icon: fontawesome/brands/x-twitter + link: https://twitter.com/Baptouuuu From 8286f612c1ceb5a391f8cad12f56c477d7c714e6 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 2 Jun 2024 10:52:10 +0200 Subject: [PATCH 03/25] update readme --- docs/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/README.md b/docs/README.md index 1d4c8c8..57b3361 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,3 +1,9 @@ +--- +hide: + - navigation + - toc +--- + # Getting Started This project brings a set of immutable data structure to bring a uniformity on how to handle data. From c0bfcaa14aa914a15f1b6891028e5876925c79aa Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 2 Jun 2024 11:00:12 +0200 Subject: [PATCH 04/25] move use cases --- docs/README.md | 5 ----- docs/use-cases/index.md | 3 +++ docs/{LAZY_FILE.md => use-cases/lazy-file.md} | 0 docs/{PARSING.md => use-cases/parsing.md} | 4 ++-- 4 files changed, 5 insertions(+), 7 deletions(-) create mode 100644 docs/use-cases/index.md rename docs/{LAZY_FILE.md => use-cases/lazy-file.md} (100%) rename docs/{PARSING.md => use-cases/parsing.md} (95%) diff --git a/docs/README.md b/docs/README.md index 57b3361..580ea5f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -35,11 +35,6 @@ See the documentation for each structure to understand how to use them. All structures are typed with [`vimeo/psalm`](https://psalm.dev), you must use it in order to verify that you use this library correctly. -## Use cases - -- [How to read a file](LAZY_FILE.md) -- [Parsing strings](PARSING.md) - ## Testing This package provides sets that can be used with [BlackBox](BLACKBOX.md). diff --git a/docs/use-cases/index.md b/docs/use-cases/index.md new file mode 100644 index 0000000..e73cff8 --- /dev/null +++ b/docs/use-cases/index.md @@ -0,0 +1,3 @@ +# Use cases + +In this chapter you'll find a set of use cases using data structures already shown in previous chapters. diff --git a/docs/LAZY_FILE.md b/docs/use-cases/lazy-file.md similarity index 100% rename from docs/LAZY_FILE.md rename to docs/use-cases/lazy-file.md diff --git a/docs/PARSING.md b/docs/use-cases/parsing.md similarity index 95% rename from docs/PARSING.md rename to docs/use-cases/parsing.md index 20c5883..c1721d1 100644 --- a/docs/PARSING.md +++ b/docs/use-cases/parsing.md @@ -33,8 +33,8 @@ $parse = function(string $string): Maybe { return Maybe::all($components->get('topLevel'), $components->get('subType')) ->flatMap(fn(Str $topLevel, Str $subType) => MediaType::of( - $topLevel->toSstring(), - $subType->toSstring(), + $topLevel->toString(), + $subType->toString(), )); } From b1a3a3d0cb5f03610f793b4481cf7af2e85af697 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 2 Jun 2024 11:04:30 +0200 Subject: [PATCH 05/25] update the testing doc --- docs/README.md | 4 ---- docs/{BLACKBOX.md => testing.md} | 22 +++++++--------------- mkdocs.yml | 2 +- 3 files changed, 8 insertions(+), 20 deletions(-) rename docs/{BLACKBOX.md => testing.md} (58%) diff --git a/docs/README.md b/docs/README.md index 580ea5f..152ced7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -34,7 +34,3 @@ This library provides the 10 following structures: See the documentation for each structure to understand how to use them. All structures are typed with [`vimeo/psalm`](https://psalm.dev), you must use it in order to verify that you use this library correctly. - -## Testing - -This package provides sets that can be used with [BlackBox](BLACKBOX.md). diff --git a/docs/BLACKBOX.md b/docs/testing.md similarity index 58% rename from docs/BLACKBOX.md rename to docs/testing.md index 7bf66c4..2bcee54 100644 --- a/docs/BLACKBOX.md +++ b/docs/testing.md @@ -1,21 +1,13 @@ -# `innmind/black-box` sets +--- +hide: + - navigation +--- -This package provides additional sets for [`innmind/black-box`](https://packagist.org/packages/innmind/black-box) so you can more easily generate: `Map`s, `Set`s and `Sequence`s. +# Testing -For the 3 `::of()` method you can pass as last parameter an instance of `Innmind\BlackBox\Set\Intergers` to specify the range of elements to generate. By default it's between `0` and `100`, depending on the values you generate you may to lower the upper bound to reduce the memory footprint and speed up your tests. +This package provides additional sets for [`innmind/black-box`](https://packagist.org/packages/innmind/black-box) so you can more easily generate: `Set`s and `Sequence`s. -## `Map` - -```php -use Fixtures\Innmind\Immutable\Map; -use Innmind\BlackBox\Set; - -/** @var Set> */ -$set = Map::of( - Set\Integers::any(), - Set\Strings::any(), -); -``` +For the 2 `::of()` method you can pass as last parameter an instance of `Innmind\BlackBox\Set\Intergers` to specify the range of elements to generate. By default it's between `0` and `100`, depending on the values you generate you may want to lower the upper bound to reduce the memory footprint and speed up your tests. ## `Set` diff --git a/mkdocs.yml b/mkdocs.yml index ec71040..a1901a5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -21,7 +21,7 @@ nav: - use-cases/index.md - How to read a file: use-cases/lazy-file.md - Parsing strings: use-cases/parsing.md - - Testing: BLACKBOX.md + - Testing: testing.md theme: name: material From 38cdbd4c77e8bf2df64ae3fbfc18789cc91f49c4 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 2 Jun 2024 11:11:57 +0200 Subject: [PATCH 06/25] update monoids --- docs/MONOIDS.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/MONOIDS.md b/docs/MONOIDS.md index ae46bd4..f52fd68 100644 --- a/docs/MONOIDS.md +++ b/docs/MONOIDS.md @@ -1,3 +1,9 @@ +--- +hide: + - navigation + - toc +--- + # Monoids Monoids describe a way to combine two values of a given type. A monoid contains an identity value that when combined with another value doesn't change its value. The combine operation has to be associative meaning `combine(a, combine(b, c))` is the same as `combine(combine(a, b), c)`. @@ -5,6 +11,7 @@ Monoids describe a way to combine two values of a given type. A monoid contains A simple monoid is an addition because adding `0` (the identity value) to any other integer won't change the value and `add(1, add(2, 3))` is the the same result as `add(add(1, 2), 3)` (both return 6). This library comes with a few monoids: + - `Innmind\Immutable\Monoid\Concat` to append 2 instances of `Innmind\Immutable\Str` together - `Innmind\Immutable\Monoid\Append` to append 2 instances of `Innmind\Immutable\Sequence` together - `Innmind\Immutable\Monoid\MergeSet` to append 2 instances of `Innmind\Immutable\Set` together @@ -38,3 +45,5 @@ return static function() { } }; ``` + +You can take a look at the [proofs](https://github.com/Innmind/Immutable/tree/master/proofs/monoid) for this package monoids to better understand how thiw works. From fcff5af16b8c2cd57e771e4e6931f60e0566895f Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 2 Jun 2024 11:13:22 +0200 Subject: [PATCH 07/25] fix list --- docs/PHILOSOPHY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/PHILOSOPHY.md b/docs/PHILOSOPHY.md index 36640da..5b6f600 100644 --- a/docs/PHILOSOPHY.md +++ b/docs/PHILOSOPHY.md @@ -1,6 +1,7 @@ # Philosophy This project was born after working with other programming languages (like [Scala](https://scala-lang.org)) and discovering [functional programming](https://en.wikipedia.org/wiki/Functional_programming). This taught me 2 things: + - higher order functions on data structures - immutability From 7269752516dcb968b123d1e7f8b4c96b2e77b68d Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 2 Jun 2024 11:14:33 +0200 Subject: [PATCH 08/25] hide navigation --- docs/PHILOSOPHY.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/PHILOSOPHY.md b/docs/PHILOSOPHY.md index 5b6f600..480e0fc 100644 --- a/docs/PHILOSOPHY.md +++ b/docs/PHILOSOPHY.md @@ -1,3 +1,9 @@ +--- +hide: + - navigation + - toc +--- + # Philosophy This project was born after working with other programming languages (like [Scala](https://scala-lang.org)) and discovering [functional programming](https://en.wikipedia.org/wiki/Functional_programming). This taught me 2 things: From 4bd9bf6f887aac8416f29bfcb1b161c5c98b3f26 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 2 Jun 2024 11:16:21 +0200 Subject: [PATCH 09/25] move structures section to its own chapter --- docs/README.md | 19 ------------------- docs/structures/index.md | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 19 deletions(-) create mode 100644 docs/structures/index.md diff --git a/docs/README.md b/docs/README.md index 152ced7..97195ab 100644 --- a/docs/README.md +++ b/docs/README.md @@ -15,22 +15,3 @@ Before diving in the documentation you may want to read about the [philosophy](P ```sh composer require innmind/immutable ``` - -## Structures - -This library provides the 10 following structures: - -- [`Sequence`](SEQUENCE.md) -- [`Set`](SET.md) -- [`Map`](MAP.md) -- [`Str`](STR.md) -- [`RegExp`](REGEXP.md) -- [`Maybe`](MAYBE.md) -- [`Either`](EITHER.md) -- [`Validation`](VALIDATION.md) -- [`State`](STATE.md) -- [`Fold`](FOLD.md) - -See the documentation for each structure to understand how to use them. - -All structures are typed with [`vimeo/psalm`](https://psalm.dev), you must use it in order to verify that you use this library correctly. diff --git a/docs/structures/index.md b/docs/structures/index.md new file mode 100644 index 0000000..6fb3e34 --- /dev/null +++ b/docs/structures/index.md @@ -0,0 +1,18 @@ +# Structures + +This library provides the 10 following structures: + +- [`Sequence`](sequence.md) +- [`Set`](set.md) +- [`Map`](map.md) +- [`Str`](str.md) +- [`RegExp`](regexp.md) +- [`Maybe`](maybe.md) +- [`Either`](either.md) +- [`Validation`](validation.md) +- [`State`](state.md) +- [`Fold`](fold.md) + +See the documentation for each structure to understand how to use them. + +All structures are typed with [`vimeo/psalm`](https://psalm.dev), you must use it in order to verify that you use this library correctly. From cba606b18d674fff37842c9200ef85b92c3e8ce1 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 2 Jun 2024 11:33:07 +0200 Subject: [PATCH 10/25] move sequence doc --- docs/{SEQUENCE.md => structures/sequence.md} | 39 +++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) rename docs/{SEQUENCE.md => structures/sequence.md} (92%) diff --git a/docs/SEQUENCE.md b/docs/structures/sequence.md similarity index 92% rename from docs/SEQUENCE.md rename to docs/structures/sequence.md index 07863e8..2c161fe 100644 --- a/docs/SEQUENCE.md +++ b/docs/structures/sequence.md @@ -27,7 +27,8 @@ $sequence = Sequence::defer((function() { The method ask a generator that will provide the elements. Once the elements are loaded they are kept in memory so you can run multiple operations on it without loading the file twice. -> **Warning** beware of the case where the source you read the elements is not altered before the first use of the sequence. +!!! warning "" + Beware of the case where the source you read the elements is not altered before the first use of the sequence. ## `::lazy()` @@ -39,7 +40,8 @@ $sequence = Sequence::lazy(function() { }); ``` -> **Warning** since the elements are reloaded each time the immutability responsability is up to you because the source may change or if you generate objects it will generate new objects each time (so if you make strict comparison it will fail). +!!! warning "" + Since the elements are reloaded each time the immutability responsability is up to you because the source may change or if you generate objects it will generate new objects each time (so if you make strict comparison it will fail). ## `::lazyStartingWith()` @@ -49,8 +51,8 @@ Same as `::lazy()` except you don't need to manually build the generator. $sequence = Sequence::lazyStartingWith(1, 2, 3); ``` -> **Note** -> this is useful when you know the first items of the sequence and you'll `append` another lazy sequence at the end. +!!! note "" + This is useful when you know the first items of the sequence and you'll `append` another lazy sequence at the end. ## `::mixed()` @@ -176,7 +178,9 @@ This is similar to `->filter()` with the advantage of psalm understanding the ty ```php use Innmind\Immutable\Predicate\Instance; -$sequence = Sequence::of(null, new \stdClass, 'foo')->keep(Instance::of('stdClass')); +$sequence = Sequence::of(null, new \stdClass, 'foo')->keep( + Instance::of('stdClass'), +); $sequence; // Sequence ``` @@ -194,9 +198,11 @@ $sequence->equals(Sequence::ints(1, 3)); Use this method to call a function for each element of the sequence. Since this structure is immutable it returns a `SideEffect` object, as its name suggest it is the only place acceptable to create side effects. ```php -$sideEffect = Sequence::strings('hello', 'world')->foreach(function(string $string): void { - echo $string.' '; -}); +$sideEffect = Sequence::strings('hello', 'world')->foreach( + function(string $string): void { + echo $string.' '; + }, +); ``` In itself the `SideEffect` object has no use except to avoid psalm complaining that the `foreach` method is not used. @@ -220,7 +226,10 @@ $map static fn($group) => $group, static fn() => Sequence::strings(), ) - ->equals(Sequence::strings('http://example.com', 'http://example.com/foo')); // true + ->equals(Sequence::strings( + 'http://example.com', + 'http://example.com/foo', + )); // true $map ->get('https') ->match( @@ -384,7 +393,7 @@ $sequence->equals(Sequence::ints(1, 2, 3, 4)); ## `->fold()` -This is similar to the `reduce` method but only takes a [`Monoid`](MONOIDS.md) as an argument. +This is similar to the `reduce` method but only takes a [`Monoid`](../MONOIDS.md) as an argument. ```php use Innmind\Immutable\Monoid\Concat; @@ -486,7 +495,8 @@ $result = sum(Sequence::of(1, 2, 3, 4)); $result; // 10 ``` -> **Warning** for lasy sequences bear in mind that the values will be kept in memory while the first call to `->match` didn't return. +!!! warning "" + For lazy sequences bear in mind that the values will be kept in memory while the first call to `->match` didn't return. ## `->zip()` @@ -542,7 +552,8 @@ This methods allows to rearrange the elements of the Sequence. This is especiall An example would be to rearrange a list of chunks from a file into lines: ```php -$chunks = ['fo', "o\n", 'ba', "r\n", 'ba', "z\n"]; // let's pretend this comes from a stream +// let's pretend this comes from a stream +$chunks = ['fo', "o\n", 'ba', "r\n", 'ba', "z\n"]; $lines = Sequence::of(...$chunks) ->map(Str::of(...)) ->aggregate(static fn($a, $b) => $a->append($b->toString())->split("\n")) @@ -552,8 +563,8 @@ $lines = Sequence::of(...$chunks) $lines; // ['foo', 'bar', 'baz', ''] ``` -> **Note** -> The `flatMap` is here in case there is only one chunk in the sequence, in which case the `aggregate` is not called +!!! note "" + The `flatMap` is here in case there is only one chunk in the sequence, in which case the `aggregate` is not called ## `->memoize()` From 6c4af6606ae42198f83a8c534f9b922abd64c889 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 2 Jun 2024 11:54:47 +0200 Subject: [PATCH 11/25] rearrange methods by groups --- docs/structures/sequence.md | 552 ++++++++++++++++++------------------ 1 file changed, 283 insertions(+), 269 deletions(-) diff --git a/docs/structures/sequence.md b/docs/structures/sequence.md index 2c161fe..b02b037 100644 --- a/docs/structures/sequence.md +++ b/docs/structures/sequence.md @@ -2,7 +2,9 @@ A sequence is an ordered list of elements, think of it like an array such as `[1, 'a', new stdClass]` or a `list` in the [Psalm](http://psalm.dev) nomenclature. -## `::of()` +## Named constructors + +### `::of()` The `of` static method allows you to create a new sequence with all the elements passed as arguments. @@ -13,7 +15,7 @@ use Innmind\Immutable\Sequence; Sequence::of(1, 2, 3, $etc); ``` -## `::defer()` +### `::defer()` This named constructor is for advanced use cases where you want the data of your sequence to be loaded upon use only and not initialisation. @@ -30,7 +32,7 @@ The method ask a generator that will provide the elements. Once the elements are !!! warning "" Beware of the case where the source you read the elements is not altered before the first use of the sequence. -## `::lazy()` +### `::lazy()` This is similar to `::defer()` with the exception that the elements are not kept in memory but reloaded upon each use. @@ -43,7 +45,7 @@ $sequence = Sequence::lazy(function() { !!! warning "" Since the elements are reloaded each time the immutability responsability is up to you because the source may change or if you generate objects it will generate new objects each time (so if you make strict comparison it will fail). -## `::lazyStartingWith()` +### `::lazyStartingWith()` Same as `::lazy()` except you don't need to manually build the generator. @@ -54,27 +56,29 @@ $sequence = Sequence::lazyStartingWith(1, 2, 3); !!! note "" This is useful when you know the first items of the sequence and you'll `append` another lazy sequence at the end. -## `::mixed()` +### `::mixed()` This is a shortcut for `::of(mixed ...$mixed)`. -## `::ints()` +### `::ints()` This is a shortcut for `::of(int ...$ints)`. -## `::floats()` +### `::floats()` This is a shortcut for `::of(float ...$floats)`. -## `::strings()` +### `::strings()` This is a shortcut for `::of(string ...$strings)`. -## `::objects()` +### `::objects()` This is a shortcut for `::of(object ...$objects)`. -## `->__invoke()` +## Add values + +### `->__invoke()` Augment the sequence with a new element. @@ -84,11 +88,22 @@ $sequence = ($sequence)(2); $sequence->equals(Sequence::ints(1, 2)); ``` -## `->add()` +### `->add()` This is an alias for `->__invoke()`. -## `->size()` +### `->append()` + +Add all elements of a sequence at the end of another. + +```php +$sequence = Sequence::ints(1, 2)->append(Sequence::ints(3, 4)); +$sequence->equals(Sequence::ints(1, 2, 3, 4)); // true +``` + +## Access values + +### `->size()` This returns the number of elements in the sequence. @@ -97,7 +112,7 @@ $sequence = Sequence::ints(1, 4, 6); $sequence->size(); // 3 ``` -## `->count()` +### `->count()` This is an alias for `->size()`, but you can also use the PHP function `\count` if you prefer. @@ -107,7 +122,7 @@ $sequence->count(); // 3 \count($sequence); // 3 ``` -## `->get()` +### `->get()` This method will return a [`Maybe`](maybe.md) object containing the element at the given index in the sequence. If the index doesn't exist it will an empty `Maybe` object. @@ -117,262 +132,250 @@ $sequence->get(1); // Maybe::just(4) $sequence->get(3); // Maybe::nothing() ``` -## `->diff()` +### `->first()` -This method will return a new sequence containing the elements that are not present in the other sequence. +This is an alias for `->get(0)`. + +### `->last()` + +This is an alias for `->get(->size() - 1)`. + +### `->contains()` + +Check if the element is present in the sequence. ```php -$sequence = Sequence::ints(1, 4, 6)->diff(Sequence::ints(1, 3, 6)); -$sequence->equals(Sequence::ints(4)); // true +$sequence = Sequence::ints(1, 42, 3); +$sequence->contains(2); // false +$sequence->contains(42); // true +$sequence->contains('42'); // false but psalm will raise an error ``` -## `->distinct()` +### `->indexOf()` -This removes any duplicates in the sequence. +This will return a [`Maybe`](maybe.md) object containing the index number at which the first occurence of the element was found. ```php -$sequence = Sequence::ints(1, 2, 1, 3)->distinct(); -$sequence->equals(Sequence::ints(1, 2, 3)); // true +$sequence = Sequence::ints(1, 2, 3, 2); +$sequence->indexOf(2); // Maybe::just(1) +$sequence->indexOf(4); // Maybe::nothing() ``` -## `->drop()` +### `->find()` -This removes the number of elements from the end of the sequence. +Returns a [`Maybe`](maybe.md) object containing the first element that matches the predicate. ```php -$sequence = Sequence::ints(5, 4, 3, 2, 1)->drop(2); -$sequence->equals(Sequence::ints(3, 2, 1)); // true +$sequence = Sequence::ints(2, 4, 6, 8, 9, 10, 11); +$firstOdd = $sequence->find(fn($i) => $i % 2 === 1); +$firstOdd; // Maybe::just(9) +$sequence->find(static fn() => false); // Maybe::nothing() ``` -## `->dropEnd()` +### `->matches()` -This removes the number of elements from the end of the sequence. +Check if all the elements of the sequence matches the given predicate. ```php -$sequence = Sequence::ints(1, 2, 3, 4, 5)->drop(2); -$sequence->equals(Sequence::ints(1, 2, 3)); // true +$isOdd = fn($i) => $i % 2 === 1; +Sequence::ints(1, 3, 5, 7)->matches($isOdd); // true +Sequence::ints(1, 3, 4, 5, 7)->matches($isOdd); // false ``` -## `->equals()` +### `->any()` -Check if two sequences are identical. +Check if at least one element of the sequence matches the given predicate. ```php -Sequence::ints(1, 2)->equals(Sequence::ints(1, 2)); // true -Sequence::ints()->equals(Sequence::strings()); // false but psalm will raise an error +$isOdd = fn($i) => $i % 2 === 1; +Sequence::ints(1, 3, 5, 7)->any($isOdd); // true +Sequence::ints(1, 3, 4, 5, 7)->any($isOdd); // true +Sequence::ints(2, 4, 6, 8)->any($isOdd); // false ``` -## `->filter()` +### `->empty()` -Removes elements from the sequence that don't match the given predicate. +Tells whether there is at least one element or not. ```php -$sequence = Sequence::ints(1, 2, 3, 4)->filter(fn($i) => $i % 2 === 0); -$sequence->equals(Sequence::ints(2, 4)); +Sequence::ints()->empty(); // true +Sequence::ints(1)->empty(); // false ``` -## `->keep()` +## Transform values -This is similar to `->filter()` with the advantage of psalm understanding the type in the new `Sequence`. +### `->map()` -```php -use Innmind\Immutable\Predicate\Instance; +Create a new sequence with the exact same number of elements but modified by the given function. -$sequence = Sequence::of(null, new \stdClass, 'foo')->keep( - Instance::of('stdClass'), -); -$sequence; // Sequence +```php +$ints = Sequence::ints(1, 2, 3); +$squares = $ints->map(fn($i) => $i**2); +$squares->equals(Sequence::ints(1, 4, 9)); // true ``` -## `->exclude()` +### `->flatMap()` -Removes elements from the sequence that match the given predicate. +This is similar to `->map()` except that instead of returning a new value it returns a new sequence for each value, and each new sequence is appended together. ```php -$sequence = Sequence::ints(1, 2, 3, 4)->filter(fn($i) => $i % 2 === 0); -$sequence->equals(Sequence::ints(1, 3)); +$ints = Sequence::ints(1, 2, 3); +$squares = $ints->flatMap(fn($i) => Sequence::of($i, $i**2)); +$squares->equals(Sequence::ints(1, 1, 2, 4, 3, 9)); // true ``` -## `->foreach()` +### `->zip()` -Use this method to call a function for each element of the sequence. Since this structure is immutable it returns a `SideEffect` object, as its name suggest it is the only place acceptable to create side effects. +This method allows to merge 2 sequences into a new one by combining the values of the 2 into pairs. ```php -$sideEffect = Sequence::strings('hello', 'world')->foreach( - function(string $string): void { - echo $string.' '; - }, -); +$firnames = Sequence::of('John', 'Luke', 'James'); +$lastnames = Sequence::of('Doe', 'Skywalker', 'Kirk'); + +$pairs = $firnames + ->zip($lastnames) + ->toList(); +$pairs; // [['John', 'Doe'], ['Luke', 'Skywalker'], ['James', 'Kirk']] ``` -In itself the `SideEffect` object has no use except to avoid psalm complaining that the `foreach` method is not used. +### `->aggregate()` -## `->groupBy()` +This methods allows to rearrange the elements of the Sequence. This is especially useful for parsers. -This will create multiples sequences with elements regrouped under the same key computed by the given function. +An example would be to rearrange a list of chunks from a file into lines: ```php -$urls = Sequence::strings( - 'http://example.com', - 'http://example.com/foo', - 'https://example.com', - 'ftp://example.com', -); -/** @var Innmind\Immutable\Map> */ -$map = $urls->groupBy(fn(string $url): string => \parse_url($url)['scheme']); -$map - ->get('http') - ->match( - static fn($group) => $group, - static fn() => Sequence::strings(), - ) - ->equals(Sequence::strings( - 'http://example.com', - 'http://example.com/foo', - )); // true -$map - ->get('https') - ->match( - static fn($group) => $group, - static fn() => Sequence::strings(), - ) - ->equals(Sequence::strings('https://example.com')); // true -$map - ->get('ftp') - ->match( - static fn($group) => $group, - static fn() => Sequence::strings(), - ) - ->equals(Sequence::strings('ftp://example.com')); // true +// let's pretend this comes from a stream +$chunks = ['fo', "o\n", 'ba', "r\n", 'ba', "z\n"]; +$lines = Sequence::of(...$chunks) + ->map(Str::of(...)) + ->aggregate(static fn($a, $b) => $a->append($b->toString())->split("\n")) + ->flatMap(static fn($chunk) => $chunk->split("\n")) + ->map(static fn($line) => $line->toString()) + ->toList(); +$lines; // ['foo', 'bar', 'baz', ''] ``` -## `->first()` +!!! note "" + The `flatMap` is here in case there is only one chunk in the sequence, in which case the `aggregate` is not called -This is an alias for `->get(0)`. +### `->indices()` -## `->last()` +Create a new sequence of integers representing the indices of the original sequence. -This is an alias for `->get(->size() - 1)`. +```php +$sequence = Sequence::ints(1, 2, 3); +$sequence->indices()->equals(Sequence::ints(...\range(0, $sequence->size() - 1))); +``` -## `->contains()` +## Filter values -Check if the element is present in the sequence. +### `->filter()` + +Removes elements from the sequence that don't match the given predicate. ```php -$sequence = Sequence::ints(1, 42, 3); -$sequence->contains(2); // false -$sequence->contains(42); // true -$sequence->contains('42'); // false but psalm will raise an error +$sequence = Sequence::ints(1, 2, 3, 4)->filter(fn($i) => $i % 2 === 0); +$sequence->equals(Sequence::ints(2, 4)); ``` -## `->indexOf()` +### `->keep()` -This will return a [`Maybe`](maybe.md) object containing the index number at which the first occurence of the element was found. +This is similar to `->filter()` with the advantage of psalm understanding the type in the new `Sequence`. ```php -$sequence = Sequence::ints(1, 2, 3, 2); -$sequence->indexOf(2); // Maybe::just(1) -$sequence->indexOf(4); // Maybe::nothing() +use Innmind\Immutable\Predicate\Instance; + +$sequence = Sequence::of(null, new \stdClass, 'foo')->keep( + Instance::of('stdClass'), +); +$sequence; // Sequence ``` -## `->indices()` +### `->exclude()` -Create a new sequence of integers representing the indices of the original sequence. +Removes elements from the sequence that match the given predicate. ```php -$sequence = Sequence::ints(1, 2, 3); -$sequence->indices()->equals(Sequence::ints(...\range(0, $sequence->size() - 1))); +$sequence = Sequence::ints(1, 2, 3, 4)->filter(fn($i) => $i % 2 === 0); +$sequence->equals(Sequence::ints(1, 3)); ``` -## `->map()` +### `->take()` -Create a new sequence with the exact same number of elements but modified by the given function. +Create a new sequence with only the given number of elements from the start of the sequence. ```php -$ints = Sequence::ints(1, 2, 3); -$squares = $ints->map(fn($i) => $i**2); -$squares->equals(Sequence::ints(1, 4, 9)); // true +Sequence::ints(4, 3, 1, 0)->take(2)->equals(Sequence::ints(4, 3)); // true ``` -## `->flatMap()` +### `->takeEnd()` -This is similar to `->map()` except that instead of returning a new value it returns a new sequence for each value, and each new sequence is appended together. +Similar to `->take()` but it starts from the end of the sequence ```php -$ints = Sequence::ints(1, 2, 3); -$squares = $ints->flatMap(fn($i) => Sequence::of($i, $i**2)); -$squares->equals(Sequence::ints(1, 1, 2, 4, 3, 9)); // true +Sequence::ints(4, 3, 1, 0)->takeEnd(2)->equals(Sequence::ints(1, 0)); // true ``` -## `->pad()` +### `->takeWhile()` -Add the same element to a new sequence in order that its size is at least the given one. +This keeps all the elements from the start of the sequence while the condition returns `true`. ```php -$sequence = Sequence::ints(1, 2, 3); -$sequence->pad(2, 0)->equals(Sequence::ints(1, 2, 3)); // true -$sequence->pad(5, 0)->equals(Sequence::ints(1, 2, 3, 0, 0)); // true +$values = Sequence::of(1, 2, 3, 0, 4, 5, 6, 0) + ->takeWhile(static fn($i) => $i === 0) + ->toList(); +$values === [1, 2, 3]; ``` -## `->partition()` +### `->drop()` -This method is similar to `->groupBy()` method but the map keys are always booleans. The difference is that here the 2 keys are always present whereas with `->groupBy()` it will depend on the original sequence. +This removes the number of elements from the end of the sequence. ```php -$sequence = Sequence::ints(1, 2, 3); -/** @var Map> */ -$map = $sequence->partition(fn($int) => $int % 2 === 0); -$map - ->get(true) - ->match( - static fn($partition) => $partition, - static fn() => Sequence::ints(), - ) - ->equals(Sequence::ints(2)); // true -$map - ->get(false) - ->match( - static fn($partition) => $partition, - static fn() => Sequence::ints(), - ) - ->equals(Sequence::ints(1, 3)); // true +$sequence = Sequence::ints(5, 4, 3, 2, 1)->drop(2); +$sequence->equals(Sequence::ints(3, 2, 1)); // true ``` -## `->slice()` +### `->dropEnd()` -Return a new sequence with only the elements that were between the given indices. (The upper bound is not included) +This removes the number of elements from the end of the sequence. ```php -$sequence = Sequence::ints(4, 3, 2, 1); -$sequence->slice(1, 4)->equals(Sequence::ints(3, 2)); // true +$sequence = Sequence::ints(1, 2, 3, 4, 5)->drop(2); +$sequence->equals(Sequence::ints(1, 2, 3)); // true ``` -## `->take()` +### `->dropWhile()` -Create a new sequence with only the given number of elements from the start of the sequence. +This removes all the elements from the start of the sequence while the condition returns `true`. ```php -Sequence::ints(4, 3, 1, 0)->take(2)->equals(Sequence::ints(4, 3)); // true +$values = Sequence::of(0, 0, 0, 1, 2, 3, 0) + ->dropWhile(static fn($i) => $i === 0) + ->toList(); +$values === [1, 2, 3, 0]; ``` -## `->takeEnd()` +### `->slice()` -Similar to `->take()` but it starts from the end of the sequence +Return a new sequence with only the elements that were between the given indices. (The upper bound is not included) ```php -Sequence::ints(4, 3, 1, 0)->takeEnd(2)->equals(Sequence::ints(1, 0)); // true +$sequence = Sequence::ints(4, 3, 2, 1); +$sequence->slice(1, 4)->equals(Sequence::ints(3, 2)); // true ``` -## `->append()` +### `->diff()` -Add all elements of a sequence at the end of another. +This method will return a new sequence containing the elements that are not present in the other sequence. ```php -$sequence = Sequence::ints(1, 2)->append(Sequence::ints(3, 4)); -$sequence->equals(Sequence::ints(1, 2, 3, 4)); // true +$sequence = Sequence::ints(1, 4, 6)->diff(Sequence::ints(1, 3, 6)); +$sequence->equals(Sequence::ints(4)); // true ``` -## `->intersect()` +### `->intersect()` Create a new sequence with the elements that are also in the other sequence. @@ -381,17 +384,42 @@ $sequence = Sequence::ints(1, 2, 3)->intersect(Sequence::ints(2, 3, 4)); $sequence->equals(Sequence::ints(2, 3)); // true ``` -## `->sort()` +### `->distinct()` -Reorder the elements within the sequence. +This removes any duplicates in the sequence. ```php -$sequence = Sequence::ints(4, 2, 3, 1); -$sequence = $sequence->sort(fn($a, $b) => $a <=> $b); -$sequence->equals(Sequence::ints(1, 2, 3, 4)); +$sequence = Sequence::ints(1, 2, 1, 3)->distinct(); +$sequence->equals(Sequence::ints(1, 2, 3)); // true ``` -## `->fold()` +## Extract values + +### `->toList()` + +It returns a new `array` containing all the elements of the sequence. + +### `->match()` + +This is a similar approach to pattern matching allowing you to decompose a sequence by accessing the first element and the rest of the sequence. + +```php +function sum(Sequence $ints): int +{ + return $ints->match( + fn(int $head, Sequence $tail) => $head + sum($tail), + fn() => 0, + ); +} + +$result = sum(Sequence::of(1, 2, 3, 4)); +$result; // 10 +``` + +!!! warning "" + For lazy sequences bear in mind that the values will be kept in memory while the first call to `->match` didn't return. + +### `->fold()` This is similar to the `reduce` method but only takes a [`Monoid`](../MONOIDS.md) as an argument. @@ -405,7 +433,7 @@ $lines = Sequence::of("foo\n", "bar\n", 'baz') $lines->equals("foo\nbar\nbaz"); // true ``` -## `->reduce()` +### `->reduce()` Iteratively compute a value for all the elements in the sequence. @@ -415,104 +443,133 @@ $sum = $sequence->reduce(0, fn($sum, $int) => $sum + $int); $sum; // 10 ``` -## `->clear()` +## Misc. -Create an empty new sequence of the same type. (To avoid to redeclare the types manually in a docblock) +### `->equals()` + +Check if two sequences are identical. ```php -$sequence = Sequence::ints(1); -$sequence->clear()->size(); // 0 +Sequence::ints(1, 2)->equals(Sequence::ints(1, 2)); // true +Sequence::ints()->equals(Sequence::strings()); // false but psalm will raise an error ``` -## `->reverse()` +### `->foreach()` -Create a new sequence where the last element become the first one and so on. +Use this method to call a function for each element of the sequence. Since this structure is immutable it returns a `SideEffect` object, as its name suggest it is the only place acceptable to create side effects. ```php -$sequence = Sequence::ints(1, 2, 3, 4); -$sequence->reverse()->equals(Sequence::ints(4, 3, 2, 1)); +$sideEffect = Sequence::strings('hello', 'world')->foreach( + function(string $string): void { + echo $string.' '; + }, +); ``` -## `->empty()` +In itself the `SideEffect` object has no use except to avoid psalm complaining that the `foreach` method is not used. -Tells whether there is at least one element or not. +### `->groupBy()` + +This will create multiples sequences with elements regrouped under the same key computed by the given function. ```php -Sequence::ints()->empty(); // true -Sequence::ints(1)->empty(); // false +$urls = Sequence::strings( + 'http://example.com', + 'http://example.com/foo', + 'https://example.com', + 'ftp://example.com', +); +/** @var Innmind\Immutable\Map> */ +$map = $urls->groupBy(fn(string $url): string => \parse_url($url)['scheme']); +$map + ->get('http') + ->match( + static fn($group) => $group, + static fn() => Sequence::strings(), + ) + ->equals(Sequence::strings( + 'http://example.com', + 'http://example.com/foo', + )); // true +$map + ->get('https') + ->match( + static fn($group) => $group, + static fn() => Sequence::strings(), + ) + ->equals(Sequence::strings('https://example.com')); // true +$map + ->get('ftp') + ->match( + static fn($group) => $group, + static fn() => Sequence::strings(), + ) + ->equals(Sequence::strings('ftp://example.com')); // true ``` -## `->toList()` - -It returns a new `array` containing all the elements of the sequence. - -## `->find()` +### `->pad()` -Returns a [`Maybe`](maybe.md) object containing the first element that matches the predicate. +Add the same element to a new sequence in order that its size is at least the given one. ```php -$sequence = Sequence::ints(2, 4, 6, 8, 9, 10, 11); -$firstOdd = $sequence->find(fn($i) => $i % 2 === 1); -$firstOdd; // Maybe::just(9) -$sequence->find(static fn() => false); // Maybe::nothing() +$sequence = Sequence::ints(1, 2, 3); +$sequence->pad(2, 0)->equals(Sequence::ints(1, 2, 3)); // true +$sequence->pad(5, 0)->equals(Sequence::ints(1, 2, 3, 0, 0)); // true ``` -## `->matches()` +### `->partition()` -Check if all the elements of the sequence matches the given predicate. +This method is similar to `->groupBy()` method but the map keys are always booleans. The difference is that here the 2 keys are always present whereas with `->groupBy()` it will depend on the original sequence. ```php -$isOdd = fn($i) => $i % 2 === 1; -Sequence::ints(1, 3, 5, 7)->matches($isOdd); // true -Sequence::ints(1, 3, 4, 5, 7)->matches($isOdd); // false +$sequence = Sequence::ints(1, 2, 3); +/** @var Map> */ +$map = $sequence->partition(fn($int) => $int % 2 === 0); +$map + ->get(true) + ->match( + static fn($partition) => $partition, + static fn() => Sequence::ints(), + ) + ->equals(Sequence::ints(2)); // true +$map + ->get(false) + ->match( + static fn($partition) => $partition, + static fn() => Sequence::ints(), + ) + ->equals(Sequence::ints(1, 3)); // true ``` -## `->any()` +### `->sort()` -Check if at least one element of the sequence matches the given predicate. +Reorder the elements within the sequence. ```php -$isOdd = fn($i) => $i % 2 === 1; -Sequence::ints(1, 3, 5, 7)->any($isOdd); // true -Sequence::ints(1, 3, 4, 5, 7)->any($isOdd); // true -Sequence::ints(2, 4, 6, 8)->any($isOdd); // false +$sequence = Sequence::ints(4, 2, 3, 1); +$sequence = $sequence->sort(fn($a, $b) => $a <=> $b); +$sequence->equals(Sequence::ints(1, 2, 3, 4)); ``` -## `->match()` +### `->clear()` -This is a similar approach to pattern matching allowing you to decompose a sequence by accessing the first element and the rest of the sequence. +Create an empty new sequence of the same type. (To avoid to redeclare the types manually in a docblock) ```php -function sum(Sequence $ints): int -{ - return $ints->match( - fn(int $head, Sequence $tail) => $head + sum($tail), - fn() => 0, - ); -} - -$result = sum(Sequence::of(1, 2, 3, 4)); -$result; // 10 +$sequence = Sequence::ints(1); +$sequence->clear()->size(); // 0 ``` -!!! warning "" - For lazy sequences bear in mind that the values will be kept in memory while the first call to `->match` didn't return. +### `->reverse()` -## `->zip()` - -This method allows to merge 2 sequences into a new one by combining the values of the 2 into pairs. +Create a new sequence where the last element become the first one and so on. ```php -$firnames = Sequence::of('John', 'Luke', 'James'); -$lastnames = Sequence::of('Doe', 'Skywalker', 'Kirk'); - -$pairs = $firnames - ->zip($lastnames) - ->toList(); -$pairs; // [['John', 'Doe'], ['Luke', 'Skywalker'], ['James', 'Kirk']] +$sequence = Sequence::ints(1, 2, 3, 4); +$sequence->reverse()->equals(Sequence::ints(4, 3, 2, 1)); ``` -## `->safeguard()` +### `->safeguard()` This method allows you to make sure all values conforms to an assertion before continuing using the sequence. @@ -545,28 +602,7 @@ Sequence::lazyStartingWith('a', 'b', 'c', 'a') This example will print `a`, `b` and `c` before throwing an exception because of the second `a`. Use this method carefully. -## `->aggregate()` - -This methods allows to rearrange the elements of the Sequence. This is especially useful for parsers. - -An example would be to rearrange a list of chunks from a file into lines: - -```php -// let's pretend this comes from a stream -$chunks = ['fo', "o\n", 'ba', "r\n", 'ba', "z\n"]; -$lines = Sequence::of(...$chunks) - ->map(Str::of(...)) - ->aggregate(static fn($a, $b) => $a->append($b->toString())->split("\n")) - ->flatMap(static fn($chunk) => $chunk->split("\n")) - ->map(static fn($line) => $line->toString()) - ->toList(); -$lines; // ['foo', 'bar', 'baz', ''] -``` - -!!! note "" - The `flatMap` is here in case there is only one chunk in the sequence, in which case the `aggregate` is not called - -## `->memoize()` +### `->memoize()` This method will load all the values in memory. This is useful only for a deferred or lazy `Sequence`, the other sequence will be unaffected. @@ -580,25 +616,3 @@ $sequence = Sequence::lazy(function() { ->map(static fn($line) => \strtoupper($line)) // still no line loaded here ->memoize(); // load all lines and apply strtoupper on each ``` - -## `->dropWhile()` - -This removes all the elements from the start of the sequence while the condition returns `true`. - -```php -$values = Sequence::of(0, 0, 0, 1, 2, 3, 0) - ->dropWhile(static fn($i) => $i === 0) - ->toList(); -$values === [1, 2, 3, 0]; -``` - -## `->takeWhile()` - -This keeps all the elements from the start of the sequence while the condition returns `true`. - -```php -$values = Sequence::of(1, 2, 3, 0, 4, 5, 6, 0) - ->takeWhile(static fn($i) => $i === 0) - ->toList(); -$values === [1, 2, 3]; -``` From 2119bbd5c5b11e1cfccf482000271313687b945b Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 2 Jun 2024 12:02:10 +0200 Subject: [PATCH 12/25] flag methods that load data into memory --- docs/structures/sequence.md | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/docs/structures/sequence.md b/docs/structures/sequence.md index b02b037..8fe6b11 100644 --- a/docs/structures/sequence.md +++ b/docs/structures/sequence.md @@ -2,6 +2,9 @@ A sequence is an ordered list of elements, think of it like an array such as `[1, 'a', new stdClass]` or a `list` in the [Psalm](http://psalm.dev) nomenclature. +!!! info "" + Methods with the :material-memory-arrow-down: symbol indicates that they will trigger loading the generator for deferred and lazy sequences. + ## Named constructors ### `::of()` @@ -103,7 +106,7 @@ $sequence->equals(Sequence::ints(1, 2, 3, 4)); // true ## Access values -### `->size()` +### `->size()` :material-memory-arrow-down: This returns the number of elements in the sequence. @@ -112,7 +115,7 @@ $sequence = Sequence::ints(1, 4, 6); $sequence->size(); // 3 ``` -### `->count()` +### `->count()` :material-memory-arrow-down: This is an alias for `->size()`, but you can also use the PHP function `\count` if you prefer. @@ -140,7 +143,7 @@ This is an alias for `->get(0)`. This is an alias for `->get(->size() - 1)`. -### `->contains()` +### `->contains()` :material-memory-arrow-down: Check if the element is present in the sequence. @@ -172,7 +175,7 @@ $firstOdd; // Maybe::just(9) $sequence->find(static fn() => false); // Maybe::nothing() ``` -### `->matches()` +### `->matches()` :material-memory-arrow-down: Check if all the elements of the sequence matches the given predicate. @@ -182,7 +185,7 @@ Sequence::ints(1, 3, 5, 7)->matches($isOdd); // true Sequence::ints(1, 3, 4, 5, 7)->matches($isOdd); // false ``` -### `->any()` +### `->any()` :material-memory-arrow-down: Check if at least one element of the sequence matches the given predicate. @@ -193,7 +196,7 @@ Sequence::ints(1, 3, 4, 5, 7)->any($isOdd); // true Sequence::ints(2, 4, 6, 8)->any($isOdd); // false ``` -### `->empty()` +### `->empty()` :material-memory-arrow-down: Tells whether there is at least one element or not. @@ -337,7 +340,7 @@ $sequence = Sequence::ints(5, 4, 3, 2, 1)->drop(2); $sequence->equals(Sequence::ints(3, 2, 1)); // true ``` -### `->dropEnd()` +### `->dropEnd()` :material-memory-arrow-down: This removes the number of elements from the end of the sequence. @@ -395,11 +398,11 @@ $sequence->equals(Sequence::ints(1, 2, 3)); // true ## Extract values -### `->toList()` +### `->toList()` :material-memory-arrow-down: It returns a new `array` containing all the elements of the sequence. -### `->match()` +### `->match()` :material-memory-arrow-down: This is a similar approach to pattern matching allowing you to decompose a sequence by accessing the first element and the rest of the sequence. @@ -419,7 +422,7 @@ $result; // 10 !!! warning "" For lazy sequences bear in mind that the values will be kept in memory while the first call to `->match` didn't return. -### `->fold()` +### `->fold()` :material-memory-arrow-down: This is similar to the `reduce` method but only takes a [`Monoid`](../MONOIDS.md) as an argument. @@ -433,7 +436,7 @@ $lines = Sequence::of("foo\n", "bar\n", 'baz') $lines->equals("foo\nbar\nbaz"); // true ``` -### `->reduce()` +### `->reduce()` :material-memory-arrow-down: Iteratively compute a value for all the elements in the sequence. @@ -445,7 +448,7 @@ $sum; // 10 ## Misc. -### `->equals()` +### `->equals()` :material-memory-arrow-down: Check if two sequences are identical. @@ -454,7 +457,7 @@ Sequence::ints(1, 2)->equals(Sequence::ints(1, 2)); // true Sequence::ints()->equals(Sequence::strings()); // false but psalm will raise an error ``` -### `->foreach()` +### `->foreach()` :material-memory-arrow-down: Use this method to call a function for each element of the sequence. Since this structure is immutable it returns a `SideEffect` object, as its name suggest it is the only place acceptable to create side effects. @@ -468,7 +471,7 @@ $sideEffect = Sequence::strings('hello', 'world')->foreach( In itself the `SideEffect` object has no use except to avoid psalm complaining that the `foreach` method is not used. -### `->groupBy()` +### `->groupBy()` :material-memory-arrow-down: This will create multiples sequences with elements regrouped under the same key computed by the given function. @@ -517,7 +520,7 @@ $sequence->pad(2, 0)->equals(Sequence::ints(1, 2, 3)); // true $sequence->pad(5, 0)->equals(Sequence::ints(1, 2, 3, 0, 0)); // true ``` -### `->partition()` +### `->partition()` :material-memory-arrow-down: This method is similar to `->groupBy()` method but the map keys are always booleans. The difference is that here the 2 keys are always present whereas with `->groupBy()` it will depend on the original sequence. @@ -602,7 +605,7 @@ Sequence::lazyStartingWith('a', 'b', 'c', 'a') This example will print `a`, `b` and `c` before throwing an exception because of the second `a`. Use this method carefully. -### `->memoize()` +### `->memoize()` :material-memory-arrow-down: This method will load all the values in memory. This is useful only for a deferred or lazy `Sequence`, the other sequence will be unaffected. From e9268ceef69e98c7b4ae176a22930d7b743feea7 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 2 Jun 2024 13:57:09 +0200 Subject: [PATCH 13/25] move set doc --- docs/{SET.md => structures/set.md} | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) rename docs/{SET.md => structures/set.md} (94%) diff --git a/docs/SET.md b/docs/structures/set.md similarity index 94% rename from docs/SET.md rename to docs/structures/set.md index 8d47fed..854ea78 100644 --- a/docs/SET.md +++ b/docs/structures/set.md @@ -25,7 +25,8 @@ $set = Set::defer((function() { The method ask a generator that will provide the elements. Once the elements are loaded they are kept in memory so you can run multiple operations on it without loading the file twice. -> **Warning** beware of the case where the source you read the elements is not altered before the first use of the set. +!!! warning "" + Beware of the case where the source you read the elements is not altered before the first use of the set. ## `::lazy()` @@ -37,7 +38,8 @@ $set = Set::lazy(function() { }); ``` -> **Warning** since the elements are reloaded each time the immutability responsability is up to you because the source may change or if you generate objects it will generate new objects each time (so if you make strict comparison it will fail). +!!! warning "" + Since the elements are reloaded each time the immutability responsability is up to you because the source may change or if you generate objects it will generate new objects each time (so if you make strict comparison it will fail). ## `::mixed()` @@ -173,9 +175,11 @@ $set->equals(Set::ints(1, 3)); Use this method to call a function for each element of the set. Since this structure is immutable it returns a `SideEffect` object, as its name suggest it is the only place acceptable to create side effects. ```php -$sideEffect = Set::strings('hello', 'world')->foreach(function(string $string): void { - echo $string.' '; -}); +$sideEffect = Set::strings('hello', 'world')->foreach( + function(string $string): void { + echo $string.' '; + }, +); ``` ## `->groupBy()` @@ -346,7 +350,8 @@ $result = sum(Set::of(1, 2, 3, 4)); $result; // 10 ``` -> **Warning** for lazy sets bear in mind that the values will be kept in memory while the first call to `->match` didn't return. +!!! warning "" + For lazy sets bear in mind that the values will be kept in memory while the first call to `->match` didn't return. ## `->matches()` From a3c67833e1ad9c0c7fb730462de74b50cbf401d3 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 2 Jun 2024 14:03:50 +0200 Subject: [PATCH 14/25] rearrange methods by groups --- docs/structures/set.md | 312 +++++++++++++++++++++-------------------- 1 file changed, 163 insertions(+), 149 deletions(-) diff --git a/docs/structures/set.md b/docs/structures/set.md index 854ea78..4dc13f2 100644 --- a/docs/structures/set.md +++ b/docs/structures/set.md @@ -2,7 +2,9 @@ A set is an unordered list of unique elements. -## `::of()` +## Named constructors + +### `::of()` ```php use Innmind\Immutable\Set; @@ -11,7 +13,7 @@ use Innmind\Immutable\Set; Set::of(1, 2, 3, $etc); ``` -## `::defer()` +### `::defer()` This named constructor is for advanced use cases where you want the data of your set to be loaded upon use only and not initialisation. @@ -28,7 +30,7 @@ The method ask a generator that will provide the elements. Once the elements are !!! warning "" Beware of the case where the source you read the elements is not altered before the first use of the set. -## `::lazy()` +### `::lazy()` This is similar to `::defer()` with the exception that the elements are not kept in memory but reloaded upon each use. @@ -41,27 +43,29 @@ $set = Set::lazy(function() { !!! warning "" Since the elements are reloaded each time the immutability responsability is up to you because the source may change or if you generate objects it will generate new objects each time (so if you make strict comparison it will fail). -## `::mixed()` +### `::mixed()` This is a shortcut for `::of(mixed ...$mixed)`. -## `::ints()` +### `::ints()` This is a shortcut for `::of(int ...$ints)`. -## `::floats()` +### `::floats()` This is a shortcut for `::of(float ...$floats)`. -## `::strings()` +### `::strings()` This is a shortcut for `::of(string ...$strings)`. -## `::objects()` +### `::objects()` This is a shortcut for `::of(object ...$objects)`. -## `->__invoke()` +## Add values + +### `->__invoke()` Augment the set with a new element. If the element is already in the set nothing changes. @@ -71,11 +75,22 @@ $set = ($set)(2)(1); $set->equals(Set::ints(1, 2)); ``` -## `->add()` +### `->add()` This is an alias for `->__invoke()`. -## `->size()` +### `->merge()` + +Create a new set with all the elements from both sets. + +```php +$set = Set::ints(1, 2, 3)->merge(Set::ints(4, 2, 3)); +$set->equals(Set::ints(1, 2, 3, 4)); +``` + +## Access values + +### `->size()` This returns the number of elements in the set. @@ -84,7 +99,7 @@ $set = Set::ints(1, 4, 6); $set->size(); // 3 ``` -## `->count()` +### `->count()` This is an alias for `->size()`, but you can also use the PHP function `\count` if you prefer. @@ -94,16 +109,7 @@ $set->count(); // 3 \count($set); // 3 ``` -## `->intersect()` - -Create a new set with the elements that are also in the other set. - -```php -$set = Set::ints(1, 2, 3)->intersect(Set::ints(2, 3, 4)); -$set->equals(Set::ints(2, 3)); // true -``` - -## `->contains()` +### `->contains()` Check if the element is present in the set. @@ -114,34 +120,81 @@ $set->contains(42); // true $set->contains('42'); // false but psalm will raise an error ``` -## `->remove()` +### `->find()` -Create a new set without the specified element. +Returns the first element that matches the predicate. ```php -$set = Set::ints(1, 2, 3); -$set->remove(2)->equals(Set::ints(1, 3)); // true +$set = Set::ints(2, 4, 6, 8, 9, 10, 11); +/** @var Maybe $firstOdd */ +$firstOdd = $set->find(fn($i) => $i % 2 === 1); +$firstOdd; // could contain 9 or 11, because there is no ordering ``` -## `->diff()` +### `->matches()` -This method will return a new set containing the elements that are not present in the other set. +Check if all the elements of the set matches the given predicate. ```php -$set = Set::ints(1, 4, 6)->diff(Set::ints(1, 3, 6)); -$set->equals(Set::ints(4)); // true +$isOdd = fn($i) => $i % 2 === 1; +Set::ints(1, 3, 5, 7)->matches($isOdd); // true +Set::ints(1, 3, 4, 5, 7)->matches($isOdd); // false ``` -## `->equals()` +### `->any()` -Check if two sets are identical. +Check if at least one element of the set matches the given predicate. ```php -Set::ints(1, 2)->equals(Set::ints(2, 1)); // true -Set::ints()->equals(Set::strings()); // false but psalm will raise an error +$isOdd = fn($i) => $i % 2 === 1; +Set::ints(1, 3, 5, 7)->any($isOdd); // true +Set::ints(1, 3, 4, 5, 7)->any($isOdd); // true +Set::ints(2, 4, 6, 8)->any($isOdd); // false ``` -## `->filter()` +### `->empty()` + +Tells whether there is at least one element or not. + +```php +Set::ints()->empty(); // true +Set::ints(1)->empty(); // false +``` + +## Transform values + +### `->map()` + +Create a new set with the exact same number of elements but modified by the given function. + +```php +$ints = Set::ints(1, 2, 3); +$squares = $ints->map(fn($i) => $i**2); +$squares->equals(Set::ints(1, 4, 9)); // true +``` + +### `->flatMap()` + +This is similar to `->map()` except that instead of returning a new value it returns a new set for each value, and each new set is merged together. + +```php +$ints = Set::ints(1, 2, 3); +$squares = $ints->flatMap(fn($i) => Set::of($i, $i**2)); +$squares->equals(Set::ints(1, 2, 4, 3, 9)); // true +``` + +## Filter values + +### `->remove()` + +Create a new set without the specified element. + +```php +$set = Set::ints(1, 2, 3); +$set->remove(2)->equals(Set::ints(1, 3)); // true +``` + +### `->filter()` Removes elements from the set that don't match the given predicate. @@ -150,7 +203,7 @@ $set = Set::ints(1, 2, 3, 4)->filter(fn($i) => $i % 2 === 0); $set->equals(Set::ints(2, 4)); ``` -## `->keep()` +### `->keep()` This is similar to `->filter()` with the advantage of psalm understanding the type in the new `Set`. @@ -161,7 +214,7 @@ $set = Set::of(null, new \stdClass, 'foo')->keep(Instance::of('stdClass')); $set; // Set ``` -## `->exclude()` +### `->exclude()` Removes elements from the set that match the given predicate. @@ -170,7 +223,72 @@ $set = Set::ints(1, 2, 3, 4)->filter(fn($i) => $i % 2 === 0); $set->equals(Set::ints(1, 3)); ``` -## `->foreach()` +### `->diff()` + +This method will return a new set containing the elements that are not present in the other set. + +```php +$set = Set::ints(1, 4, 6)->diff(Set::ints(1, 3, 6)); +$set->equals(Set::ints(4)); // true +``` + +### `->intersect()` + +Create a new set with the elements that are also in the other set. + +```php +$set = Set::ints(1, 2, 3)->intersect(Set::ints(2, 3, 4)); +$set->equals(Set::ints(2, 3)); // true +``` + +## Extract values + +### `->toList()` + +It returns a new `array` containing all the elements of the set. + +### `->match()` + +This is a similar approach to pattern matching allowing you to decompose a set by accessing the first element and the rest of the set. + +```php +function sum(Set $ints): int +{ + return $ints->match( + fn(int $head, Set $tail) => $head + sum($tail), + fn() => 0, + ); +} + +$result = sum(Set::of(1, 2, 3, 4)); +$result; // 10 +``` + +!!! warning "" + For lazy sets bear in mind that the values will be kept in memory while the first call to `->match` didn't return. + +### `->reduce()` + +Iteratively compute a value for all the elements in the set. + +```php +$set = Set::ints(1, 2, 3, 4); +$sum = $set->reduce(0, fn($sum, $int) => $sum + $int); +$sum; // 10 +``` + +## Misc. + +### `->equals()` + +Check if two sets are identical. + +```php +Set::ints(1, 2)->equals(Set::ints(2, 1)); // true +Set::ints()->equals(Set::strings()); // false but psalm will raise an error +``` + +### `->foreach()` Use this method to call a function for each element of the set. Since this structure is immutable it returns a `SideEffect` object, as its name suggest it is the only place acceptable to create side effects. @@ -182,7 +300,7 @@ $sideEffect = Set::strings('hello', 'world')->foreach( ); ``` -## `->groupBy()` +### `->groupBy()` This will create multiples sets with elements regrouped under the same key computed by the given function. @@ -218,27 +336,7 @@ $map ->equals(Set::strings('ftp://example.com')); // true ``` -## `->map()` - -Create a new set with the exact same number of elements but modified by the given function. - -```php -$ints = Set::ints(1, 2, 3); -$squares = $ints->map(fn($i) => $i**2); -$squares->equals(Set::ints(1, 4, 9)); // true -``` - -## `->flatMap()` - -This is similar to `->map()` except that instead of returning a new value it returns a new set for each value, and each new set is merged together. - -```php -$ints = Set::ints(1, 2, 3); -$squares = $ints->flatMap(fn($i) => Set::of($i, $i**2)); -$squares->equals(Set::ints(1, 2, 4, 3, 9)); // true -``` - -## `->partition()` +### `->partition()` This method is similar to `->groupBy()` method but the map keys are always booleans. The difference is that here the 2 keys are always present whereas with `->groupBy()` it will depend on the original set. @@ -262,7 +360,7 @@ $map ->equals(Set::ints(1, 3)); // true ``` -## `->sort()` +### `->sort()` It will transform the set into an ordered sequence. @@ -271,7 +369,7 @@ $sequence = Set::ints(1, 4, 2, 3)->sort(fn($a, $b) => $a <=> $b); $sequence->equals(Sequence::ints(1, 2, 3, 4)); ``` -## `->unsorted()` +### `->unsorted()` It will transform the set into an unordered sequence. @@ -281,26 +379,7 @@ $sequence = Set::ints(1, 4, 2, 3)->unsorted(); $sequence = Sequence::of(...Set::of(1, 4, 2, 3)->toList()); ``` -## `->merge()` - -Create a new set with all the elements from both sets. - -```php -$set = Set::ints(1, 2, 3)->merge(Set::ints(4, 2, 3)); -$set->equals(Set::ints(1, 2, 3, 4)); -``` - -## `->reduce()` - -Iteratively compute a value for all the elements in the set. - -```php -$set = Set::ints(1, 2, 3, 4); -$sum = $set->reduce(0, fn($sum, $int) => $sum + $int); -$sum; // 10 -``` - -## `->clear()` +### `->clear()` Create an empty new set of the same type. (To avoid to redeclare the types manually in a docblock) @@ -309,72 +388,7 @@ $set = Set::ints(1); $set->clear()->size(); // 0 ``` -## `->empty()` - -Tells whether there is at least one element or not. - -```php -Set::ints()->empty(); // true -Set::ints(1)->empty(); // false -``` - -## `->toList()` - -It returns a new `array` containing all the elements of the set. - -## `->find()` - -Returns the first element that matches the predicate. - -```php -$set = Set::ints(2, 4, 6, 8, 9, 10, 11); -/** @var Maybe $firstOdd */ -$firstOdd = $set->find(fn($i) => $i % 2 === 1); -$firstOdd; // could contain 9 or 11, because there is no ordering -``` - -## `->match()` - -This is a similar approach to pattern matching allowing you to decompose a set by accessing the first element and the rest of the set. - -```php -function sum(Set $ints): int -{ - return $ints->match( - fn(int $head, Set $tail) => $head + sum($tail), - fn() => 0, - ); -} - -$result = sum(Set::of(1, 2, 3, 4)); -$result; // 10 -``` - -!!! warning "" - For lazy sets bear in mind that the values will be kept in memory while the first call to `->match` didn't return. - -## `->matches()` - -Check if all the elements of the set matches the given predicate. - -```php -$isOdd = fn($i) => $i % 2 === 1; -Set::ints(1, 3, 5, 7)->matches($isOdd); // true -Set::ints(1, 3, 4, 5, 7)->matches($isOdd); // false -``` - -## `->any()` - -Check if at least one element of the set matches the given predicate. - -```php -$isOdd = fn($i) => $i % 2 === 1; -Set::ints(1, 3, 5, 7)->any($isOdd); // true -Set::ints(1, 3, 4, 5, 7)->any($isOdd); // true -Set::ints(2, 4, 6, 8)->any($isOdd); // false -``` - -## `->safeguard()` +### `->safeguard()` This method allows you to make sure all values conforms to an assertion before continuing using the set. @@ -417,7 +431,7 @@ Set::lazy(function() { This example will print `a`, `b` and `c` before throwing an exception because of the second `a`. Use this method carefully. -## `->memoize()` +### `->memoize()` This method will load all the values in memory. This is useful only for a deferred or lazy `Set`, the other set will be unaffected. From 3af0ad83d26c4e222158456af43a89adef9dd1f5 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 2 Jun 2024 14:11:23 +0200 Subject: [PATCH 15/25] move map doc --- docs/{MAP.md => structures/map.md} | 304 +++++++++++++++-------------- 1 file changed, 160 insertions(+), 144 deletions(-) rename docs/{MAP.md => structures/map.md} (89%) diff --git a/docs/MAP.md b/docs/structures/map.md similarity index 89% rename from docs/MAP.md rename to docs/structures/map.md index 86c2dc7..3599338 100644 --- a/docs/MAP.md +++ b/docs/structures/map.md @@ -2,7 +2,9 @@ A map is an unordered list of pair of elements, think of it like an associative array or an `array` in the [Psalm](http://psalm.dev) nomenclature. But with the added benefit that the keys can be of any type, even objects! -## `::of()` +## Named constructors + +### `::of()` ```php use Innmind\Immutable\Map; @@ -13,7 +15,9 @@ $map = Map::of(); The first type is for the keys and the second one for the values. This order is the same for all the methods below. -## `->__invoke()` +## Add values + +### `->__invoke()` Augment the map with a new pair of elements. If the key already exist it will replace the value. @@ -25,11 +29,29 @@ $map->equals( ); ``` -## `->put()` +### `->put()` This is an alias for `->__invoke()`. -## `->size()` +### `->merge()` + +Create a new map with all pairs from both maps. Pairs from the map in the argument will replace existing pairs from the original map. + +```php +$a = Map::of([1, 2], [3, 4]); +$b = Map::of([5, 6], [3, 7]); +$a->merge($b)->equals( + Map::of( + [1, 2], + [5, 6], + [3, 7], + ), +); // true +``` + +## Access values + +### `->size()` This returns the number of elements in the map. @@ -38,7 +60,7 @@ $map = Map::of([1, 2]); $map->size(); // 1 ``` -## `->count()` +### `->count()` This is an alias for `->size()`, but you can also use the PHP function `\count` if you prefer. @@ -48,7 +70,7 @@ $map->size(); // 1 \count($map); // 1 ``` -## `->get()` +### `->get()` Return an instance of [`Maybe`](maybe.md) that may contain the value associated to the given key (if it exists). @@ -58,7 +80,7 @@ $map->get(1); // Maybe::just(2) $map->get(2); // Maybe::nothing() ``` -## `->contains()` +### `->contains()` Check if the map contains a given key. @@ -68,119 +90,72 @@ $map->contains(1); // true $map->contains(2); // false ``` -## `->clear()` +### `->keys()` -Return an empty new map of the same type. Useful to avoid to respecify the templates types of the map in a new docblock annotation. +Return a [`Set`](SET.md) of all the keys of the map. ```php -$map = Map::of([1, 2], [3, 4]); -$map->clear()->size(); // 0 +$keys = Map::of([24, 1], [42, 2])->keys(); +$keys->equals(Set::of(24, 42)); // true ``` -## `->equals()` +### `->values()` -Check if two maps are identical. +Return a [`Sequence`](SEQUENCE.md) of all the values of the map. ```php -$a = Map::of([1, 2], [3, 4]); -$b = Map::of([3, 4], [1, 2]); -$a->equals($b); // true -$a->equals(Map::of(); // false +$values = Map::of([24, 1], [42, 2])->values(); +$values->equals(Sequence::of(1, 2)); // true ``` -## `->filter()` +!!! note "" + It returns a `Sequence` because it can contain duplicates, the order is not guaranteed as a map is not ordered. -Removes the pairs from the map that don't match the given predicate. +### `->find()` -```php -$map = Map::of([1, 1], [3, 2]); -$map = $map->filter(fn($key, $value) => ($key + $value) % 2 === 0); -$map->equals(Map::of([1, 1])); -``` - -## `->exclude()` - -Removes the pairs from the map that match the given predicate. +This will return the first pair that matches the given predicate (remember that the map is not ordered). ```php -$map = Map::of([1, 1], [3, 2]); -$map = $map->exclude(fn($key, $value) => ($key + $value) % 2 === 0); -$map->equals(Map::of([3, 2])); -``` - -## `->foreach()` - -Use this method to call a function for each pair of the map. Since this structure is immutable it returns a `SideEffect` object, as its name suggest it is the only place acceptable to create side effects. +use Innmind\Immutable\Pair; -```php -$sideEffect = Map::of(['hello', 'world'])->foreach(function(string $key, string $value): void { - echo "$key $value"; // will print "hello world" -}); +Map::of([1, 2], [3, 4], [5, 6], [7, 8])->find( + fn($key, $value) => ($key + $value) > 10, +); // Maybe::just(new Pair(5, 6)) ``` -In itself the `SideEffect` object has no use except to avoid psalm complaining that the `foreach` method is not used. - -## `->groupBy()` +### `->matches()` -This will create multiples maps with elements regrouped under the same key computed by the given function. +Check if all the pairs of the map matches the given predicate. ```php -$urls = Map::of( - ['http://example.com', 1], - ['http://example.com/foo', 1], - ['https://example.com', 2], - ['ftp://example.com', 4], -); -/** @var Innmind\Immutable\Map> */ -$map = $urls->groupBy(fn(string $url, int $whatever): string => \parse_url($url)['scheme']); -$map - ->get('http') - ->match( - static fn($group) => $group, - static fn() => Map::of(), - ) - ->equals(Map::of( - ['http://example.com', 1], - ['http://example.com/foo', 1] - )); // true -$map - ->get('https') - ->match( - static fn($group) => $group, - static fn() => Map::of(), - ) - ->equals(Map::of(['https://example.com', 2])); // true -$map - ->get('ftp') - ->match( - static fn($group) => $group, - static fn() => Map::of(), - ) - ->equals(Map::of(['ftp://example.com', 4])); // true +$isOdd = fn($i) => $i % 2 === 1; +Map::of([1, 2], [3, 4])->matches(fn($key) => $isOdd($key)); // true +Map::of([1, 2], [3, 4])->matches(fn($key, $value) => $isOdd($value)); // false ``` -## `->keys()` +### `->any()` -Return a [`Set`](SET.md) of all the keys of the map. +Check if at least one pair of the map matches the given predicate. ```php -$keys = Map::of([24, 1], [42, 2])->keys(); -$keys->equals(Set::of(24, 42)); // true +$isOdd = fn($i) => $i % 2 === 1; +Map::of([1, 2], [3, 4])->any(fn($key) => $isOdd($key)); // true +Map::of([1, 3], [3, 4])->any(fn($key, $value) => $isOdd($value)); // true +Map::of([1, 2], [3, 4])->any(fn($key, $value) => $isOdd($value)); // false ``` -## `->values()` +### `->empty()` -Return a [`Sequence`](SEQUENCE.md) of all the values of the map. +Tells whether there is at least one pair or not. ```php -$values = Map::of([24, 1], [42, 2])->values(); -$values->equals(Sequence::of(1, 2)); // true +Map::of()->empty(); // true +Map::of([1, 2])->empty(); // false ``` -> **Note** -> it returns a `Sequence` because it can contain duplicates, the order is not guaranteed as a map is not ordered. +## Transform values -## `->map()` +### `->map()` Create a new map of the same type with the exact same number of pairs but modified by the given function. @@ -202,7 +177,7 @@ $incremented->equals( ); ``` -## `->flatMap()` +### `->flatMap()` This is similar to `->map()` but instead of returning a new value it returns a new `Map` for each value, all maps are merged to form only one `Map`. @@ -233,56 +208,40 @@ $withScheme->equals( ); ``` -## `->remove()` +## Filter values -Remove the pair from the map with the given key. +### `->filter()` + +Removes the pairs from the map that don't match the given predicate. ```php -$map = Map::of([2, 3], [3, 4]); -$map->remove(3)->equals(Map::of([2, 3])); // true +$map = Map::of([1, 1], [3, 2]); +$map = $map->filter(fn($key, $value) => ($key + $value) % 2 === 0); +$map->equals(Map::of([1, 1])); ``` -## `->merge()` +### `->exclude()` -Create a new map with all pairs from both maps. Pairs from the map in the argument will replace existing pairs from the original map. +Removes the pairs from the map that match the given predicate. ```php -$a = Map::of([1, 2], [3, 4]); -$b = Map::of([5, 6], [3, 7]); -$a->merge($b)->equals( - Map::of( - [1, 2], - [5, 6], - [3, 7], - ), -); // true +$map = Map::of([1, 1], [3, 2]); +$map = $map->exclude(fn($key, $value) => ($key + $value) % 2 === 0); +$map->equals(Map::of([3, 2])); ``` -## `->partition()` +### `->remove()` -This method is similar to `->groupBy()` method but the map keys are always booleans. The difference is that here the 2 keys are always present whereas with `->groupBy()` it will depend on the original map. +Remove the pair from the map with the given key. ```php -$map = Map::of([1, 2], [2, 3], [3, 3]); -/** @var Map> */ -$map = $map->partition(fn($key, $value) => ($key + $value) % 2 === 0); -$map - ->get(true) - ->match( - static fn($partition) => $partition, - static fn() => Map::of(), - ) - ->equals(Map::of([3, 3])); // true -$map - ->get(false) - ->match( - static fn($partition) => $partition, - static fn() => Map::of(), - ) - ->equals(Map::of([1, 2], [2, 3])); // true +$map = Map::of([2, 3], [3, 4]); +$map->remove(3)->equals(Map::of([2, 3])); // true ``` -## `->reduce()` +## Extract values + +### `->reduce()` Iteratively compute a value for all the pairs in the map. @@ -292,44 +251,101 @@ $sum = $map->reduce(0, fn($sum, $key, $value) => $sum + $key + $value); $sum; // 14 ``` -## `->empty()` +## Misc. -Tells whether there is at least one pair or not. +### `->equals()` + +Check if two maps are identical. ```php -Map::of()->empty(); // true -Map::of([1, 2])->empty(); // false +$a = Map::of([1, 2], [3, 4]); +$b = Map::of([3, 4], [1, 2]); +$a->equals($b); // true +$a->equals(Map::of(); // false ``` -## `->find()` +### `->foreach()` -This will return the first pair that matches the given predicate (remember that the map is not ordered). +Use this method to call a function for each pair of the map. Since this structure is immutable it returns a `SideEffect` object, as its name suggest it is the only place acceptable to create side effects. ```php -use Innmind\Immutable\Pair; +$sideEffect = Map::of(['hello', 'world'])->foreach( + function(string $key, string $value): void { + echo "$key $value"; // will print "hello world" + }, +); +``` -Map::of([1, 2], [3, 4], [5, 6], [7, 8])->find( - fn($key, $value) => ($key + $value) > 10, -); // Maybe::just(new Pair(5, 6)) +In itself the `SideEffect` object has no use except to avoid psalm complaining that the `foreach` method is not used. + +### `->groupBy()` + +This will create multiples maps with elements regrouped under the same key computed by the given function. + +```php +$urls = Map::of( + ['http://example.com', 1], + ['http://example.com/foo', 1], + ['https://example.com', 2], + ['ftp://example.com', 4], +); +/** @var Innmind\Immutable\Map> */ +$map = $urls->groupBy(fn(string $url, int $whatever): string => \parse_url($url)['scheme']); +$map + ->get('http') + ->match( + static fn($group) => $group, + static fn() => Map::of(), + ) + ->equals(Map::of( + ['http://example.com', 1], + ['http://example.com/foo', 1] + )); // true +$map + ->get('https') + ->match( + static fn($group) => $group, + static fn() => Map::of(), + ) + ->equals(Map::of(['https://example.com', 2])); // true +$map + ->get('ftp') + ->match( + static fn($group) => $group, + static fn() => Map::of(), + ) + ->equals(Map::of(['ftp://example.com', 4])); // true ``` -## `->matches()` +### `->partition()` -Check if all the pairs of the map matches the given predicate. +This method is similar to `->groupBy()` method but the map keys are always booleans. The difference is that here the 2 keys are always present whereas with `->groupBy()` it will depend on the original map. ```php -$isOdd = fn($i) => $i % 2 === 1; -Map::of([1, 2], [3, 4])->matches(fn($key) => $isOdd($key)); // true -Map::of([1, 2], [3, 4])->matches(fn($key, $value) => $isOdd($value)); // false +$map = Map::of([1, 2], [2, 3], [3, 3]); +/** @var Map> */ +$map = $map->partition(fn($key, $value) => ($key + $value) % 2 === 0); +$map + ->get(true) + ->match( + static fn($partition) => $partition, + static fn() => Map::of(), + ) + ->equals(Map::of([3, 3])); // true +$map + ->get(false) + ->match( + static fn($partition) => $partition, + static fn() => Map::of(), + ) + ->equals(Map::of([1, 2], [2, 3])); // true ``` -## `->any()` +### `->clear()` -Check if at least one pair of the map matches the given predicate. +Return an empty new map of the same type. Useful to avoid to respecify the templates types of the map in a new docblock annotation. ```php -$isOdd = fn($i) => $i % 2 === 1; -Map::of([1, 2], [3, 4])->any(fn($key) => $isOdd($key)); // true -Map::of([1, 3], [3, 4])->any(fn($key, $value) => $isOdd($value)); // true -Map::of([1, 2], [3, 4])->any(fn($key, $value) => $isOdd($value)); // false +$map = Map::of([1, 2], [3, 4]); +$map->clear()->size(); // 0 ``` From 39dd47d26cb132558027bb1d24bfdd13cdb34599 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 2 Jun 2024 14:17:06 +0200 Subject: [PATCH 16/25] move str doc --- docs/{STR.md => structures/str.md} | 50 ++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 17 deletions(-) rename docs/{STR.md => structures/str.md} (89%) diff --git a/docs/STR.md b/docs/structures/str.md similarity index 89% rename from docs/STR.md rename to docs/structures/str.md index 079ef77..b9a458b 100644 --- a/docs/STR.md +++ b/docs/structures/str.md @@ -21,8 +21,8 @@ $str->length(); // 1 Str::of('👋')->length(); // 4 ``` -> **Note** -> `Str\Encoding::utf8` is the default value when not specified +!!! note "" + `Str\Encoding::utf8` is the default value when not specified ## `->toString()` @@ -233,13 +233,15 @@ Str::of('abcdef')->matches('/^b/'); // false Return a map of the elements matching the regular expression. ```php -Str::of('http://www.php.net/index.html')->capture('@^(?:http://)?(?P[^/]+)@i')->equals( - Map::of( - [0, Str::of('http://www.php.net')], - [1, Str::of('www.php.net')], - ['host', Str::of('www.php.net')], - ), -); +Str::of('http://www.php.net/index.html') + ->capture('@^(?:http://)?(?P[^/]+)@i') + ->equals( + Map::of( + [0, Str::of('http://www.php.net')], + [1, Str::of('www.php.net')], + ['host', Str::of('www.php.net')], + ), + ); ``` ## `->pregReplace()` @@ -247,9 +249,9 @@ Str::of('http://www.php.net/index.html')->capture('@^(?:http://)?(?P[^/]+) Replace part of the string by using a regular expression. ```php -Str::of('April 15, 2003')->pregReplace('/(\w+) (\d+), (\d+)/i', '${1}1,$3')->equals( - Str::of('April1,2003'), -); +Str::of('April 15, 2003') + ->pregReplace('/(\w+) (\d+), (\d+)/i', '${1}1,$3') + ->equals(Str::of('April1,2003')); ``` ## `->substring()` @@ -298,7 +300,9 @@ Str::of('foobar')->dropEnd(3)->equals(Str::of('foo')); // true Return a formatted string. ```php -Str::of('%s %s')->sprintf('hello', 'world')->equals(Str::of('hello world')); // true +Str::of('%s %s') + ->sprintf('hello', 'world') + ->equals(Str::of('hello world')); // true ``` ## `->ucfirst()` @@ -394,7 +398,10 @@ This function will create a new `Str` object with the value modified by the give ```php $str = Str::of('foo|bar|baz')->map( - fn(string $value, string $encoding): string => \implode(',', \explode('|', $string)), + fn(string $value, string $encoding): string => \implode( + ',', + \explode('|', $string), + ), ); $str->equals(Str::of('foo,bar,baz')); // true ``` @@ -405,7 +412,9 @@ This is similar to `->map()` but instead of the function returning a value it mu ```php $str = Str::of('foo|bar|baz')->flatMap( - fn(string $value, string $encoding): Str => Str::of(',')->join(Sequence::of(...\explode('|', $string))), + fn(string $value, string $encoding): Str => Str::of(',')->join( + Sequence::of(...\explode('|', $string)), + ), ); $str->equals(Str::of('foo,bar,baz')); // true ``` @@ -415,6 +424,13 @@ $str->equals(Str::of('foo,bar,baz')); // true The is a shortcut method, the 2 examples below do the same thing. ```php -Str::of('foobar')->maybe(static fn($str) => $str->startsWith('foo')); // Maybe -Maybe::of(Str::of('foobar'))->filter(static fn($str) => $str->startsWith('foo')); // Maybe +Str::of('foobar')->maybe( + static fn($str) => $str->startsWith('foo'), +); // Maybe + +// is the same as + +Maybe::of(Str::of('foobar'))->filter( + static fn($str) => $str->startsWith('foo'), +); // Maybe ``` From 42561daaf837e7a2cc8f3a57259167613917d353 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 2 Jun 2024 14:18:19 +0200 Subject: [PATCH 17/25] move regexp doc --- docs/{REGEXP.md => structures/regexp.md} | 0 mkdocs.yml | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename docs/{REGEXP.md => structures/regexp.md} (100%) diff --git a/docs/REGEXP.md b/docs/structures/regexp.md similarity index 100% rename from docs/REGEXP.md rename to docs/structures/regexp.md diff --git a/mkdocs.yml b/mkdocs.yml index a1901a5..1e2f6a1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -10,7 +10,7 @@ nav: - Set: structures/set.md - Map: structures/map.md - Str: structures/str.md - - RegExp: structures/regExp.md + - RegExp: structures/regexp.md - Maybe: structures/maybe.md - Either: structures/either.md - Validation: structures/validation.md From 0b1141f05fe3c0feec4b6684024299ddfb62f4f1 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 2 Jun 2024 14:21:34 +0200 Subject: [PATCH 18/25] move maybe doc --- docs/{MAYBE.md => structures/maybe.md} | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) rename docs/{MAYBE.md => structures/maybe.md} (97%) diff --git a/docs/MAYBE.md b/docs/structures/maybe.md similarity index 97% rename from docs/MAYBE.md rename to docs/structures/maybe.md index f96bcd5..681b461 100644 --- a/docs/MAYBE.md +++ b/docs/structures/maybe.md @@ -62,7 +62,8 @@ $maybe = Maybe::defer(static function() { Methods called (except `match`) on a deferred `Maybe` will not be called immediately but will be composed to be executed once you call `match`. -> **Warning** this means that if you never call `match` on a deferred `Maybe` it will do nothing. +!!! warning "" + This means that if you never call `match` on a deferred `Maybe` it will do nothing. ## `->map()` @@ -190,8 +191,8 @@ $vars = Sequence::of('DB_URL', 'MAILER_URL', /* and so on */) ->flatMap(static fn($var) => env($var)->toSequence()); ``` -> **Note** -> this example uses the `env` function defined at the start of this documentation. +!!! note "" + This example uses the `env` function defined at the start of this documentation. This is equivalent to: From 30599cf04d79d6f14b8e06968eadb2b2cae43aa2 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 2 Jun 2024 14:25:55 +0200 Subject: [PATCH 19/25] move either doc --- docs/{EITHER.md => structures/either.md} | 34 +++++++++++++++--------- 1 file changed, 21 insertions(+), 13 deletions(-) rename docs/{EITHER.md => structures/either.md} (89%) diff --git a/docs/EITHER.md b/docs/structures/either.md similarity index 89% rename from docs/EITHER.md rename to docs/structures/either.md index e405f25..06a9162 100644 --- a/docs/EITHER.md +++ b/docs/structures/either.md @@ -32,8 +32,8 @@ function accessResource(User $user): Either { } ``` -> **Note** -> `ServerRequest`, `User`, `Resource` and `Error` are imaginary classes. +!!! note "" + `ServerRequest`, `User`, `Resource` and `Error` are imaginary classes. ## `::left()` @@ -43,8 +43,8 @@ This builds an `Either` instance with the given value in the left hand side. $either = Either::left($anyValue); ``` -> **Note** -> usually this side is used for errors. +!!! note "" + Usually this side is used for errors. ## `::right()` @@ -54,8 +54,8 @@ This builds an `Either` instance with the given value in the right hand side. $either = Either::right($anyValue); ``` -> **Note** -> usually this side is used for valid values. +!!! note "" + Usually this side is used for valid values. ## `::defer()` @@ -75,7 +75,8 @@ $either = Either::defer(static function() { Methods called (except `match`) on a deferred `Either` will not be called immediately but will be composed to be executed once you call `match`. -> **Warning** this means that if you never call `match` on a deferred `Either` it will do nothing. +!!! warning "" + This means that if you never call `match` on a deferred `Either` it will do nothing. ## `->map()` @@ -85,7 +86,9 @@ This will apply the map transformation on the right value if there is one, other /** @var Either */ $either = identify($serverRequest); /** @var Either */ -$impersonated = $either->map(fn(User $user): Impersonated => $user->impersonateAdmin()); +$impersonated = $either->map( + fn(User $user): Impersonated => $user->impersonateAdmin(), +); ``` ## `->flatMap()` @@ -109,12 +112,14 @@ $response = identify($serverRequest) ->flatMap(fn(User $user): Either => accessResource($user)) ->match( fn(Resource $resource) => new Response(200, $resource->toString()), - fn(Error $error) => new Response(400, $error->message()), // here the error can be from identify or from accessResource + fn(Error $error) => new Response(400, $error->message()), //(1) ); ``` -> **Note** -> `Response` is an imaginary class. +1. Here the error can be from `identify` or from `accessResource`. + +!!! note "" + `Response` is an imaginary class. ## `->otherwise()` @@ -148,11 +153,14 @@ identify($request) fn() => new Error('User is not allowed'), ) ->match( - fn(User $user) => doSomething($user), // here we know the user is allowed - fn(Error $error) => print($error->message()), // can be "User not found" or "User is not allowed" + fn(User $user) => doSomething($user), //(1) + fn(Error $error) => print($error->message()), //(2) ); ``` +1. Here we know the user is allowed. +2. Can be "User not found" or "User is not allowed". + ## `->leftMap()` This is similar to the `->map()` function but will be applied on the left value only. From 312706353f9e83e16f357b9aa603a56aa049f0d9 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 2 Jun 2024 14:28:50 +0200 Subject: [PATCH 20/25] move validation doc --- docs/{VALIDATION.md => structures/validation.md} | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) rename docs/{VALIDATION.md => structures/validation.md} (92%) diff --git a/docs/VALIDATION.md b/docs/structures/validation.md similarity index 92% rename from docs/VALIDATION.md rename to docs/structures/validation.md index 673653f..718bceb 100644 --- a/docs/VALIDATION.md +++ b/docs/structures/validation.md @@ -1,6 +1,6 @@ # `Validation` -This structure is similar to [`Either`](EITHER.md) except that the right side is called success and left fail. The difference is that `Validation` allows to accumulate failures. +This structure is similar to [`Either`](either.md) except that the right side is called success and left fail. The difference is that `Validation` allows to accumulate failures. For the examples below we will use the given imaginary functions: @@ -30,8 +30,8 @@ function isLocal(string $value): Validation { } ``` -> **Note** -> `Error` is imaginary class. +!!! note "" + `Error` is imaginary class. ## `::fail()` @@ -82,7 +82,9 @@ $localEmail = isEmail($serverRequest) ->map(static fn(string $email) => new Email($email)) ->match( fn(Email $email) => $email, - fn(Sequence $failures) => throw new \Exception(\implode(', ', $failure->toList())), + fn(Sequence $failures) => throw new \Exception( + \implode(', ', $failure->toList()), + ), ); ``` @@ -147,7 +149,7 @@ $foobar ## `->maybe()` -This returns a [`Maybe`](MAYBE.md) containing the success value, in case of failures it returns a `Maybe` with nothing inside. +This returns a [`Maybe`](maybe.md) containing the success value, in case of failures it returns a `Maybe` with nothing inside. ```php Validation::success('something')->maybe()->match( @@ -162,7 +164,7 @@ Validation::fail('something')->maybe()->match( ## `->either()` -This returns an [`Either`](EITHER.md) containing the success value as the right side, in case of failures it returns an `Either` with failures as the left side. +This returns an [`Either`](either.md) containing the success value as the right side, in case of failures it returns an `Either` with failures as the left side. ```php Validation::success('something')->either()->match( From 789585f7722f3d8b910fd7c4a3510311ee75a7b5 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 2 Jun 2024 14:29:47 +0200 Subject: [PATCH 21/25] fix links --- docs/structures/either.md | 2 +- docs/structures/map.md | 4 ++-- docs/structures/maybe.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/structures/either.md b/docs/structures/either.md index 06a9162..9f6239e 100644 --- a/docs/structures/either.md +++ b/docs/structures/either.md @@ -173,7 +173,7 @@ $either = identify($request) ## `->maybe()` -This returns a [`Maybe`](MAYBE.md) containing the right value, in case of a left value it returns a `Maybe` with nothing inside. +This returns a [`Maybe`](maybe.md) containing the right value, in case of a left value it returns a `Maybe` with nothing inside. ```php Either::right('something')->maybe()->match( diff --git a/docs/structures/map.md b/docs/structures/map.md index 3599338..7486485 100644 --- a/docs/structures/map.md +++ b/docs/structures/map.md @@ -92,7 +92,7 @@ $map->contains(2); // false ### `->keys()` -Return a [`Set`](SET.md) of all the keys of the map. +Return a [`Set`](set.md) of all the keys of the map. ```php $keys = Map::of([24, 1], [42, 2])->keys(); @@ -101,7 +101,7 @@ $keys->equals(Set::of(24, 42)); // true ### `->values()` -Return a [`Sequence`](SEQUENCE.md) of all the values of the map. +Return a [`Sequence`](sequence.md) of all the values of the map. ```php $values = Map::of([24, 1], [42, 2])->values(); diff --git a/docs/structures/maybe.md b/docs/structures/maybe.md index 681b461..874620f 100644 --- a/docs/structures/maybe.md +++ b/docs/structures/maybe.md @@ -150,7 +150,7 @@ This is the inverse of the `->filter()` method. ## `->either()` -This returns an [`Either`](EITHER.md) containing the value on the right side and `null` on the left side. +This returns an [`Either`](either.md) containing the value on the right side and `null` on the left side. ```php Maybe::just('something')->either()->match( From 1d6f3a76acec728f5ff80cf65ae17a7c522e6ee2 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 2 Jun 2024 14:30:44 +0200 Subject: [PATCH 22/25] move state doc --- docs/{STATE.md => structures/state.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/{STATE.md => structures/state.md} (100%) diff --git a/docs/STATE.md b/docs/structures/state.md similarity index 100% rename from docs/STATE.md rename to docs/structures/state.md From e2e93c51100e564af70d245e5a73a8f7205a4db8 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 2 Jun 2024 14:31:34 +0200 Subject: [PATCH 23/25] move fold doc --- docs/{FOLD.md => structures/fold.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename docs/{FOLD.md => structures/fold.md} (93%) diff --git a/docs/FOLD.md b/docs/structures/fold.md similarity index 93% rename from docs/FOLD.md rename to docs/structures/fold.md index bd2a465..ef6bb4a 100644 --- a/docs/FOLD.md +++ b/docs/structures/fold.md @@ -1,6 +1,6 @@ # `Fold` -The `Fold` monad is intented to work with _(infinte) stream of data_ by folding each element to a single value. This monad distinguishes between the type used to fold and the result type, this allows to inform the _stream_ that it's no longer necessary to extract elements as the folding is done. +The `Fold` monad is intented to work with _(infinite) stream of data_ by folding each element to a single value. This monad distinguishes between the type used to fold and the result type, this allows to inform the _stream_ that it's no longer necessary to extract elements as the folding is done. An example is reading from a socket as it's an infinite stream of strings: From 3b2deb2de87eca4c6c66cd4dfe433162a8a3720c Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 2 Jun 2024 14:36:09 +0200 Subject: [PATCH 24/25] update documentation link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index da253ff..964bfba 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A set of classes to wrap PHP primitives to build immutable data. -[Documentation](docs/README.md) +[Documentation](https://innmind.github.io/Immutable/) ## Installation From bc4127d825cbc9ad6fb6430689badac3c573c287 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 2 Jun 2024 14:41:36 +0200 Subject: [PATCH 25/25] specify next release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84a9701..56a1c91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [Unreleased] +## 5.5.0 - 2024-06-02 ### Changed