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/CHANGELOG.md b/CHANGELOG.md index af94f56..56a1c91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 5.5.0 - 2024-06-02 + +### 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/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/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 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. diff --git a/docs/PHILOSOPHY.md b/docs/PHILOSOPHY.md index 36640da..480e0fc 100644 --- a/docs/PHILOSOPHY.md +++ b/docs/PHILOSOPHY.md @@ -1,6 +1,13 @@ +--- +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: + - higher order functions on data structures - immutability diff --git a/docs/README.md b/docs/README.md index 1d4c8c8..97195ab 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. @@ -9,31 +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. - -## 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/assets/favicon.png b/docs/assets/favicon.png new file mode 100644 index 0000000..08dee3a Binary files /dev/null and b/docs/assets/favicon.png differ diff --git a/docs/assets/fonts/MonaspaceNeon-Regular.woff b/docs/assets/fonts/MonaspaceNeon-Regular.woff new file mode 100644 index 0000000..ce0168b Binary files /dev/null and b/docs/assets/fonts/MonaspaceNeon-Regular.woff differ 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/docs/EITHER.md b/docs/structures/either.md similarity index 88% rename from docs/EITHER.md rename to docs/structures/either.md index e405f25..9f6239e 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. @@ -165,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/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: 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. diff --git a/docs/MAP.md b/docs/structures/map.md similarity index 88% rename from docs/MAP.md rename to docs/structures/map.md index 86c2dc7..7486485 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()` - -Removes the pairs from the map that don't match the given predicate. +!!! note "" + It returns a `Sequence` because it can contain duplicates, the order is not guaranteed as a map is not ordered. -```php -$map = Map::of([1, 1], [3, 2]); -$map = $map->filter(fn($key, $value) => ($key + $value) % 2 === 0); -$map->equals(Map::of([1, 1])); -``` +### `->find()` -## `->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 ``` diff --git a/docs/MAYBE.md b/docs/structures/maybe.md similarity index 96% rename from docs/MAYBE.md rename to docs/structures/maybe.md index f96bcd5..874620f 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()` @@ -149,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( @@ -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: 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/docs/SEQUENCE.md b/docs/structures/sequence.md similarity index 82% rename from docs/SEQUENCE.md rename to docs/structures/sequence.md index 07863e8..8fe6b11 100644 --- a/docs/SEQUENCE.md +++ b/docs/structures/sequence.md @@ -2,7 +2,12 @@ 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()` +!!! 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()` The `of` static method allows you to create a new sequence with all the elements passed as arguments. @@ -13,7 +18,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. @@ -27,9 +32,10 @@ $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()` +### `::lazy()` This is similar to `::defer()` with the exception that the elements are not kept in memory but reloaded upon each use. @@ -39,9 +45,10 @@ $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()` +### `::lazyStartingWith()` Same as `::lazy()` except you don't need to manually build the generator. @@ -49,30 +56,32 @@ 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()` +### `::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. @@ -82,11 +91,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()` :material-memory-arrow-down: This returns the number of elements in the sequence. @@ -95,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. @@ -105,7 +125,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. @@ -115,255 +135,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()` :material-memory-arrow-down: + +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()` :material-memory-arrow-down: -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()` :material-memory-arrow-down: -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()` :material-memory-arrow-down: -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()` :material-memory-arrow-down: -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. @@ -372,19 +387,44 @@ $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 -This is similar to the `reduce` method but only takes a [`Monoid`](MONOIDS.md) as an argument. +### `->toList()` :material-memory-arrow-down: + +It returns a new `array` containing all the elements of the sequence. + +### `->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. + +```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()` :material-memory-arrow-down: + +This is similar to the `reduce` method but only takes a [`Monoid`](../MONOIDS.md) as an argument. ```php use Innmind\Immutable\Monoid\Concat; @@ -396,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. @@ -406,103 +446,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()` :material-memory-arrow-down: + +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()` :material-memory-arrow-down: -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()` :material-memory-arrow-down: + +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()` :material-memory-arrow-down: -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 lasy 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. @@ -535,27 +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. -## `->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 -$chunks = ['fo', "o\n", 'ba', "r\n", 'ba', "z\n"]; // let's pretend this comes from a stream -$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()` :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. @@ -569,25 +619,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]; -``` diff --git a/docs/SET.md b/docs/structures/set.md similarity index 87% rename from docs/SET.md rename to docs/structures/set.md index 8d47fed..4dc13f2 100644 --- a/docs/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. @@ -25,9 +27,10 @@ $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()` +### `::lazy()` This is similar to `::defer()` with the exception that the elements are not kept in memory but reloaded upon each use. @@ -37,29 +40,32 @@ $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()` +### `::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. @@ -69,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. @@ -82,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. @@ -92,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. @@ -112,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 +``` + +### `->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 ``` -## `->filter()` +### `->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. @@ -148,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`. @@ -159,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. @@ -168,17 +223,84 @@ $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. ```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()` +### `->groupBy()` This will create multiples sets with elements regrouped under the same key computed by the given function. @@ -214,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. @@ -258,7 +360,7 @@ $map ->equals(Set::ints(1, 3)); // true ``` -## `->sort()` +### `->sort()` It will transform the set into an ordered sequence. @@ -267,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. @@ -277,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) @@ -305,71 +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. @@ -412,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. 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 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 ``` 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( 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/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(), )); } diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..1e2f6a1 --- /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: testing.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 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; + } + }, + ); } /**