document callback adaptation, errors and min/max ties
This commit is contained in:
parent
db3aab644e
commit
c27e326d59
58
README.md
58
README.md
@ -125,10 +125,10 @@ $slow('stogi'); // remembered
|
||||
|
||||
## Compiling decorators
|
||||
|
||||
`partial()`, `curry()` and `fix()` return decorator objects. Calling one goes through its `__invoke()`, which
|
||||
inspects the arguments every time. That is fine for a single call, but in a loop over thousands of elements it
|
||||
adds up. `f\compile()` flattens such a callable into a plain `\Closure` once, so the loop pays only for the
|
||||
function itself:
|
||||
`partial()`, `curry()`, `fix()`, `pipe()` and the predicates return objects. Calling one goes through its
|
||||
`__invoke()`, which inspects the arguments every time. That is fine for a single call, but in a loop over thousands
|
||||
of elements it adds up. `f\compile()` flattens such a callable into a plain `\Closure` once, so the loop pays only
|
||||
for the functions it calls:
|
||||
|
||||
```php
|
||||
use Kadet\Functional as f;
|
||||
@ -139,9 +139,17 @@ $pad = f\compile(f\partial(str_pad(...), _, 3, '0', STR_PAD_LEFT));
|
||||
$pad('7'); // '007'
|
||||
```
|
||||
|
||||
Objects implementing `Kadet\Functional\Compilable` are compiled with their own `compile()`; any other callable is
|
||||
converted to a closure. Implement `Compilable` on your own decorators to get the same benefit.
|
||||
You rarely need to call `compile()` yourself: the collection functions compile every callback once per step.
|
||||
Objects implementing `Kadet\Functional\Compilable` are compiled with their own `compile()`, also when given as a
|
||||
closure of their `__invoke()`; any other callable is converted to a closure. Compiling is recursive: a predicate
|
||||
like `p\property('delay', p\gt(60))` becomes a closure calling the accessor and the comparison directly. Implement
|
||||
`Compilable` on your own decorators to get the same benefit. `memoize()` is not compiled, its cache is the point.
|
||||
|
||||
A compiled callable behaves like the object, with two differences. When the call ends in an internal function,
|
||||
arguments it can't take are dropped instead of throwing. And a compiled `curry()` calls its function with the
|
||||
arguments it gets, without currying further.
|
||||
|
||||
You rarely need to call `compile()` yourself: the collection functions compile every callback once per step,
|
||||
predicates and pipes included.
|
||||
|
||||
## Collections
|
||||
|
||||
@ -161,9 +169,10 @@ $lines = $departures
|
||||
```
|
||||
|
||||
The namespace says which flavour you get. `c\` is eager and gives a finished collection after every step. `lazy\`
|
||||
holds the streaming transforms, which give a generator and do no work until something reads it. Sorting, grouping,
|
||||
collectors and queries need the whole input, so they exist only in `c\`. They take any iterable, generators
|
||||
included, and are how a lazy chain ends.
|
||||
holds the streaming transforms, which give a generator and do no work until something reads it. Collectors and
|
||||
queries end a chain, turning it into a collection or a single value, so there is nothing to delay and they exist
|
||||
only in `c\`. They take any iterable, generators included, and are how a lazy chain ends. Sorting needs the whole
|
||||
input before it can give the first element, so it is eager only as well.
|
||||
|
||||
### Pipes, and calling without them
|
||||
|
||||
@ -206,12 +215,19 @@ argument, so a naive `($value, $key)` call would round the first element to 0 pl
|
||||
third to 2. To prevent that, two kinds of callables are called with the value only:
|
||||
|
||||
- **PHP's internal functions**, written as first-class callables: `trim(...)`, `round(...)`, `strtoupper(...)`.
|
||||
This also holds when they are wrapped, e.g. `f\partial(str_pad(...), _, 3, '0')`.
|
||||
- **Any callable declaring exactly one parameter**, like `fn ($value) => ...` or a one-parameter method.
|
||||
|
||||
The library's own wrappers are seen through: what counts is the function a callable ultimately calls. For
|
||||
`partial()`, `curry()`, `memoize()` and `f\predicate()` that is the wrapped function, for `f\not()` the
|
||||
negated predicate, and for `f\pipe()` its first function, the one that gets the arguments. So
|
||||
`f\partial(str_pad(...), _, 3, '0')`, `f\memoize(fn ($stop) => ...)` (cached by the value alone) and
|
||||
`f\pipe(trim(...), strtolower(...))` all get only the value. `f\all()` and `f\any()` decide for each of their
|
||||
predicates: in `f\all(is_numeric(...), fn ($value, $key) => ...)` the first gets the value, the second the key too.
|
||||
|
||||
```php
|
||||
[1.25, 2.5, 3.14159] |> c\map(round(...)); // [1.0, 3.0, 3.0], not [1.0, 2.5, 3.14]
|
||||
[' a ', ' b'] |> c\map(trim(...)); // ['a', 'b']; the key never becomes trim's character list
|
||||
['n' => ' Main St '] |> c\map(f\pipe(trim(...), strtolower(...))); // ['n' => 'main st']
|
||||
```
|
||||
|
||||
Any other callable gets the key, **even into an optional parameter**. A method like
|
||||
@ -316,7 +332,7 @@ $byId->get('d4')->destination; // 'Oliwa'
|
||||
```
|
||||
|
||||
`toArray()` throws an `InvalidArgumentException` when a key can't be an array key, like an object key of a
|
||||
`Ds\Map`, instead of PHP's "Illegal offset type".
|
||||
`Ds\Map`, instead of PHP's "Illegal offset type". So does `merge()` when it merges such keys into an array.
|
||||
|
||||
### Queries
|
||||
|
||||
@ -341,7 +357,23 @@ the largest delay, not the departure that has it. To get the element, sort and t
|
||||
$departures |> c\sortBy(t\property('delay'), descending: true) |> c\first(); // d4
|
||||
```
|
||||
|
||||
`reduce()` callbacks take the carry first: `fn ($carry, $value, $key)`.
|
||||
On a tie, `min()` and `max()` return the first of the equal values: `[1, 1.0] |> c\max()` is `1`, and of two
|
||||
objects equal under `<=>` you get the one that came first.
|
||||
|
||||
`reduce()` callbacks take the carry first: `fn ($carry, $value, $key)`. Like the value-only rule above, an internal
|
||||
function or a callable declaring exactly two parameters gets only `($carry, $value)`, so
|
||||
`[5 => 1, 9 => 2] |> c\reduce(max(...), 0)` is `2`, not the key `9`.
|
||||
|
||||
### Errors
|
||||
|
||||
Exceptions name the function and the problem. Two kinds are thrown:
|
||||
|
||||
- **Invalid arguments** raise PHP's own SPL exceptions: `InvalidArgumentException` (`c\take(-1)`, a key that can't be
|
||||
an array key in `toArray()` or `merge()`) and `UnexpectedValueException` (a `flatMap()` callback that returns no
|
||||
iterable).
|
||||
- **Library conditions** raise subclasses of `Kadet\Functional\Exception\FunctionalException`:
|
||||
`EmptyCollectionException` (a `required` query found nothing) and `DsUnavailableException` (a collector needs
|
||||
php-ds, see below).
|
||||
|
||||
### Lazy pipelines
|
||||
|
||||
|
||||
@ -17,8 +17,11 @@ interface Compilable
|
||||
/**
|
||||
* Returns a closure that behaves like invoking this object, but does not go through it on each call.
|
||||
*
|
||||
* The one difference: when the call ends in a non-variadic internal function, arguments past its parameter count
|
||||
* are dropped, where invoking this object would throw an ArgumentCountError.
|
||||
* The differences:
|
||||
* - when the call ends in a non-variadic internal function, arguments past its parameter count are dropped, where
|
||||
* invoking this object would throw an ArgumentCountError (`all()` and `any()` likewise give each predicate only
|
||||
* the arguments it takes);
|
||||
* - a compiled CurriedFunction calls its function with the arguments it gets, it does not curry further.
|
||||
*/
|
||||
public function compile(): \Closure;
|
||||
}
|
||||
|
||||
@ -192,6 +192,17 @@ class QueriesTest extends \PHPUnit\Framework\TestCase
|
||||
$this->assertNull(c\max()([]));
|
||||
}
|
||||
|
||||
public function testMinMaxTiesKeepFirstExtremum()
|
||||
{
|
||||
$a = (object) ['x' => 1];
|
||||
$b = (object) ['x' => 1];
|
||||
|
||||
$this->assertSame($a, c\min()([$a, $b]));
|
||||
$this->assertSame($a, c\max()([$a, $b]));
|
||||
$this->assertSame(1, c\max()([1, 1.0]));
|
||||
$this->assertSame(1.0, c\min()([1.0, 1]));
|
||||
}
|
||||
|
||||
public function testMinWithSelector()
|
||||
{
|
||||
$this->assertSame(1, c\min(fn ($v) => strlen($v))(['ccc', 'a', 'bb']));
|
||||
|
||||
Loading…
Reference in New Issue
Block a user