├── .github
└── workflows
│ └── php.yml
├── .gitignore
├── Dockerfile
├── Makefile
├── README.md
├── bench
├── auto-curry.php
├── named-closures.php
└── sort-from-array.php
├── composer.json
├── docker-compose.yml
├── make-code.php
├── peridot.php
├── src
├── c.generated.php
├── consts.generated.php
├── consts.ns.generated.php
├── curried.generated.php
├── f.generated.php
├── fn.php
└── generate.php
└── test
├── api.spec.php
└── fn.spec.php
/.github/workflows/php.yml:
--------------------------------------------------------------------------------
1 | name: PHP Composer
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | strategy:
9 | matrix:
10 | php-versions: ['7.4']
11 | steps:
12 | - uses: actions/checkout@v2
13 | - name: Setup PHP
14 | uses: shivammathur/setup-php@v2
15 | with:
16 | php-version: ${{ matrix.php-versions }}
17 | - name: Install dependencies
18 | run: composer install
19 | - name: Run Tests
20 | run: composer test
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | /composer.lock
3 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM php:7.4-cli
2 |
3 | RUN apt-get update && apt-get install -y git zip
4 |
5 | COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/bin/
6 | COPY --from=composer:2.4.4 /usr/bin/composer /usr/bin/composer
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: test code
2 |
3 | test:
4 | ./vendor/bin/peridot test
5 | code:
6 | php make-code.php
7 | docs:
8 | head -n $$(grep -n '## API' README.md | cut -f1 -d:) README.md > _tmp_readme.bak
9 | ./vendor/bin/peridot -r peridocs test >> _tmp_readme.bak
10 | mv _tmp_readme.bak README.md
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Fun
2 |
3 | Yet another functional library for PHP. What makes this library special is that it uses PHP Parser to generate curried versions of the non-curried implementations for best performance.
4 |
5 | ## Installation
6 |
7 | Install with composer at `krak/fn`
8 |
9 | ## Usage
10 |
11 | All functions are defined in `Krak\Fun`, are not curried, and are data last. Curried versions of functions are defined `Kran\Fun\Curried`. Constants are also generated per function in `Krak\Fun\Consts`.
12 |
13 | ```php
14 | ')(2))
24 | )([1,2,3,4]);
25 | assert($res == [9, 12]);
26 | ```
27 |
28 | Check the `src/fn.php` for examples of all the functions.
29 |
30 | ### Fun API
31 |
32 | In addition to importing the functions/consts individually, you can also utilize the `f` and `c` namespaces as a shorthand which make using the library a lot easier.
33 |
34 | ```php
35 | ')(2))
43 | )([1,2,3,4]);
44 | assert($res == [9, 12]);
45 | ```
46 |
47 | The `f` namespace holds the standard functions basically copied over verbatim from the `Krak\Fun` namespace.
48 |
49 | The `c` namespace contains all of the curried functions and constant definitions.
50 |
51 | One great way to use the consts is with compose or pipe chains:
52 |
53 | ```php
54 | use Krak\Fun\{f, c};
55 |
56 | $res = f\compose(
57 | c\toArray,
58 | c\map(function($tup) {
59 | return $tup[0] + $tup[1];
60 | }),
61 | c\toPairs
62 | )([1,2,3]);
63 | // $res == [1, 3, 5]
64 | ```
65 |
66 | ### Constants
67 |
68 | This library generates constants with same name as the function they were generated from where their value is the fully qualified name of the function.
69 |
70 | PHP (unfortunately) will treat strings as callables if they resolve to a function name. So generating constants with the same name as functions allows us to support a neat first class function type syntax.
71 |
72 | ```php
73 | Void
123 | ```
124 |
125 | the curried verison would look like:
126 |
127 | ```
128 | (a, c = null) -> (b) -> Void
129 | ```
130 |
131 |
132 | ### Debugging
133 |
134 | If you have a function compose chain and want to debug/test the result of any of the functions, you can do something like the following examples:
135 |
136 | 1. Debug a single value:
137 |
138 | ```php
139 | f\compose(
140 | function() {}, // do something else
141 | c\dd(), // debug result here
142 | function() {}, // do another thing that returns a single value
143 | function() {} // do something
144 | );
145 | ```
146 | 2. Debug an iterable:
147 |
148 | ```php
149 | f\compose(
150 | function() {}, // do something else
151 | c\dd(), c\toArray, // debug result here
152 | c\map(function() {}) // do something
153 | );
154 | ```
155 |
156 | ### Using Compose Chains for Readable Code
157 |
158 | One of my favorite features of using this library is building compose chains in a way that make your application services a lot easier to read and follow along with.
159 |
160 | ```php
161 |
162 | use Krak\Fun\{f, c};
163 |
164 | /**
165 | * Fetches orders based off of the arguments, filters data, and imports to database
166 | */
167 | final class ImportOrdersFromApi
168 | {
169 | public function __construct(ApiClient $client, OrderRepository $orderRepository, SaveOrders $saveOrders) {
170 | // ...
171 | }
172 |
173 | public function __invoke(ImportOrderRequest $req): void {
174 | f\compose(
175 | $this->persistApiOrders(),
176 | $this->removeAlreadyImportedOrders(),
177 | $this->fetchOrdersFromApi()
178 | )($req);
179 | }
180 |
181 | private function persistApiOrders(): callable {
182 | // important that this is an c\each so that it will consume the iterable chain
183 | return f\compose(
184 | c\each($this->saveOrders), // saveOrders has a signature of `__invoke(iterable Order[]) => void`
185 | c\chunk(50), // chunk to persist many at once
186 | c\map(function(array $apiOrder) {
187 | return Order::createFromApiData($apiOrder);
188 | })
189 | );
190 | }
191 |
192 | private function removeAlreadyImportedOrders(): callable {
193 | return f\compose(
194 | c\flatMap(function(array $apiOrders) {
195 | $apiOrderIds = array_column($apiOrders, 'order_id');
196 | /** array of order id => order entity */
197 | $orders = $this->orderRepository->findByApiOrderIds($ids);
198 | return f\filter(function(array $apiOrder) use ($orders) {
199 | return !array_key_exists($apiOrder['order_id'], $orders);
200 | }, $apiOrders);
201 | }),
202 | // chunk by 50 to save on database requests
203 | c\chunk(50)
204 | );
205 | }
206 |
207 | /** Returns an iterable of api orders */
208 | private function fetchOrdersFromApi(): callable {
209 | return function(ImportOrderRequest $req) {
210 | yield from $this->apiClient->fetch(/* pass in req args */);
211 | };
212 | }
213 | }
214 | ```
215 |
216 | ## Docs
217 |
218 | Docs are generated with `make docs`. This uses Krak Peridocs to actually generate the documentation from the peridot tests.
219 |
220 | ## Code Generation
221 |
222 | The constants and curried functions are generated with `make code`.
223 |
224 | ## Tests
225 |
226 | Tests are run via `make test` and are stored in the `test` directory. We use peridot for testing.
227 |
228 | ## API
229 |
230 |
231 | all(callable $predicate, iterable $iter): bool
232 |
233 | **Name:** `Krak\Fun\all`
234 |
235 | Returns true if the predicate returns true on all of the items:
236 |
237 | ```php
238 | $res = all(function ($v) {
239 | return $v % 2 == 0;
240 | }, [2, 4, 6]);
241 | expect($res)->equal(true);
242 | ```
243 |
244 | Returns false if the predicate returns false on any of the items:
245 |
246 | ```php
247 | $res = all(function ($v) {
248 | return $v % 2 == 0;
249 | }, [1, 2, 4, 6]);
250 | expect($res)->equal(false);
251 | ```
252 |
253 |
254 |
255 | any(callable $predicate, iterable $iter): bool
256 |
257 | **Name:** `Krak\Fun\any`
258 |
259 | Returns true if the predicate returns true on any of the items:
260 |
261 | ```php
262 | $res = any(function ($v) {
263 | return $v % 2 == 0;
264 | }, [1, 3, 4, 5]);
265 | expect($res)->equal(true);
266 | ```
267 |
268 | Returns false if the predicate returns false on all of the items:
269 |
270 | ```php
271 | $res = any(function ($v) {
272 | return $v % 2 == 0;
273 | }, [1, 3, 5]);
274 | expect($res)->equal(false);
275 | ```
276 |
277 |
278 |
279 | arrayCompact(iterable $iter): array
280 |
281 | **Name:** `Krak\Fun\arrayCompact`
282 |
283 | It will remove all nulls from an iterable and return an array:
284 |
285 | ```php
286 | $res = arrayCompact([1, 2, null, null, 3]);
287 | expect(\array_values($res))->equal([1, 2, 3]);
288 | ```
289 |
290 | Keep in mind that the keys will be preserved when using arrayCompact, so make sure to use array_values if you want to ignore keys.
291 |
292 | arrayFilter(callable $fn, iterable $data): array
293 |
294 | **Name:** `Krak\Fun\arrayFilter`
295 |
296 | Alias of array_filter:
297 |
298 | ```php
299 | $res = arrayFilter(partial(op, '<', 2), [1, 2, 3]);
300 | expect($res)->equal([1]);
301 | ```
302 |
303 | Filters iterables as well as arrays:
304 |
305 | ```php
306 | $res = arrayFilter(partial(op, '<', 2), range(1, 3));
307 | expect($res)->equal([1]);
308 | ```
309 |
310 |
311 |
312 | arrayMap(callable $fn, iterable $data): array
313 |
314 | **Name:** `Krak\Fun\arrayMap`
315 |
316 | Alias of array_map:
317 |
318 | ```php
319 | $res = arrayMap(partial(op, '*', 2), [1, 2, 3]);
320 | expect($res)->equal([2, 4, 6]);
321 | ```
322 |
323 | Maps iterables as well as arrays:
324 |
325 | ```php
326 | $res = arrayMap(partial(op, '*', 2), range(1, 3));
327 | expect($res)->equal([2, 4, 6]);
328 | ```
329 |
330 |
331 |
332 | arrayReindex(callable $fn, iterable $iter): array
333 |
334 | **Name:** `Krak\Fun\arrayReindex`
335 |
336 | Re-indexes a collection via a callable into an associative array:
337 |
338 | ```php
339 | $res = arrayReindex(function ($v) {
340 | return $v['id'];
341 | }, [['id' => 2], ['id' => 3], ['id' => 1]]);
342 | expect($res)->equal([2 => ['id' => 2], 3 => ['id' => 3], 1 => ['id' => 1]]);
343 | ```
344 |
345 |
346 |
347 | arrayWrap($value)
348 |
349 | **Name:** `Krak\Fun\arrayWrap`
350 |
351 | Wraps any non list array into an array:
352 |
353 | ```php
354 | $results = arrayMap(arrayWrap, [1, 'abc', ['a' => 1]]);
355 | expect($results)->equal([[1], ['abc'], [['a' => 1]]]);
356 | ```
357 |
358 | List based arrays are left as is:
359 |
360 | ```php
361 | $results = arrayMap(arrayWrap, [[], [1, 2, 3]]);
362 | expect($results)->equal([[], [1, 2, 3]]);
363 | ```
364 |
365 | Note: `array_is_list` which requires php 8.1 or symfony/polyfill-php81
366 |
367 | assign($obj, iterable $iter)
368 |
369 | **Name:** `Krak\Fun\assign`
370 |
371 | Assigns iterable keys and values to an object:
372 |
373 | ```php
374 | $obj = new \StdClass();
375 | $obj = assign($obj, ['a' => 1, 'b' => 2]);
376 | expect($obj->a)->equal(1);
377 | expect($obj->b)->equal(2);
378 | ```
379 |
380 |
381 |
382 | chain(iterable ...$iters)
383 |
384 | **Name:** `Krak\Fun\chain`
385 |
386 | Chains iterables together into one iterable:
387 |
388 | ```php
389 | $res = chain([1], range(2, 3));
390 | expect(toArray($res))->equal([1, 2, 3]);
391 | ```
392 |
393 |
394 |
395 | chunk(int $size, iterable $iter): iterable
396 |
397 | **Name:** `Krak\Fun\chunk`
398 |
399 | Chunks an iterable into equal sized chunks.:
400 |
401 | ```php
402 | $res = chunk(2, [1, 2, 3, 4]);
403 | expect(toArray($res))->equal([[1, 2], [3, 4]]);
404 | ```
405 |
406 | If there is any remainder, it is yielded as is:
407 |
408 | ```php
409 | $res = chunk(3, [1, 2, 3, 4]);
410 | expect(toArray($res))->equal([[1, 2, 3], [4]]);
411 | ```
412 |
413 |
414 |
415 | chunkBy(callable $fn, iterable $iter, ?int $maxSize = null): iterable
416 |
417 | **Name:** `Krak\Fun\chunkBy`
418 |
419 | Chunks items together off of the result from the callable:
420 |
421 | ```php
422 | $items = ['aa', 'ab', 'ac', 'ba', 'bb', 'bc', 'ca', 'cb', 'cc'];
423 | $chunks = chunkBy(function (string $item) {
424 | return $item[0];
425 | // return first char
426 | }, $items);
427 | expect(toArray($chunks))->equal([['aa', 'ab', 'ac'], ['ba', 'bb', 'bc'], ['ca', 'cb', 'cc']]);
428 | ```
429 |
430 | Allows a maxSize to prevent chunks from exceeding a limit:
431 |
432 | ```php
433 | $items = ['aa', 'ab', 'ac', 'ba', 'bb', 'bc', 'ca', 'cb', 'cc'];
434 | $chunks = chunkBy(function (string $item) {
435 | return $item[0];
436 | // return first char
437 | }, $items, 2);
438 | expect(toArray($chunks))->equal([['aa', 'ab'], ['ac'], ['ba', 'bb'], ['bc'], ['ca', 'cb'], ['cc']]);
439 | ```
440 |
441 |
442 |
443 | compact(iterable $iter): iterable
444 |
445 | **Name:** `Krak\Fun\compact`
446 |
447 | Removes all null values from an iterable:
448 |
449 | ```php
450 | $res = compact([1, null, 2, 3, null, null, 4]);
451 | expect(toArray($res))->equal([1, 2, 3, 4]);
452 | ```
453 |
454 |
455 |
456 | compose(callable ...$fns)
457 |
458 | **Name:** `Krak\Fun\compose`
459 |
460 | Composes functions together. compose(f, g)(x) == f(g(x)):
461 |
462 | ```php
463 | $mul2 = Curried\op('*')(2);
464 | $add3 = Curried\op('+')(3);
465 | $add3ThenMul2 = compose($mul2, $add3);
466 | $res = $add3ThenMul2(5);
467 | expect($res)->equal(16);
468 | ```
469 |
470 | Allows an empty initial argument:
471 |
472 | ```php
473 | $res = compose(Curried\reduce(function ($acc, $v) {
474 | return $acc + $v;
475 | }, 0), function () {
476 | yield from [1, 2, 3];
477 | })();
478 | expect($res)->equal(6);
479 | ```
480 |
481 |
482 |
483 | construct($className, ...$args)
484 |
485 | **Name:** `Krak\Fun\construct`
486 |
487 | Constructs (instantiates) a new class with the given arguments:
488 |
489 | ```php
490 | $res = construct(\ArrayObject::class, [1, 2, 3]);
491 | expect($res->count())->equal(3);
492 | ```
493 |
494 |
495 |
496 | curry(callable $fn, int $num = 1)
497 |
498 | **Name:** `Krak\Fun\curry`
499 |
500 | currys the given function $n times:
501 |
502 | ```php
503 | $res = curry(_idArgs::class, 2)(1)(2)(3);
504 | expect($res)->equal([1, 2, 3]);
505 | ```
506 |
507 | Given a function definition: (a, b) -> c. A curried version will look like (a) -> (b) -> c
508 |
509 | differenceWith(callable $cmp, iterable $a, iterable $b)
510 |
511 | **Name:** `Krak\Fun\differenceWith`
512 |
513 | Takes the difference between two iterables with a given comparator:
514 |
515 | ```php
516 | $res = differenceWith(partial(op, '==='), [1, 2, 3, 4, 5], [2, 3, 4]);
517 | expect(toArray($res))->equal([1, 5]);
518 | ```
519 |
520 |
521 |
522 | dd($value, callable $dump = null, callable $die = null)
523 |
524 | **Name:** `Krak\Fun\dd`
525 |
526 | dumps and dies:
527 |
528 | ```php
529 | $res = null;
530 | $died = false;
531 | $dump = function ($v) use(&$res) {
532 | $res = $v;
533 | };
534 | $die = function () use(&$died) {
535 | $died = true;
536 | };
537 | dd(1, $dump, $die);
538 | expect($res)->equal(1);
539 | expect($died)->equal(true);
540 | ```
541 |
542 |
543 |
544 | drop(int $num, iterable $iter): iterable
545 |
546 | **Name:** `Krak\Fun\drop`
547 |
548 | Drops the first num items from an iterable:
549 |
550 | ```php
551 | $res = drop(2, range(0, 3));
552 | expect(toArray($res))->equal([2, 3]);
553 | ```
554 |
555 |
556 |
557 | dropWhile(callable $predicate, iterable $iter): iterable
558 |
559 | **Name:** `Krak\Fun\dropWhile`
560 |
561 | Drops elements from the iterable while the predicate returns true:
562 |
563 | ```php
564 | $res = dropWhile(Curried\op('>')(0), [2, 1, 0, 1, 2]);
565 | expect(toArray($res))->equal([0, 1, 2]);
566 | ```
567 |
568 |
569 |
570 | each(callable $handle, iterable $iter)
571 |
572 | **Name:** `Krak\Fun\each`
573 |
574 | Invokes a callable on each item in an iterable:
575 |
576 | ```php
577 | $state = [(object) ['id' => 1], (object) ['id' => 2]];
578 | each(function ($item) {
579 | $item->id += 1;
580 | }, $state);
581 | expect([$state[0]->id, $state[1]->id])->equal([2, 3]);
582 | ```
583 |
584 | Normally using php foreach should suffice for iterating over an iterable; however, php variables in foreach loops are not scoped whereas closures are.
585 |
586 | filter(callable $predicate, iterable $iter): iterable
587 |
588 | **Name:** `Krak\Fun\filter`
589 |
590 | Lazily filters an iterable off of a predicate that should return true or false. If true, keep the data, else remove the data from the iterable:
591 |
592 | ```php
593 | $values = filter(partial(op, '>', 2), [1, 2, 3, 4]);
594 | // keep all items that are greater than 2
595 | expect(toArray($values))->equal([3, 4]);
596 | ```
597 |
598 |
599 |
600 | filterKeys(callable $predicate, iterable $iter): iterable
601 |
602 | **Name:** `Krak\Fun\filterKeys`
603 |
604 | Filters an iterable off of the keys:
605 |
606 | ```php
607 | $res = filterKeys(Curried\inArray(['a', 'b']), ['a' => 1, 'b' => 2, 'c' => 3]);
608 | expect(toArrayWithKeys($res))->equal(['a' => 1, 'b' => 2]);
609 | ```
610 |
611 |
612 |
613 | flatMap(callable $map, iterable $iter): iterable
614 |
615 | **Name:** `Krak\Fun\flatMap`
616 |
617 | Maps and then flattens an iterable:
618 |
619 | ```php
620 | $res = flatMap(function ($v) {
621 | return [-$v, $v];
622 | }, range(1, 3));
623 | expect(toArray($res))->equal([-1, 1, -2, 2, -3, 3]);
624 | ```
625 |
626 | flatMap is perfect for when you want to map an iterable and also add elements to the resulting iterable.
627 |
628 | flatten(iterable $iter, $levels = INF): iterable
629 |
630 | **Name:** `Krak\Fun\flatten`
631 |
632 | Flattens nested iterables into a flattened set of elements:
633 |
634 | ```php
635 | $res = flatten([1, [2, [3, [4]]]]);
636 | expect(toArray($res))->equal([1, 2, 3, 4]);
637 | ```
638 |
639 | Can flatten a specific number of levels:
640 |
641 | ```php
642 | $res = flatten([1, [2, [3]]], 1);
643 | expect(toArray($res))->equal([1, 2, [3]]);
644 | ```
645 |
646 | Flattening zero levels does nothing:
647 |
648 | ```php
649 | $res = flatten([1, [2]], 0);
650 | expect(toArray($res))->equal([1, [2]]);
651 | ```
652 |
653 |
654 |
655 | flip(iterable $iter): iterable
656 |
657 | **Name:** `Krak\Fun\flip`
658 |
659 | Flips the keys => values of an iterable to values => keys:
660 |
661 | ```php
662 | $res = flip(['a' => 0, 'b' => 1]);
663 | expect(toArray($res))->equal(['a', 'b']);
664 | ```
665 |
666 |
667 |
668 | fromPairs(iterable $iter): iterable
669 |
670 | **Name:** `Krak\Fun\fromPairs`
671 |
672 | Converts an iterable of tuples [$key, $value] into an associative iterable:
673 |
674 | ```php
675 | $res = fromPairs([['a', 1], ['b', 2]]);
676 | expect(toArrayWithKeys($res))->equal(['a' => 1, 'b' => 2]);
677 | ```
678 |
679 |
680 |
681 | groupBy(callable $fn, iterable $iter, ?int $maxSize = null): iterable
682 |
683 | **Name:** `Krak\Fun\groupBy`
684 |
685 | Alias of chunkBy
686 |
687 | Groups items together off of the result from the callable:
688 |
689 | ```php
690 | $items = ['aa', 'ab', 'ac', 'ba', 'bb', 'bc', 'ca', 'cb', 'cc'];
691 | $groupedItems = groupBy(function (string $item) {
692 | return $item[0];
693 | // return first char
694 | }, $items);
695 | expect(toArray($groupedItems))->equal([['aa', 'ab', 'ac'], ['ba', 'bb', 'bc'], ['ca', 'cb', 'cc']]);
696 | ```
697 |
698 | Allows a maxSize to prevent groups from exceeding a limit:
699 |
700 | ```php
701 | $items = ['aa', 'ab', 'ac', 'ba', 'bb', 'bc', 'ca', 'cb', 'cc'];
702 | $groupedItems = groupBy(function (string $item) {
703 | return $item[0];
704 | // return first char
705 | }, $items, 2);
706 | expect(toArray($groupedItems))->equal([['aa', 'ab'], ['ac'], ['ba', 'bb'], ['bc'], ['ca', 'cb'], ['cc']]);
707 | ```
708 |
709 |
710 |
711 | hasIndexIn(array $keys, array $data): bool
712 |
713 | **Name:** `Krak\Fun\hasIndexIn`
714 |
715 | Checks if a nested index exists in the given data:
716 |
717 | ```php
718 | $res = hasIndexIn(['a', 'b', 'c'], ['a' => ['b' => ['c' => null]]]);
719 | expect($res)->equal(true);
720 | ```
721 |
722 | Returns false if any of the indexes do not exist in the data:
723 |
724 | ```php
725 | $res = hasIndexIn(['a', 'b', 'c'], ['a' => ['b' => []]]);
726 | expect($res)->equal(false);
727 | ```
728 |
729 |
730 |
731 | head(iterable $iter)
732 |
733 | **Name:** `Krak\Fun\head`
734 |
735 | Returns the fist element in an iterable:
736 |
737 | ```php
738 | $res = head([1, 2, 3]);
739 | expect($res)->equal(1);
740 | ```
741 |
742 | But returns null if the iterable is empty:
743 |
744 | ```php
745 | $res = head([]);
746 | expect($res)->equal(null);
747 | ```
748 |
749 |
750 |
751 | inArray(array $set, $item): bool
752 |
753 | **Name:** `Krak\Fun\inArray`
754 |
755 | Checks if an item is within an array of items:
756 |
757 | ```php
758 | $res = inArray([1, 2, 3], 2);
759 | expect($res)->equal(true);
760 | ```
761 |
762 |
763 |
764 | index($key, $data, $else = null)
765 |
766 | **Name:** `Krak\Fun\index`
767 |
768 | Accesses an index in an array:
769 |
770 | ```php
771 | $res = index('a', ['a' => 1]);
772 | expect($res)->equal(1);
773 | ```
774 |
775 | If no value exists at the given index, $else will be returned:
776 |
777 | ```php
778 | $res = index('a', ['b' => 1], 2);
779 | expect($res)->equal(2);
780 | ```
781 |
782 | Also works with objects that implement ArrayAccess:
783 |
784 | ```php
785 | class MyClass implements \ArrayAccess
786 | {
787 | private $container = [];
788 | public function __construct()
789 | {
790 | $this->container = ['one' => 1, 'two' => 2];
791 | }
792 | public function offsetExists($offset)
793 | {
794 | return isset($this->container[$offset]);
795 | }
796 | public function offsetGet($offset)
797 | {
798 | return isset($this->container[$offset]) ? $this->container[$offset] : null;
799 | }
800 | public function offsetSet($offset, $value)
801 | {
802 | /* ... */
803 | }
804 | public function offsetUnset($offset)
805 | {
806 | /* ... */
807 | }
808 | }
809 | $object = new MyClass();
810 | expect(index('two', $object))->equal(2);
811 | expect(index('three', $object, 'else'))->equal('else');
812 | ```
813 |
814 |
815 |
816 | indexIn(array $keys, array $data, $else = null)
817 |
818 | **Name:** `Krak\Fun\indexIn`
819 |
820 | Accesses a nested index in a deep array structure:
821 |
822 | ```php
823 | $res = indexIn(['a', 'b'], ['a' => ['b' => 1]]);
824 | expect($res)->equal(1);
825 | ```
826 |
827 | If any of the indexes do not exist, $else will be returned:
828 |
829 | ```php
830 | $res = indexIn(['a', 'b'], ['a' => ['c' => 1]], 2);
831 | expect($res)->equal(2);
832 | ```
833 |
834 |
835 |
836 | indexOf(callable $predicate, iterable $iter)
837 |
838 | **Name:** `Krak\Fun\indexOf`
839 |
840 | Searches for an element and returns the key if found:
841 |
842 | ```php
843 | $res = indexOf(partial(op, '==', 'b'), ['a', 'b', 'c']);
844 | expect($res)->equal(1);
845 | ```
846 |
847 |
848 |
849 | isNull($val)
850 |
851 | **Name:** `Krak\Fun\isNull`
852 |
853 | alias for is_null:
854 |
855 | ```php
856 | expect(isNull(null))->equal(true);
857 | expect(isNull(0))->equal(false);
858 | ```
859 |
860 |
861 |
862 | iter($iter): \Iterator
863 |
864 | **Name:** `Krak\Fun\iter`
865 |
866 | Converts any iterable into a proper instance of Iterator.
867 |
868 | Can convert arrays:
869 |
870 | ```php
871 | expect(iter([1, 2, 3]))->instanceof('Iterator');
872 | ```
873 |
874 | Can convert an Iterator:
875 |
876 | ```php
877 | expect(iter(new \ArrayIterator([1, 2, 3])))->instanceof('Iterator');
878 | ```
879 |
880 | Can convert objects:
881 |
882 | ```php
883 | $obj = (object) ['a' => 1, 'b' => 2];
884 | expect(iter($obj))->instanceof('Iterator');
885 | expect(toArrayWithKeys(iter($obj)))->equal(['a' => 1, 'b' => 2]);
886 | ```
887 |
888 | Can convert any iterable:
889 |
890 | ```php
891 | $a = new class implements \IteratorAggregate
892 | {
893 | public function getIterator()
894 | {
895 | return new \ArrayIterator([1, 2, 3]);
896 | }
897 | };
898 | expect(iter($a))->instanceof('Iterator');
899 | expect(toArray(iter($a)))->equal([1, 2, 3]);
900 | ```
901 |
902 | Can convert strings:
903 |
904 | ```php
905 | expect(iter('abc'))->instanceof('Iterator');
906 | expect(toArray(iter('abc')))->equal(['a', 'b', 'c']);
907 | ```
908 |
909 | Will throw an exception otherwise:
910 |
911 | ```php
912 | expect(function () {
913 | iter(1);
914 | })->throw('LogicException', 'Iter could not be converted into an iterable.');
915 | ```
916 |
917 |
918 |
919 | join(string $sep, iterable $iter)
920 |
921 | **Name:** `Krak\Fun\join`
922 |
923 | Joins an iterable with a given separator:
924 |
925 | ```php
926 | $res = join(",", range(1, 3));
927 | expect($res)->equal("1,2,3");
928 | ```
929 |
930 |
931 |
932 | keys(iterable $iter): iterable
933 |
934 | **Name:** `Krak\Fun\keys`
935 |
936 | Yields only the keys of an in iterable:
937 |
938 | ```php
939 | $keys = keys(['a' => 1, 'b' => 2]);
940 | expect(toArray($keys))->equal(['a', 'b']);
941 | ```
942 |
943 |
944 |
945 | map(callable $predicate, iterable $iter): iterable
946 |
947 | **Name:** `Krak\Fun\map`
948 |
949 | Lazily maps an iterable's values to a different set:
950 |
951 | ```php
952 | $values = map(partial(op, '*', 2), [1, 2, 3, 4]);
953 | expect(toArray($values))->equal([2, 4, 6, 8]);
954 | ```
955 |
956 |
957 |
958 | mapAccum(callable $fn, iterable $iter, $acc = null)
959 |
960 | **Name:** `Krak\Fun\mapAccum`
961 |
962 | Maps a function to each element of a list while passing in an accumulator to accumulate over every iteration:
963 |
964 | ```php
965 | $data = iter('abcd');
966 | [$totalSort, $values] = mapAccum(function ($acc, $value) {
967 | return [$acc + 1, ['name' => $value, 'sort' => $acc]];
968 | }, iter('abcd'), 0);
969 | expect($totalSort)->equal(4);
970 | expect($values)->equal([['name' => 'a', 'sort' => 0], ['name' => 'b', 'sort' => 1], ['name' => 'c', 'sort' => 2], ['name' => 'd', 'sort' => 3]]);
971 | ```
972 |
973 | Note: mapAccum converts the interable into an array and is not lazy like most of the other functions in this library
974 |
975 | mapKeys(callable $predicate, iterable $iter): iterable
976 |
977 | **Name:** `Krak\Fun\mapKeys`
978 |
979 | Lazily maps an iterable's keys to a different set:
980 |
981 | ```php
982 | $keys = mapKeys(partial(op, '.', '_'), ['a' => 1, 'b' => 2]);
983 | expect(toArrayWithKeys($keys))->equal(['a_' => 1, 'b_' => 2]);
984 | ```
985 |
986 |
987 |
988 | mapKeyValue(callable $fn, iterable $iter): iterable
989 |
990 | **Name:** `Krak\Fun\mapKeyValue`
991 |
992 | Lazily maps an iterable's key/value tuples to a different set:
993 |
994 | ```php
995 | $keys = mapKeyValue(function ($kv) {
996 | [$key, $value] = $kv;
997 | return ["{$key}_", $value * $value];
998 | }, ['a' => 1, 'b' => 2]);
999 | expect(toArrayWithKeys($keys))->equal(['a_' => 1, 'b_' => 4]);
1000 | ```
1001 |
1002 |
1003 |
1004 | mapOn(array $maps, iterable $iter): iterable
1005 |
1006 | **Name:** `Krak\Fun\mapOn`
1007 |
1008 | Maps values on specific keys:
1009 |
1010 | ```php
1011 | $values = mapOn(['a' => partial(op, '*', 3), 'b' => partial(op, '+', 1)], ['a' => 1, 'b' => 2, 'c' => 3]);
1012 | expect(toArray($values))->equal([3, 3, 3]);
1013 | ```
1014 |
1015 |
1016 |
1017 | nullable(callable $fn, $value)
1018 |
1019 | **Name:** `Krak\Fun\nullable`
1020 |
1021 | Performs the callable if the value is not null:
1022 |
1023 | ```php
1024 | expect(nullable('intval', '0'))->equal(0);
1025 | ```
1026 |
1027 | Returns null if the value is null:
1028 |
1029 | ```php
1030 | expect(nullable('intval', null))->equal(null);
1031 | ```
1032 |
1033 |
1034 |
1035 | onEach(callable $handle, iterable $iter)
1036 |
1037 | **Name:** `Krak\Fun\onEach`
1038 |
1039 | Duplicate of each.
1040 |
1041 | Invokes a callable on each item in an iterable:
1042 |
1043 | ```php
1044 | $state = [(object) ['id' => 1], (object) ['id' => 2]];
1045 | onEach(function ($item) {
1046 | $item->id += 1;
1047 | }, $state);
1048 | expect([$state[0]->id, $state[1]->id])->equal([2, 3]);
1049 | ```
1050 |
1051 | Normally using php foreach should suffice for iterating over an iterable; however, php variables in foreach loops are not scoped whereas closures are.
1052 |
1053 | op(string $op, $b, $a)
1054 |
1055 | **Name:** `Krak\Fun\op`
1056 |
1057 | op evaluates binary operations. It expects the right hand operator first which makes most sense when currying or partially applying the op function.
1058 | When reading the op func, it should be read: `evaluate $op with $b with $a` e.g.:
1059 |
1060 | ```
1061 | op('+', 2, 3) -> add 2 with 3
1062 | op('-', 2, 3) -> subtract 2 from 3
1063 | op('>', 2, 3) => compare greater than 2 with 3
1064 | ```
1065 |
1066 | Evaluates two values with a given operator:
1067 |
1068 | ```php
1069 | $res = op('<', 2, 1);
1070 | expect($res)->equal(true);
1071 | ```
1072 |
1073 | Supports equality operators:
1074 |
1075 | ```php
1076 | $obj = new stdClass();
1077 | $ops = [['==', [1, 1]], ['eq', [2, 2]], ['!=', [1, 2]], ['neq', [2, 3]], ['===', [$obj, $obj]], ['!==', [new stdClass(), new stdClass()]], ['>', [1, 2]], ['gt', [1, 3]], ['>=', [1, 2]], ['gte', [1, 1]], ['<', [2, 1]], ['lt', [3, 1]], ['<=', [2, 1]], ['lte', [1, 1]]];
1078 | foreach ($ops as list($op, list($b, $a))) {
1079 | $res = op($op, $b, $a);
1080 | expect($res)->equal(true);
1081 | }
1082 | ```
1083 |
1084 | Supports other operators:
1085 |
1086 | ```php
1087 | $ops = [['+', [2, 3], 5], ['-', [2, 3], 1], ['*', [2, 3], 6], ['**', [2, 3], 9], ['/', [2, 3], 1.5], ['%', [2, 3], 1], ['.', ['b', 'a'], 'ab']];
1088 | foreach ($ops as list($op, list($b, $a), $expected)) {
1089 | $res = op($op, $b, $a);
1090 | expect($res)->equal($expected);
1091 | }
1092 | ```
1093 |
1094 | Is more useful partially applied or curried:
1095 |
1096 | ```php
1097 | $add2 = Curried\op('+')(2);
1098 | $mul3 = partial(op, '*', 3);
1099 | $sub4 = Curried\op('-')(4);
1100 | // ((2 + 2) * 3) - 4
1101 | $res = compose($sub4, $mul3, $add2)(2);
1102 | expect($res)->equal(8);
1103 | ```
1104 |
1105 |
1106 |
1107 | pad(int $size, iterable $iter, $padValue = null): iterable
1108 |
1109 | **Name:** `Krak\Fun\pad`
1110 |
1111 | Pads an iterable to a specific size:
1112 |
1113 | ```php
1114 | $res = pad(5, [1, 2, 3]);
1115 | expect(toArray($res))->equal([1, 2, 3, null, null]);
1116 | ```
1117 |
1118 | Allows custom pad values:
1119 |
1120 | ```php
1121 | $res = pad(5, [1, 2, 3], 0);
1122 | expect(toArray($res))->equal([1, 2, 3, 0, 0]);
1123 | ```
1124 |
1125 | Pads nothing if iterable is the same size as pad size:
1126 |
1127 | ```php
1128 | $res = pad(5, [1, 2, 3, 4, 5]);
1129 | expect(toArray($res))->equal([1, 2, 3, 4, 5]);
1130 | ```
1131 |
1132 | Pads nothing if iterable is greater than pad size:
1133 |
1134 | ```php
1135 | $res = pad(5, [1, 2, 3, 4, 5, 6]);
1136 | expect(toArray($res))->equal([1, 2, 3, 4, 5, 6]);
1137 | ```
1138 |
1139 | Ignores keys of original iterable:
1140 |
1141 | ```php
1142 | $res = pad(3, ['a' => 1, 'b' => 2]);
1143 | expect(toArrayWithKeys($res))->equal([1, 2, null]);
1144 | ```
1145 |
1146 |
1147 |
1148 | partial(callable $fn, ...$appliedArgs)
1149 |
1150 | **Name:** `Krak\Fun\partial`
1151 |
1152 | Partially applies arguments to a function. Given a function signature like f = (a, b, c) -> d, partial(f, a, b) -> (c) -> d:
1153 |
1154 | ```php
1155 | $fn = function ($a, $b, $c) {
1156 | return ($a + $b) * $c;
1157 | };
1158 | $fn = partial($fn, 1, 2);
1159 | // apply the two arguments (a, b) and return a new function with signature (c) -> d
1160 | expect($fn(3))->equal(9);
1161 | ```
1162 |
1163 | You can also use place holders when partially applying:
1164 |
1165 | ```php
1166 | $fn = function ($a, $b, $c) {
1167 | return ($a + $b) * $c;
1168 | };
1169 | // _() represents a placeholder for parameter b.
1170 | $fn = partial($fn, 1, _(), 3);
1171 | // create the new func with signature (b) -> d
1172 | expect($fn(2))->equal(9);
1173 | ```
1174 |
1175 | Full partial application also works:
1176 |
1177 | ```php
1178 | $fn = function ($a, $b) {
1179 | return [$a, $b];
1180 | };
1181 | $fn = partial($fn, 1, 2);
1182 | expect($fn())->equal([1, 2]);
1183 | ```
1184 |
1185 |
1186 |
1187 | partition(callable $partition, iterable $iter, int $numParts = 2): array
1188 |
1189 | **Name:** `Krak\Fun\partition`
1190 |
1191 | Splits an iterable into different arrays based off of a predicate. The predicate should return the index to partition the data into:
1192 |
1193 | ```php
1194 | list($left, $right) = partition(function ($v) {
1195 | return $v < 3 ? 0 : 1;
1196 | }, [1, 2, 3, 4]);
1197 | expect([$left, $right])->equal([[1, 2], [3, 4]]);
1198 | ```
1199 |
1200 |
1201 |
1202 | pick(iterable $fields, array $data): array
1203 |
1204 | **Name:** `Krak\Fun\pick`
1205 |
1206 | Picks only the given fields from a structured array:
1207 |
1208 | ```php
1209 | $res = pick(['a', 'b'], ['a' => 1, 'b' => 2, 'c' => 3]);
1210 | expect($res)->equal(['a' => 1, 'b' => 2]);
1211 | ```
1212 |
1213 | Can be used in curried form:
1214 |
1215 | ```php
1216 | $res = arrayMap(Curried\pick(['id', 'name']), [['id' => 1, 'name' => 'Foo', 'slug' => 'foo'], ['id' => 2, 'name' => 'Bar', 'slug' => 'bar']]);
1217 | expect($res)->equal([['id' => 1, 'name' => 'Foo'], ['id' => 2, 'name' => 'Bar']]);
1218 | ```
1219 |
1220 |
1221 |
1222 | pickBy(callable $pick, array $data): array
1223 |
1224 | **Name:** `Krak\Fun\pickBy`
1225 |
1226 | Picks only the fields that match the pick function from a structured array:
1227 |
1228 | ```php
1229 | $res = pickBy(Curried\spread(function (string $key, int $value) : bool {
1230 | return $value % 2 === 0;
1231 | }), ['a' => 1, 'b' => 2, 'c' => 3]);
1232 | expect($res)->equal(['b' => 2]);
1233 | ```
1234 |
1235 |
1236 |
1237 | pipe(callable ...$fns)
1238 |
1239 | **Name:** `Krak\Fun\pipe`
1240 |
1241 | Creates a function that pipes values from one func to the next.:
1242 |
1243 | ```php
1244 | $add3 = Curried\op('+')(3);
1245 | $mul2 = Curried\op('*')(2);
1246 | $add3ThenMul2 = pipe($add3, $mul2);
1247 | $res = $add3ThenMul2(5);
1248 | expect($res)->equal(16);
1249 | ```
1250 |
1251 | Allows an empty initial argument:
1252 |
1253 | ```php
1254 | $res = pipe(function () {
1255 | yield from [1, 2, 3];
1256 | }, Curried\reduce(function ($acc, $v) {
1257 | return $acc + $v;
1258 | }, 0))();
1259 | expect($res)->equal(6);
1260 | ```
1261 |
1262 | `pipe` and `compose` are sister functions and do the same thing except the functions are composed in reverse order. pipe(f, g)(x) = g(f(x))
1263 |
1264 | product(iterable ...$iters): iterable
1265 |
1266 | **Name:** `Krak\Fun\product`
1267 |
1268 | Creates a cartesian product of multiple sets:
1269 |
1270 | ```php
1271 | $res = product([1, 2], [3, 4], [5, 6]);
1272 | expect(toArray($res))->equal([[1, 3, 5], [1, 3, 6], [1, 4, 5], [1, 4, 6], [2, 3, 5], [2, 3, 6], [2, 4, 5], [2, 4, 6]]);
1273 | ```
1274 |
1275 |
1276 |
1277 | prop(string $key, $data, $else = null)
1278 |
1279 | **Name:** `Krak\Fun\prop`
1280 |
1281 | Accesses a property from an object:
1282 |
1283 | ```php
1284 | $obj = new \StdClass();
1285 | $obj->id = 1;
1286 | $res = prop('id', $obj);
1287 | expect($res)->equal(1);
1288 | ```
1289 |
1290 | If no property exists, it will return the $else value:
1291 |
1292 | ```php
1293 | $obj = new \StdClass();
1294 | $res = prop('id', $obj, 2);
1295 | expect($res)->equal(2);
1296 | ```
1297 |
1298 |
1299 |
1300 | propIn(array $props, $obj, $else = null)
1301 |
1302 | **Name:** `Krak\Fun\propIn`
1303 |
1304 | Accesses a property deep in an object tree:
1305 |
1306 | ```php
1307 | $obj = new \StdClass();
1308 | $obj->id = 1;
1309 | $obj->child = new \StdClass();
1310 | $obj->child->id = 2;
1311 | $res = propIn(['child', 'id'], $obj);
1312 | expect($res)->equal(2);
1313 | ```
1314 |
1315 | If any property is missing in the tree, it will return the $else value:
1316 |
1317 | ```php
1318 | $obj = new \StdClass();
1319 | $obj->id = 1;
1320 | $obj->child = new \StdClass();
1321 | $res = propIn(['child', 'id'], $obj, 3);
1322 | expect($res)->equal(3);
1323 | ```
1324 |
1325 |
1326 |
1327 | range($start, $end, $step = null)
1328 |
1329 | **Name:** `Krak\Fun\range`
1330 |
1331 | Creates an iterable of a range of values starting from $start going to $end inclusively incrementing by $step:
1332 |
1333 | ```php
1334 | $res = range(1, 3);
1335 | expect(toArray($res))->equal([1, 2, 3]);
1336 | ```
1337 |
1338 | It also allows a decreasing range:
1339 |
1340 | ```php
1341 | $res = range(3, 1);
1342 | expect(toArray($res))->equal([3, 2, 1]);
1343 | ```
1344 |
1345 | An exception will be thrown if the $step provided goes in the wrong direction:
1346 |
1347 | ```php
1348 | expect(function () {
1349 | toArray(range(1, 2, -1));
1350 | })->throw(\InvalidArgumentException::class);
1351 | expect(function () {
1352 | toArray(range(2, 1, 1));
1353 | })->throw(\InvalidArgumentException::class);
1354 | ```
1355 |
1356 |
1357 |
1358 | reduce(callable $reduce, iterable $iter, $acc = null)
1359 |
1360 | **Name:** `Krak\Fun\reduce`
1361 |
1362 | Reduces an iterable into a single value:
1363 |
1364 | ```php
1365 | $res = reduce(function ($acc, $v) {
1366 | return $acc + $v;
1367 | }, range(1, 3), 0);
1368 | expect($res)->equal(6);
1369 | ```
1370 |
1371 |
1372 |
1373 | reduceKeyValue(callable $reduce, iterable $iter, $acc = null)
1374 |
1375 | **Name:** `Krak\Fun\reduceKeyValue`
1376 |
1377 | Reduces an iterables key value pairs into a value:
1378 |
1379 | ```php
1380 | $res = reduceKeyValue(function ($acc, $kv) {
1381 | [$key, $value] = $kv;
1382 | return $acc . $key . $value;
1383 | }, fromPairs([['a', 1], ['b', 2]]), "");
1384 | expect($res)->equal("a1b2");
1385 | ```
1386 |
1387 |
1388 |
1389 | reindex(callable $fn, iterable $iter): iterable
1390 |
1391 | **Name:** `Krak\Fun\reindex`
1392 |
1393 | Re-indexes a collection via a callable:
1394 |
1395 | ```php
1396 | $res = reindex(function ($v) {
1397 | return $v['id'];
1398 | }, [['id' => 2], ['id' => 3], ['id' => 1]]);
1399 | expect(toArrayWithKeys($res))->equal([2 => ['id' => 2], 3 => ['id' => 3], 1 => ['id' => 1]]);
1400 | ```
1401 |
1402 |
1403 |
1404 | retry(callable $fn, $shouldRetry = null)
1405 |
1406 | **Name:** `Krak\Fun\retry`
1407 |
1408 | Executes a function and retries if an exception is thrown:
1409 |
1410 | ```php
1411 | $i = 0;
1412 | $res = retry(function () use(&$i) {
1413 | $i += 1;
1414 | if ($i <= 1) {
1415 | throw new \Exception('bad');
1416 | }
1417 | return $i;
1418 | });
1419 | expect($res)->equal(2);
1420 | ```
1421 |
1422 | Only retries $maxTries times else it gives up and bubbles the exception:
1423 |
1424 | ```php
1425 | expect(function () {
1426 | $i = 0;
1427 | retry(function () use(&$i) {
1428 | $i += 1;
1429 | throw new \Exception((string) $i);
1430 | }, 5);
1431 | })->throw('Exception', '6');
1432 | ```
1433 |
1434 | Retries until $shouldRetry returns false:
1435 |
1436 | ```php
1437 | $i = 0;
1438 | expect(function () {
1439 | $res = retry(function () use(&$i) {
1440 | $i += 1;
1441 | throw new \Exception((string) $i);
1442 | }, function ($numRetries, \Throwable $t = null) {
1443 | return $numRetries < 2;
1444 | });
1445 | })->throw('Exception', '2');
1446 | ```
1447 |
1448 | Sends numRetries into the main fn:
1449 |
1450 | ```php
1451 | $res = retry(function ($numRetries) {
1452 | if (!$numRetries) {
1453 | throw new Exception('bad');
1454 | }
1455 | return $numRetries;
1456 | }, 2);
1457 | expect($res)->equal(1);
1458 | ```
1459 |
1460 | Keep in mind that maxTries determines the number of *re*-tries. This means the function will execute maxTries + 1 times since the first invocation is not a retry.
1461 |
1462 | search(callable $predicate, iterable $iter)
1463 |
1464 | **Name:** `Krak\Fun\search`
1465 |
1466 | Searches for an element in a collection where the callable returns true:
1467 |
1468 | ```php
1469 | $res = search(function ($v) {
1470 | return $v['id'] == 2;
1471 | }, [['id' => 1], ['id' => 2], ['id' => 3]]);
1472 | expect($res)->equal(['id' => 2]);
1473 | ```
1474 |
1475 | Returns null if no element was found:
1476 |
1477 | ```php
1478 | $res = search(function ($v) {
1479 | return false;
1480 | }, [['id' => 1], ['id' => 2], ['id' => 3]]);
1481 | expect($res)->equal(null);
1482 | ```
1483 |
1484 |
1485 |
1486 | setIndex($key, $value, array $data)
1487 |
1488 | **Name:** `Krak\Fun\setIndex`
1489 |
1490 | Sets an index in an array:
1491 |
1492 | ```php
1493 | $res = setIndex('a', 1, []);
1494 | expect($res['a'])->equal(1);
1495 | ```
1496 |
1497 |
1498 |
1499 | setIndexIn(array $keys, $value, array $data)
1500 |
1501 | **Name:** `Krak\Fun\setIndexIn`
1502 |
1503 | Sets a nested index in an array:
1504 |
1505 | ```php
1506 | $res = setIndexIn(['a', 'b'], 1, ['a' => []]);
1507 | expect($res['a']['b'])->equal(1);
1508 | ```
1509 |
1510 |
1511 |
1512 | setProp(string $key, $value, $data)
1513 |
1514 | **Name:** `Krak\Fun\setProp`
1515 |
1516 | Sets a property in an object:
1517 |
1518 | ```php
1519 | $res = setProp('a', 1, (object) []);
1520 | expect($res->a)->equal(1);
1521 | ```
1522 |
1523 |
1524 |
1525 | slice(int $start, iterable $iter, $length = INF): iterable
1526 |
1527 | **Name:** `Krak\Fun\slice`
1528 |
1529 | It takes an inclusive slice from start to a given length of an interable:
1530 |
1531 | ```php
1532 | $sliced = slice(1, range(0, 4), 2);
1533 | expect(toArray($sliced))->equal([1, 2]);
1534 | ```
1535 |
1536 | If length is not supplied it default to the end of the iterable:
1537 |
1538 | ```php
1539 | $sliced = slice(2, range(0, 4));
1540 | expect(toArray($sliced))->equal([2, 3, 4]);
1541 | ```
1542 |
1543 | will not consume the iterator once the slice has been yielded:
1544 |
1545 | ```php
1546 | $i = 0;
1547 | $gen = function () use(&$i) {
1548 | foreach (range(0, 4) as $v) {
1549 | $i = $v;
1550 | (yield $i);
1551 | }
1552 | };
1553 | $sliced = toArray(slice(1, $gen(), 2));
1554 | expect($sliced)->equal([1, 2]);
1555 | expect($i)->equal(2);
1556 | ```
1557 |
1558 |
1559 |
1560 | sortFromArray(callable $fn, array $orderedElements, iterable $iter): array
1561 |
1562 | **Name:** `Krak\Fun\sortFromArray`
1563 |
1564 | Sort an iterable with a given array of ordered elements to sort by:
1565 |
1566 | ```php
1567 | $data = [['id' => 1, 'name' => 'A'], ['id' => 2, 'name' => 'B'], ['id' => 3, 'name' => 'C']];
1568 | $res = sortFromArray(Curried\index('id'), [2, 3, 1], $data);
1569 | expect(arrayMap(Curried\index('name'), $res))->equal(['B', 'C', 'A']);
1570 | ```
1571 |
1572 | Throws an exception if any item in the iterable is not within the orderedElements:
1573 |
1574 | ```php
1575 | expect(function () {
1576 | $data = [['id' => 1]];
1577 | $res = sortFromArray(Curried\index('id'), [], $data);
1578 | })->throw(\LogicException::class, 'Cannot sort element key 1 because it does not exist in the ordered elements.');
1579 | ```
1580 |
1581 | I've found this to be very useful when you fetch records from a database with a WHERE IN clause, and you need to make sure the results are in the same order as the ids in the WHERE IN clause.
1582 |
1583 | spread(callable $fn, array $data)
1584 |
1585 | **Name:** `Krak\Fun\spread`
1586 |
1587 | Spreads an array of arguments to a callable:
1588 |
1589 | ```php
1590 | $res = spread(function ($a, $b) {
1591 | return $a . $b;
1592 | }, ['a', 'b']);
1593 | expect($res)->equal('ab');
1594 | ```
1595 |
1596 | Can be used in the curried form to unpack tuple arguments:
1597 |
1598 | ```php
1599 | $res = arrayMap(Curried\spread(function (string $first, int $second) {
1600 | return $first . $second;
1601 | }), [['a', 1], ['b', 2]]);
1602 | expect($res)->equal(['a1', 'b2']);
1603 | ```
1604 |
1605 | Note: this is basically just an alias for `call_user_func_array` or simply a functional wrapper around the `...` (spread) operator.
1606 |
1607 | take(int $num, iterable $iter): iterable
1608 |
1609 | **Name:** `Krak\Fun\take`
1610 |
1611 | Takes the first num items from an iterable:
1612 |
1613 | ```php
1614 | $res = take(2, range(0, 10));
1615 | expect(toArray($res))->equal([0, 1]);
1616 | ```
1617 |
1618 |
1619 |
1620 | takeWhile(callable $predicate, iterable $iter): iterable
1621 |
1622 | **Name:** `Krak\Fun\takeWhile`
1623 |
1624 | Takes elements from an iterable while the $predicate returns true:
1625 |
1626 | ```php
1627 | $res = takeWhile(Curried\op('>')(0), [2, 1, 0, 1, 2]);
1628 | expect(toArray($res))->equal([2, 1]);
1629 | ```
1630 |
1631 |
1632 |
1633 | tap(callable $tap, $value)
1634 |
1635 | **Name:** `Krak\Fun\tap`
1636 |
1637 | Calls given tap function on value and returns value:
1638 |
1639 | ```php
1640 | $loggedValues = [];
1641 | $res = tap(function (string $v) use(&$loggedValues) {
1642 | $loggedValues[] = $v;
1643 | }, 'abc');
1644 | expect([$loggedValues[0], $res])->equal(['abc', 'abc']);
1645 | ```
1646 |
1647 | `tap` is useful anytime you need to operate on a value and do not want to modify the return value.
1648 |
1649 | throwIf(callable $throw, callable $if, $value)
1650 |
1651 | **Name:** `Krak\Fun\throwIf`
1652 |
1653 | Throws the given exception if value given evaluates to true:
1654 |
1655 | ```php
1656 | expect(function () {
1657 | throwIf(function (int $value) {
1658 | return new RuntimeException('Error: ' . $value);
1659 | }, function (int $value) {
1660 | return $value === 0;
1661 | }, 0);
1662 | })->throw(RuntimeException::class, 'Error: 0');
1663 | ```
1664 |
1665 | Returns given value if value evaluates to false:
1666 |
1667 | ```php
1668 | $res = throwIf(function (int $value) {
1669 | return new RuntimeException('Error: ' . $value);
1670 | }, function (int $value) {
1671 | return $value === 0;
1672 | }, 1);
1673 | expect($res)->equal(1);
1674 | ```
1675 |
1676 | Note: works best with short closures!
1677 |
1678 | toArray(iterable $iter): array
1679 |
1680 | **Name:** `Krak\Fun\toArray`
1681 |
1682 | will tranform any iterable into an array:
1683 |
1684 | ```php
1685 | $res = toArray((function () {
1686 | (yield 1);
1687 | (yield 2);
1688 | (yield 3);
1689 | })());
1690 | expect($res)->equal([1, 2, 3]);
1691 | ```
1692 |
1693 | can also be used as a constant:
1694 |
1695 | ```php
1696 | $res = compose(toArray, id)((function () {
1697 | (yield 1);
1698 | (yield 2);
1699 | (yield 3);
1700 | })());
1701 | expect($res)->equal([1, 2, 3]);
1702 | ```
1703 |
1704 |
1705 |
1706 | toArrayWithKeys(iterable $iter): array
1707 |
1708 | **Name:** `Krak\Fun\toArrayWithKeys`
1709 |
1710 | can convert to an array and keep the keys:
1711 |
1712 | ```php
1713 | $gen = function () {
1714 | (yield 'a' => 1);
1715 | (yield 'b' => 2);
1716 | };
1717 | expect(toArrayWithKeys($gen()))->equal(['a' => 1, 'b' => 2]);
1718 | ```
1719 |
1720 |
1721 |
1722 | toPairs(iterable $iter): iterable
1723 |
1724 | **Name:** `Krak\Fun\toPairs`
1725 |
1726 | Transforms an associative array into an iterable of tuples [$key, $value]:
1727 |
1728 | ```php
1729 | $res = toPairs(['a' => 1, 'b' => 2]);
1730 | expect(toArray($res))->equal([['a', 1], ['b', 2]]);
1731 | ```
1732 |
1733 |
1734 |
1735 | updateIndexIn(array $keys, callable $update, array $data): array
1736 |
1737 | **Name:** `Krak\Fun\updateIndexIn`
1738 |
1739 | Updates a nested element within a deep array structure:
1740 |
1741 | ```php
1742 | $data = ['a' => ['b' => ['c' => 3]]];
1743 | $data = updateIndexIn(['a', 'b', 'c'], function ($v) {
1744 | return $v * $v;
1745 | }, $data);
1746 | expect($data)->equal(['a' => ['b' => ['c' => 9]]]);
1747 | ```
1748 |
1749 | Throws an exception if nested key does not exist:
1750 |
1751 | ```php
1752 | expect(function () {
1753 | $data = ['a' => ['b' => ['c' => 9]]];
1754 | updateIndexIn(['a', 'c', 'c'], function () {
1755 | }, $data);
1756 | })->throw(\RuntimeException::class, 'Could not updateIn because the keys a -> c -> c could not be found.');
1757 | ```
1758 |
1759 |
1760 |
1761 | values(iterable $iter): iterable
1762 |
1763 | **Name:** `Krak\Fun\values`
1764 |
1765 | Exports only the values of an iterable:
1766 |
1767 | ```php
1768 | $res = values(['a' => 1, 'b' => 2]);
1769 | expect(toArrayWithKeys($res))->equal([1, 2]);
1770 | ```
1771 |
1772 |
1773 |
1774 | when(callable $if, callable $then, $value)
1775 |
1776 | **Name:** `Krak\Fun\when`
1777 |
1778 | Evaluates the given value with the $then callable if the predicate returns true:
1779 |
1780 | ```php
1781 | $if = function ($v) {
1782 | return $v == 3;
1783 | };
1784 | $then = function ($v) {
1785 | return $v * $v;
1786 | };
1787 | $res = when($if, $then, 3);
1788 | expect($res)->equal(9);
1789 | ```
1790 |
1791 | But will return the given value if the predicate returns false:
1792 |
1793 | ```php
1794 | $if = function ($v) {
1795 | return $v == 3;
1796 | };
1797 | $then = function ($v) {
1798 | return $v * $v;
1799 | };
1800 | $res = when($if, $then, 4);
1801 | expect($res)->equal(4);
1802 | ```
1803 |
1804 |
1805 |
1806 | withState(callable $fn, $initialState = null)
1807 |
1808 | **Name:** `Krak\Fun\withState`
1809 |
1810 | Decorate a function with accumulating state:
1811 |
1812 | ```php
1813 | $fn = withState(function ($state, $v) {
1814 | return [$state + 1, $state . ': ' . $v];
1815 | }, 1);
1816 | $res = arrayMap($fn, iter('abcd'));
1817 | expect($res)->equal(['1: a', '2: b', '3: c', '4: d']);
1818 | ```
1819 |
1820 |
1821 |
1822 | within(array $fields, iterable $iter): \Iterator
1823 |
1824 | **Name:** `Krak\Fun\within`
1825 |
1826 | Only allows keys within the given array to stay:
1827 |
1828 | ```php
1829 | $data = flip(iter('abcd'));
1830 | $res = within(['a', 'c'], $data);
1831 | expect(toArrayWithKeys($res))->equal(['a' => 0, 'c' => 2]);
1832 | ```
1833 |
1834 |
1835 |
1836 | without(array $fields, iterable $iter): \Iterator
1837 |
1838 | **Name:** `Krak\Fun\without`
1839 |
1840 | Filters an iterable to be without the given keys:
1841 |
1842 | ```php
1843 | $data = flip(iter('abcd'));
1844 | $res = without(['a', 'c'], $data);
1845 | expect(toArrayWithKeys($res))->equal(['b' => 1, 'd' => 3]);
1846 | ```
1847 |
1848 |
1849 |
1850 | zip(iterable ...$iters): \Iterator
1851 |
1852 | **Name:** `Krak\Fun\zip`
1853 |
1854 | Zips multiple iterables into an iterable n-tuples:
1855 |
1856 | ```php
1857 | $res = zip(iter('abc'), range(1, 3), [4, 5, 6]);
1858 | expect(toArray($res))->equal([['a', 1, 4], ['b', 2, 5], ['c', 3, 6]]);
1859 | ```
1860 |
1861 | Returns an empty iterable if no iters are present:
1862 |
1863 | ```php
1864 | expect(toArray(zip()))->equal([]);
1865 | ```
1866 |
1867 |
1868 |
--------------------------------------------------------------------------------
/bench/auto-curry.php:
--------------------------------------------------------------------------------
1 | $val) {
13 | yield $key => $predicate($val);
14 | }
15 | };
16 | }
17 |
18 | function mapAutoCurried(...$args) {
19 | return autoCurry($args, 2, function(callable $predicate, $data) {
20 | foreach ($data as $key => $val) {
21 | yield $key => $predicate($val);
22 | }
23 | });
24 | }
25 |
26 | function mapAutoCurriedStatic(...$args) {
27 | switch (count($args)) {
28 | case 1:
29 | return map1($args[0]);
30 | default:
31 | return map2(...$args);
32 | }
33 | }
34 |
35 | function map1(callable $predicate) {
36 | return function($data) use ($predicate) {
37 | return map2($predicate, $data);
38 | };
39 | }
40 | function map2(callable $predicate, $data) {
41 | foreach ($data as $key => $val) {
42 | yield $key => $predicate($val);
43 | }
44 | }
45 |
46 | function autoCurry(array $args, $numArgs, callable $fn) {
47 | if (count($args) >= $numArgs) {
48 | return $fn(...$args);
49 | }
50 | if (count($args) == $numArgs - 1) {
51 | return partial($fn, ...$args);
52 | }
53 | if (count($args) == 0) {
54 | return curry($fn, $numArgs - 1);
55 | }
56 |
57 | return curry(
58 | partial($fn, ...$args),
59 | ($numArgs - 1 - count($args))
60 | );
61 | }
62 |
63 | function plusOne() {
64 | static $fn;
65 | $fn = $fn ?: function($v) {
66 | return $v + 1;
67 | };
68 | return $fn;
69 | }
70 |
71 | $bm = new Benchmark();
72 | $bm->add('curried', function() {
73 | $res = compose(toArray, mapCurried(plusOne()))([1,2,3]);
74 | });
75 | // $bm->add('auto-curried', function() {
76 | // $res = compose(toArray, mapAutoCurried(plusOne()))([1,2,3]);
77 | // });
78 | // $bm->add('auto-curried-no-curry', function() {
79 | // $res = toArray(mapAutoCurried(plusOne(), [1,2,3]));
80 | // });
81 | $bm->add('auto-curried-static', function() {
82 | $res = compose(toArray, mapAutoCurriedStatic(plusOne()))([1,2,3]);
83 | });
84 | // $bm->add('auto-curried-static-no-curry', function() {
85 | // $res = toArray(mapAutoCurriedStatic(plusOne(), [1,2,3]));
86 | // });
87 | $bm->run();
88 |
--------------------------------------------------------------------------------
/bench/named-closures.php:
--------------------------------------------------------------------------------
1 | a = $a;
33 | }
34 |
35 | public function __invoke(int $b) {
36 | return new SumCurriedArg3($this->a, $b);
37 | }
38 | }
39 |
40 | final class SumCurriedArg3 {
41 | private $a;
42 | private $b;
43 | public function __construct(int $a, int $b) {
44 | $this->a = $a;
45 | $this->b = $b;
46 | }
47 |
48 | public function __invoke(int $c) {
49 | return $this->a + $this->b + $c;
50 | }
51 | }
52 |
53 | function sumCurriedClassStateMachine(int $a) {
54 | return new SumCurried($a);
55 | }
56 |
57 | final class SumCurried {
58 | private $a;
59 | private $b;
60 | private $arg = 1;
61 |
62 | public function __construct(int $a) {
63 | $this->a = $a;
64 | }
65 |
66 | public function __invoke(...$args) {
67 | if ($this->arg === 1) {
68 | $this->arg = 2;
69 | $this->arg2(...$args);
70 | return $this;
71 | } else if ($this->arg === 2) {
72 | return $this->arg3(...$args);
73 | }
74 | }
75 |
76 | private function arg2(int $b) {
77 | $this->b = $b;
78 | }
79 |
80 | private function arg3(int $c) {
81 | return $this->a + $this->b + $c;
82 | }
83 | }
84 |
85 | function sumCurriedNamed(int $a) {
86 | return namedClosure('sumCurriedArg_b', function(int $b) use ($a) {
87 | return namedClosure('sumCurriedArg_c', function(int $c) use ($a, $b) {
88 | return $a + $b + $c;
89 | });
90 | });
91 | }
92 |
93 | sumCurriedClass(1)(2)(null);
94 |
95 | $bm = new Benchmark();
96 | $bm->add('standard', function() {
97 | sumCurriedStandard(1)(2)(3);
98 | });
99 |
100 | $bm->add('class', function() {
101 | sumCurriedClass(1)(2)(3);
102 | });
103 |
104 | $bm->add('classSM', function() {
105 | sumCurriedClassStateMachine(1)(2)(3);
106 | });
107 |
108 | $bm->add('named', function() {
109 | sumCurriedNamed(1)(2)(3);
110 | });
111 |
112 | $bm->run();
113 |
--------------------------------------------------------------------------------
/bench/sort-from-array.php:
--------------------------------------------------------------------------------
1 | $id, 'name' => $id];
33 | }, $ids);
34 | $orderedElements = $ids;
35 | shuffle($orderedElements);
36 | $fn = c\index("id");
37 |
38 | // dump(sortFromArrayKSort($fn, $orderedElements, $data));
39 | // dump(sortFromArraySearch($fn, $orderedElements, $data));
40 |
41 | $bm = new Benchmark();
42 | $bm->add('sortFromArrayKSort', function() use ($data, $orderedElements, $fn) {
43 | $res = sortFromArrayKSort($fn, $orderedElements, $data);
44 | });
45 |
46 | $bm->add('sortFromArraySearch', function() use ($data, $orderedElements, $fn) {
47 | $res = sortFromArraySearch($fn, $orderedElements, $data);
48 | });
49 | $bm->run();
50 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "krak/fn",
3 | "description": "Functional library for php with proper currying",
4 | "type": "library",
5 | "license": "MIT",
6 | "keywords": [
7 | "functional",
8 | "php",
9 | "curry",
10 | "partial-application"
11 | ],
12 | "authors": [
13 | {
14 | "name": "RJ Garcia",
15 | "email": "ragboyjr@icloud.com"
16 | }
17 | ],
18 | "autoload": {
19 | "psr-4": {
20 | "Krak\\Fun\\": "src"
21 | },
22 | "files": ["src/fn.php", "src/curried.generated.php", "src/consts.generated.php", "src/consts.ns.generated.php", "src/c.generated.php", "src/f.generated.php", "src/generate.php"]
23 | },
24 | "require-dev": {
25 | "krak/peridocs": "dev-master",
26 | "lavoiesl/php-benchmark": "^1.4",
27 | "nikic/php-parser": "^4.0",
28 | "peridot-php/leo": "^1.6",
29 | "peridot-php/peridot": "^1.19",
30 | "symfony/polyfill-php81": "^1.27",
31 | "symfony/var-dumper": "^4.0"
32 | },
33 | "suggest": {
34 | "symfony/polyfill-php81": "To use arrayWrap in PHP 7"
35 | },
36 | "extra": {
37 | "branch-alias": {
38 | "dev-master": "0.1.0-dev"
39 | }
40 | },
41 | "minimum-stability": "dev",
42 | "prefer-stable": true,
43 | "scripts": {
44 | "test": "peridot test"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | services:
4 | php:
5 | build: .
6 | command: "bash -c 'echo \"Container running.\" && tail -f /dev/null'"
7 | working_dir: /var/www/html
8 | volumes:
9 | - ./:/var/www/html
--------------------------------------------------------------------------------
/make-code.php:
--------------------------------------------------------------------------------
1 | create(ParserFactory::PREFER_PHP7);
25 |
26 | try {
27 | $ast = $parser->parse(file_get_contents(__DIR__ . '/src/fn.php'));
28 | $ast[0]->setDocComment(createDocComment());
29 |
30 | $ns = $ast[0];
31 | $originalStmts = $ns->stmts;
32 |
33 | $constStmts = filter(isInstance(Function_::class), $originalStmts);
34 | $constStmts = toArray(map(function($stmt) use ($ns) {
35 | return createConstFromFunc($stmt, $ns);
36 | }, $constStmts));
37 |
38 | $curriedStmts = filter(andf(
39 | isInstance(Function_::class),
40 | 'Krak\Fun\Generate\isCurryable'
41 | ), $originalStmts);
42 | $curriedStmts = toArray(map(function($stmt) {
43 | return curryFunction($stmt);
44 | }, $curriedStmts));
45 |
46 | $ast = [$ns];
47 | } catch (Error $error) {
48 | echo "Parse error: {$error->getMessage()}\n";
49 | return;
50 | }
51 |
52 | $pp = new PrettyPrinter\Standard();
53 |
54 | $ns->name->parts[] = 'Curried';
55 | $ns->stmts = toArray($curriedStmts);
56 | file_put_contents(__DIR__ . '/src/curried.generated.php', $pp->prettyPrintFile($ast));
57 |
58 | $ast[0]->name->parts[2] = 'c';
59 | $ast[0]->stmts = toArray(chain($curriedStmts, $constStmts));
60 | file_put_contents(__DIR__ . '/src/c.generated.php', $pp->prettyPrintFile($ast));
61 |
62 | $ast[0]->name->parts[2] = 'f';
63 | $ast[0]->stmts = toArray($originalStmts);
64 | file_put_contents(__DIR__ . '/src/f.generated.php', $pp->prettyPrintFile($ast));
65 |
66 | $ast[0]->name->parts[2] = 'Consts';
67 | $ast[0]->stmts = toArray($constStmts);
68 | file_put_contents(__DIR__ . '/src/consts.ns.generated.php', $pp->prettyPrintFile($ast));
69 |
70 | unset($ast[0]->name->parts[2]);
71 | $ast[0]->stmts = toArray($constStmts);
72 | file_put_contents(__DIR__ . '/src/consts.generated.php', $pp->prettyPrintFile($ast));
73 | }
74 |
75 | main();
76 |
--------------------------------------------------------------------------------
/peridot.php:
--------------------------------------------------------------------------------
1 | true,
9 | 'nsPrefix' => 'Krak\\Fun\\',
10 | 'numTableRows' => 6,
11 | ]);
12 | });
13 | };
14 |
15 |
--------------------------------------------------------------------------------
/src/c.generated.php:
--------------------------------------------------------------------------------
1 | {$name}(...$optionalArgs);
10 | };
11 | }
12 | function prop(string $key, $else = null)
13 | {
14 | return function ($data) use($key, $else) {
15 | return \property_exists($data, $key) ? $data->{$key} : $else;
16 | };
17 | }
18 | function index($key, $else = null)
19 | {
20 | return function ($data) use($key, $else) {
21 | if (\Krak\Fun\isInstance(\ArrayAccess::class, $data)) {
22 | $exists = $data->offsetExists($key);
23 | } else {
24 | $exists = \array_key_exists($key, $data);
25 | }
26 | return $exists ? $data[$key] : $else;
27 | };
28 | }
29 | function setProp(string $key)
30 | {
31 | return function ($value) use($key) {
32 | return function ($data) use($value, $key) {
33 | $data->{$key} = $value;
34 | return $data;
35 | };
36 | };
37 | }
38 | function setIndex($key)
39 | {
40 | return function ($value) use($key) {
41 | return function (array $data) use($value, $key) {
42 | $data[$key] = $value;
43 | return $data;
44 | };
45 | };
46 | }
47 | function setIndexIn(array $keys)
48 | {
49 | return function ($value) use($keys) {
50 | return function (array $data) use($value, $keys) {
51 | return \Krak\Fun\updateIndexIn($keys, function () use($value) {
52 | return $value;
53 | }, $data);
54 | };
55 | };
56 | }
57 | function propIn(array $props, $else = null)
58 | {
59 | return function ($obj) use($props, $else) {
60 | foreach ($props as $prop) {
61 | if (!\is_object($obj) || !\property_exists($obj, $prop)) {
62 | return $else;
63 | }
64 | $obj = $obj->{$prop};
65 | }
66 | return $obj;
67 | };
68 | }
69 | function indexIn(array $keys, $else = null)
70 | {
71 | return function (array $data) use($keys, $else) {
72 | foreach ($keys as $part) {
73 | if (!\is_array($data) || !\array_key_exists($part, $data)) {
74 | return $else;
75 | }
76 | $data = $data[$part];
77 | }
78 | return $data;
79 | };
80 | }
81 | function hasIndexIn(array $keys)
82 | {
83 | return function (array $data) use($keys) {
84 | foreach ($keys as $key) {
85 | if (!\is_array($data) || !\array_key_exists($key, $data)) {
86 | return false;
87 | }
88 | $data = $data[$key];
89 | }
90 | return true;
91 | };
92 | }
93 | function updateIndexIn(array $keys)
94 | {
95 | return function (callable $update) use($keys) {
96 | return function (array $data) use($update, $keys) {
97 | $curData =& $data;
98 | foreach (\array_slice($keys, 0, -1) as $key) {
99 | if (!\array_key_exists($key, $curData)) {
100 | throw new \RuntimeException('Could not updateIn because the keys ' . \implode(' -> ', $keys) . ' could not be found.');
101 | }
102 | $curData =& $curData[$key];
103 | }
104 | $lastKey = $keys[count($keys) - 1];
105 | $curData[$lastKey] = $update($curData[$lastKey] ?? null);
106 | return $data;
107 | };
108 | };
109 | }
110 | function assign($obj)
111 | {
112 | return function (iterable $iter) use($obj) {
113 | foreach ($iter as $key => $value) {
114 | $obj->{$key} = $value;
115 | }
116 | return $obj;
117 | };
118 | }
119 | function join(string $sep)
120 | {
121 | return function (iterable $iter) use($sep) {
122 | return \Krak\Fun\reduce(function ($acc, $v) use($sep) {
123 | return $acc ? $acc . $sep . $v : $v;
124 | }, $iter, "");
125 | };
126 | }
127 | function construct($className)
128 | {
129 | return function (...$args) use($className) {
130 | return new $className(...$args);
131 | };
132 | }
133 | function spread(callable $fn)
134 | {
135 | return function (array $data) use($fn) {
136 | return $fn(...$data);
137 | };
138 | }
139 | function dd(callable $dump = null, callable $die = null)
140 | {
141 | return function ($value) use($dump, $die) {
142 | $dump = $dump ?: (function_exists('dump') ? 'dump' : 'var_dump');
143 | $dump($value);
144 | ($die ?? function () {
145 | die;
146 | })();
147 | };
148 | }
149 | function takeWhile(callable $predicate)
150 | {
151 | return function (iterable $iter) use($predicate) {
152 | foreach ($iter as $k => $v) {
153 | if ($predicate($v)) {
154 | (yield $k => $v);
155 | } else {
156 | return;
157 | }
158 | }
159 | };
160 | }
161 | function dropWhile(callable $predicate)
162 | {
163 | return function (iterable $iter) use($predicate) {
164 | $stillDropping = true;
165 | foreach ($iter as $k => $v) {
166 | if ($stillDropping && $predicate($v)) {
167 | continue;
168 | } else {
169 | if ($stillDropping) {
170 | $stillDropping = false;
171 | }
172 | }
173 | (yield $k => $v);
174 | }
175 | };
176 | }
177 | function take(int $num)
178 | {
179 | return function (iterable $iter) use($num) {
180 | return \Krak\Fun\slice(0, $iter, $num);
181 | };
182 | }
183 | function drop(int $num)
184 | {
185 | return function (iterable $iter) use($num) {
186 | return \Krak\Fun\slice($num, $iter);
187 | };
188 | }
189 | function slice(int $start, $length = INF)
190 | {
191 | return function (iterable $iter) use($start, $length) {
192 | assert($start >= 0);
193 | $i = 0;
194 | $end = $start + $length - 1;
195 | foreach ($iter as $k => $v) {
196 | if ($start <= $i && $i <= $end) {
197 | (yield $k => $v);
198 | }
199 | $i += 1;
200 | if ($i > $end) {
201 | return;
202 | }
203 | }
204 | };
205 | }
206 | function chunk(int $size)
207 | {
208 | return function (iterable $iter) use($size) {
209 | assert($size > 0);
210 | $chunk = [];
211 | foreach ($iter as $v) {
212 | $chunk[] = $v;
213 | if (\count($chunk) == $size) {
214 | (yield $chunk);
215 | $chunk = [];
216 | }
217 | }
218 | if ($chunk) {
219 | (yield $chunk);
220 | }
221 | };
222 | }
223 | function chunkBy(callable $fn, ?int $maxSize = null)
224 | {
225 | return function (iterable $iter) use($fn, $maxSize) {
226 | assert($maxSize === null || $maxSize > 0);
227 | $group = [];
228 | $groupKey = null;
229 | foreach ($iter as $v) {
230 | $curGroupKey = $fn($v);
231 | $shouldYieldGroup = $groupKey !== null && $groupKey !== $curGroupKey || $maxSize !== null && \count($group) >= $maxSize;
232 | if ($shouldYieldGroup) {
233 | (yield $group);
234 | $group = [];
235 | }
236 | $group[] = $v;
237 | $groupKey = $curGroupKey;
238 | }
239 | if (\count($group)) {
240 | (yield $group);
241 | }
242 | };
243 | }
244 | function groupBy(callable $fn, ?int $maxSize = null)
245 | {
246 | return function (iterable $iter) use($fn, $maxSize) {
247 | return \Krak\Fun\chunkBy($fn, $iter, $maxSize);
248 | };
249 | }
250 | function range($start, $step = null)
251 | {
252 | return function ($end) use($start, $step) {
253 | if ($start == $end) {
254 | (yield $start);
255 | } else {
256 | if ($start < $end) {
257 | $step = $step ?: 1;
258 | if ($step <= 0) {
259 | throw new \InvalidArgumentException('Step must be greater than 0.');
260 | }
261 | for ($i = $start; $i <= $end; $i += $step) {
262 | (yield $i);
263 | }
264 | } else {
265 | $step = $step ?: -1;
266 | if ($step >= 0) {
267 | throw new \InvalidArgumentException('Step must be less than 0.');
268 | }
269 | for ($i = $start; $i >= $end; $i += $step) {
270 | (yield $i);
271 | }
272 | }
273 | }
274 | };
275 | }
276 | function op(string $op)
277 | {
278 | return function ($b) use($op) {
279 | return function ($a) use($b, $op) {
280 | switch ($op) {
281 | case '==':
282 | case 'eq':
283 | return $a == $b;
284 | case '!=':
285 | case 'neq':
286 | return $a != $b;
287 | case '===':
288 | return $a === $b;
289 | case '!==':
290 | return $a !== $b;
291 | case '>':
292 | case 'gt':
293 | return $a > $b;
294 | case '>=':
295 | case 'gte':
296 | return $a >= $b;
297 | case '<':
298 | case 'lt':
299 | return $a < $b;
300 | case '<=':
301 | case 'lte':
302 | return $a <= $b;
303 | case '+':
304 | return $a + $b;
305 | case '-':
306 | return $a - $b;
307 | case '*':
308 | return $a * $b;
309 | case '**':
310 | return $a ** $b;
311 | case '/':
312 | return $a / $b;
313 | case '%':
314 | return $a % $b;
315 | case '.':
316 | return $a . $b;
317 | default:
318 | throw new \LogicException('Invalid operator ' . $op);
319 | }
320 | };
321 | };
322 | }
323 | function flatMap(callable $map)
324 | {
325 | return function (iterable $iter) use($map) {
326 | foreach ($iter as $k => $v) {
327 | foreach ($map($v) as $k => $v) {
328 | (yield $k => $v);
329 | }
330 | }
331 | };
332 | }
333 | function flatten($levels = INF)
334 | {
335 | return function (iterable $iter) use($levels) {
336 | if ($levels == 0) {
337 | yield from $iter;
338 | } else {
339 | if ($levels == 1) {
340 | foreach ($iter as $k => $v) {
341 | if (\is_iterable($v)) {
342 | foreach ($v as $k1 => $v1) {
343 | (yield $k1 => $v1);
344 | }
345 | } else {
346 | (yield $k => $v);
347 | }
348 | }
349 | } else {
350 | foreach ($iter as $k => $v) {
351 | if (\is_iterable($v)) {
352 | foreach (flatten($v, $levels - 1) as $k1 => $v1) {
353 | (yield $k1 => $v1);
354 | }
355 | } else {
356 | (yield $k => $v);
357 | }
358 | }
359 | }
360 | }
361 | };
362 | }
363 | function when(callable $if)
364 | {
365 | return function (callable $then) use($if) {
366 | return function ($value) use($then, $if) {
367 | return $if($value) ? $then($value) : $value;
368 | };
369 | };
370 | }
371 | function pick(iterable $fields)
372 | {
373 | return function (array $data) use($fields) {
374 | $pickedData = [];
375 | foreach ($fields as $field) {
376 | $pickedData[$field] = $data[$field] ?? null;
377 | }
378 | return $pickedData;
379 | };
380 | }
381 | function pickBy(callable $pick)
382 | {
383 | return function (array $data) use($pick) {
384 | $pickedData = [];
385 | foreach ($data as $key => $value) {
386 | if ($pick([$key, $value])) {
387 | $pickedData[$key] = $value;
388 | }
389 | }
390 | return $pickedData;
391 | };
392 | }
393 | function within(array $fields)
394 | {
395 | return function (iterable $iter) use($fields) {
396 | return \Krak\Fun\filterKeys(\Krak\Fun\Curried\inArray($fields), $iter);
397 | };
398 | }
399 | function without(array $fields)
400 | {
401 | return function (iterable $iter) use($fields) {
402 | return \Krak\Fun\filterKeys(\Krak\Fun\Curried\not(\Krak\Fun\Curried\inArray($fields)), $iter);
403 | };
404 | }
405 | function pad(int $size, $padValue = null)
406 | {
407 | return function (iterable $iter) use($size, $padValue) {
408 | $i = 0;
409 | foreach ($iter as $key => $value) {
410 | (yield $value);
411 | $i += 1;
412 | }
413 | if ($i >= $size) {
414 | return;
415 | }
416 | foreach (\Krak\Fun\range($i, $size - 1) as $index) {
417 | (yield $padValue);
418 | }
419 | };
420 | }
421 | function inArray(array $set)
422 | {
423 | return function ($item) use($set) {
424 | return \in_array($item, $set);
425 | };
426 | }
427 | function arrayMap(callable $fn)
428 | {
429 | return function (iterable $data) use($fn) {
430 | return \array_map($fn, \is_array($data) ? $data : \Krak\Fun\toArray($data));
431 | };
432 | }
433 | function arrayFilter(callable $fn)
434 | {
435 | return function (iterable $data) use($fn) {
436 | return \array_filter(\is_array($data) ? $data : \Krak\Fun\toArray($data), $fn);
437 | };
438 | }
439 | function all(callable $predicate)
440 | {
441 | return function (iterable $iter) use($predicate) {
442 | foreach ($iter as $key => $value) {
443 | if (!$predicate($value)) {
444 | return false;
445 | }
446 | }
447 | return true;
448 | };
449 | }
450 | function any(callable $predicate)
451 | {
452 | return function (iterable $iter) use($predicate) {
453 | foreach ($iter as $key => $value) {
454 | if ($predicate($value)) {
455 | return true;
456 | }
457 | }
458 | return false;
459 | };
460 | }
461 | function search(callable $predicate)
462 | {
463 | return function (iterable $iter) use($predicate) {
464 | foreach ($iter as $value) {
465 | if ($predicate($value)) {
466 | return $value;
467 | }
468 | }
469 | };
470 | }
471 | function indexOf(callable $predicate)
472 | {
473 | return function (iterable $iter) use($predicate) {
474 | foreach ($iter as $key => $value) {
475 | if ($predicate($value)) {
476 | return $key;
477 | }
478 | }
479 | };
480 | }
481 | function trans(callable $trans)
482 | {
483 | return function (callable $fn) use($trans) {
484 | return function ($data) use($fn, $trans) {
485 | return $fn($trans($data));
486 | };
487 | };
488 | }
489 | function not(callable $fn)
490 | {
491 | return function (...$args) use($fn) {
492 | return !$fn(...$args);
493 | };
494 | }
495 | function isInstance($class)
496 | {
497 | return function ($item) use($class) {
498 | return $item instanceof $class;
499 | };
500 | }
501 | function nullable(callable $fn)
502 | {
503 | return function ($value) use($fn) {
504 | return $value === null ? $value : $fn($value);
505 | };
506 | }
507 | function partition(callable $partition, int $numParts = 2)
508 | {
509 | return function (iterable $iter) use($partition, $numParts) {
510 | $parts = \array_fill(0, $numParts, []);
511 | foreach ($iter as $val) {
512 | $index = (int) $partition($val);
513 | $parts[$index][] = $val;
514 | }
515 | return $parts;
516 | };
517 | }
518 | function map(callable $predicate)
519 | {
520 | return function (iterable $iter) use($predicate) {
521 | foreach ($iter as $key => $value) {
522 | (yield $key => $predicate($value));
523 | }
524 | };
525 | }
526 | function mapKeys(callable $predicate)
527 | {
528 | return function (iterable $iter) use($predicate) {
529 | foreach ($iter as $key => $value) {
530 | (yield $predicate($key) => $value);
531 | }
532 | };
533 | }
534 | function mapKeyValue(callable $fn)
535 | {
536 | return function (iterable $iter) use($fn) {
537 | foreach ($iter as $key => $value) {
538 | [$key, $value] = $fn([$key, $value]);
539 | (yield $key => $value);
540 | }
541 | };
542 | }
543 | function mapOn(array $maps)
544 | {
545 | return function (iterable $iter) use($maps) {
546 | foreach ($iter as $key => $value) {
547 | if (isset($maps[$key])) {
548 | (yield $key => $maps[$key]($value));
549 | } else {
550 | (yield $key => $value);
551 | }
552 | }
553 | };
554 | }
555 | function mapAccum(callable $fn, $acc = null)
556 | {
557 | return function (iterable $iter) use($fn, $acc) {
558 | $data = [];
559 | foreach ($iter as $key => $value) {
560 | [$acc, $value] = $fn($acc, $value);
561 | $data[] = $value;
562 | }
563 | return [$acc, $data];
564 | };
565 | }
566 | function withState($initialState = null)
567 | {
568 | return function (callable $fn) use($initialState) {
569 | $state = $initialState;
570 | return function (...$args) use($fn, &$state) {
571 | [$state, $res] = $fn($state, ...$args);
572 | return $res;
573 | };
574 | };
575 | }
576 | function arrayReindex(callable $fn)
577 | {
578 | return function (iterable $iter) use($fn) {
579 | $res = [];
580 | foreach ($iter as $key => $value) {
581 | $res[$fn($value)] = $value;
582 | }
583 | return $res;
584 | };
585 | }
586 | function reindex(callable $fn)
587 | {
588 | return function (iterable $iter) use($fn) {
589 | foreach ($iter as $key => $value) {
590 | (yield $fn($value) => $value);
591 | }
592 | };
593 | }
594 | function reduce(callable $reduce, $acc = null)
595 | {
596 | return function (iterable $iter) use($reduce, $acc) {
597 | foreach ($iter as $key => $value) {
598 | $acc = $reduce($acc, $value);
599 | }
600 | return $acc;
601 | };
602 | }
603 | function reduceKeyValue(callable $reduce, $acc = null)
604 | {
605 | return function (iterable $iter) use($reduce, $acc) {
606 | foreach ($iter as $key => $value) {
607 | $acc = $reduce($acc, [$key, $value]);
608 | }
609 | return $acc;
610 | };
611 | }
612 | function filter(callable $predicate)
613 | {
614 | return function (iterable $iter) use($predicate) {
615 | foreach ($iter as $key => $value) {
616 | if ($predicate($value)) {
617 | (yield $key => $value);
618 | }
619 | }
620 | };
621 | }
622 | function filterKeys(callable $predicate)
623 | {
624 | return function (iterable $iter) use($predicate) {
625 | foreach ($iter as $key => $value) {
626 | if ($predicate($key)) {
627 | (yield $key => $value);
628 | }
629 | }
630 | };
631 | }
632 | function partial(callable $fn)
633 | {
634 | return function (...$appliedArgs) use($fn) {
635 | return function (...$args) use($fn, $appliedArgs) {
636 | list($appliedArgs, $args) = \array_reduce($appliedArgs, function ($acc, $arg) {
637 | list($appliedArgs, $args) = $acc;
638 | if ($arg === \Krak\Fun\placeholder()) {
639 | $arg = array_shift($args);
640 | }
641 | $appliedArgs[] = $arg;
642 | return [$appliedArgs, $args];
643 | }, [[], $args]);
644 | return $fn(...$appliedArgs, ...$args);
645 | };
646 | };
647 | }
648 | function tap(callable $tap)
649 | {
650 | return function ($value) use($tap) {
651 | $tap($value);
652 | return $value;
653 | };
654 | }
655 | function throwIf(callable $throw)
656 | {
657 | return function (callable $if) use($throw) {
658 | return function ($value) use($if, $throw) {
659 | if ($if($value)) {
660 | throw $throw($value);
661 | }
662 | return $value;
663 | };
664 | };
665 | }
666 | function differenceWith(callable $cmp)
667 | {
668 | return function (iterable $a) use($cmp) {
669 | return function (iterable $b) use($a, $cmp) {
670 | return \Krak\Fun\filter(function ($aItem) use($cmp, $b) {
671 | return \Krak\Fun\indexOf(\Krak\Fun\partial($cmp, $aItem), $b) === null;
672 | }, $a);
673 | };
674 | };
675 | }
676 | function sortFromArray(callable $fn)
677 | {
678 | return function (array $orderedElements) use($fn) {
679 | return function (iterable $iter) use($orderedElements, $fn) {
680 | $data = [];
681 | $flippedElements = \array_flip($orderedElements);
682 | foreach ($iter as $value) {
683 | $key = $fn($value);
684 | if (!\array_key_exists($key, $flippedElements)) {
685 | throw new \LogicException('Cannot sort element key ' . $key . ' because it does not exist in the ordered elements.');
686 | }
687 | $data[$flippedElements[$key]] = $value;
688 | }
689 | ksort($data);
690 | return $data;
691 | };
692 | };
693 | }
694 | function retry($shouldRetry = null)
695 | {
696 | return function (callable $fn) use($shouldRetry) {
697 | if (\is_null($shouldRetry)) {
698 | $shouldRetry = function ($numRetries, \Throwable $t = null) {
699 | return true;
700 | };
701 | }
702 | if (\is_int($shouldRetry)) {
703 | $maxTries = $shouldRetry;
704 | if ($maxTries < 0) {
705 | throw new \LogicException("maxTries must be greater than or equal to 0");
706 | }
707 | $shouldRetry = function ($numRetries, \Throwable $t = null) use($maxTries) {
708 | return $numRetries <= $maxTries;
709 | };
710 | }
711 | if (!\is_callable($shouldRetry)) {
712 | throw new \InvalidArgumentException('shouldRetry must be an int or callable');
713 | }
714 | $numRetries = 0;
715 | do {
716 | try {
717 | return $fn($numRetries);
718 | } catch (\Throwable $t) {
719 | }
720 | $numRetries += 1;
721 | } while ($shouldRetry($numRetries, $t));
722 | throw $t;
723 | };
724 | }
725 | function stack(callable $last = null, callable $resolve = null)
726 | {
727 | return function (array $funcs) use($last, $resolve) {
728 | return function (...$args) use($funcs, $resolve, $last) {
729 | return \Krak\Fun\reduce(function ($acc, $func) use($resolve) {
730 | return function (...$args) use($acc, $func, $resolve) {
731 | $args[] = $acc;
732 | $func = $resolve ? $resolve($func) : $func;
733 | return $func(...$args);
734 | };
735 | }, $funcs, $last ?: function () {
736 | throw new \LogicException('No stack handler was able to capture this request');
737 | });
738 | };
739 | };
740 | }
741 | function each(callable $handle)
742 | {
743 | return function (iterable $iter) use($handle) {
744 | foreach ($iter as $v) {
745 | $handle($v);
746 | }
747 | };
748 | }
749 | function onEach(callable $handle)
750 | {
751 | return function (iterable $iter) use($handle) {
752 | foreach ($iter as $v) {
753 | $handle($v);
754 | }
755 | };
756 | }
757 | const method = 'Krak\\Fun\\method';
758 | const prop = 'Krak\\Fun\\prop';
759 | const index = 'Krak\\Fun\\index';
760 | const setProp = 'Krak\\Fun\\setProp';
761 | const setIndex = 'Krak\\Fun\\setIndex';
762 | const setIndexIn = 'Krak\\Fun\\setIndexIn';
763 | const propIn = 'Krak\\Fun\\propIn';
764 | const indexIn = 'Krak\\Fun\\indexIn';
765 | const hasIndexIn = 'Krak\\Fun\\hasIndexIn';
766 | const updateIndexIn = 'Krak\\Fun\\updateIndexIn';
767 | const assign = 'Krak\\Fun\\assign';
768 | const join = 'Krak\\Fun\\join';
769 | const construct = 'Krak\\Fun\\construct';
770 | const spread = 'Krak\\Fun\\spread';
771 | const dd = 'Krak\\Fun\\dd';
772 | const takeWhile = 'Krak\\Fun\\takeWhile';
773 | const dropWhile = 'Krak\\Fun\\dropWhile';
774 | const take = 'Krak\\Fun\\take';
775 | const drop = 'Krak\\Fun\\drop';
776 | const slice = 'Krak\\Fun\\slice';
777 | const head = 'Krak\\Fun\\head';
778 | const chunk = 'Krak\\Fun\\chunk';
779 | const chunkBy = 'Krak\\Fun\\chunkBy';
780 | const groupBy = 'Krak\\Fun\\groupBy';
781 | const range = 'Krak\\Fun\\range';
782 | const op = 'Krak\\Fun\\op';
783 | const andf = 'Krak\\Fun\\andf';
784 | const orf = 'Krak\\Fun\\orf';
785 | const chain = 'Krak\\Fun\\chain';
786 | const zip = 'Krak\\Fun\\zip';
787 | const flatMap = 'Krak\\Fun\\flatMap';
788 | const flatten = 'Krak\\Fun\\flatten';
789 | const product = 'Krak\\Fun\\product';
790 | const when = 'Krak\\Fun\\when';
791 | const toPairs = 'Krak\\Fun\\toPairs';
792 | const fromPairs = 'Krak\\Fun\\fromPairs';
793 | const pick = 'Krak\\Fun\\pick';
794 | const pickBy = 'Krak\\Fun\\pickBy';
795 | const within = 'Krak\\Fun\\within';
796 | const without = 'Krak\\Fun\\without';
797 | const compact = 'Krak\\Fun\\compact';
798 | const arrayCompact = 'Krak\\Fun\\arrayCompact';
799 | const pad = 'Krak\\Fun\\pad';
800 | const inArray = 'Krak\\Fun\\inArray';
801 | const arrayMap = 'Krak\\Fun\\arrayMap';
802 | const arrayFilter = 'Krak\\Fun\\arrayFilter';
803 | const arrayWrap = 'Krak\\Fun\\arrayWrap';
804 | const all = 'Krak\\Fun\\all';
805 | const any = 'Krak\\Fun\\any';
806 | const search = 'Krak\\Fun\\search';
807 | const indexOf = 'Krak\\Fun\\indexOf';
808 | const trans = 'Krak\\Fun\\trans';
809 | const not = 'Krak\\Fun\\not';
810 | const isInstance = 'Krak\\Fun\\isInstance';
811 | const isNull = 'Krak\\Fun\\isNull';
812 | const nullable = 'Krak\\Fun\\nullable';
813 | const partition = 'Krak\\Fun\\partition';
814 | const map = 'Krak\\Fun\\map';
815 | const mapKeys = 'Krak\\Fun\\mapKeys';
816 | const mapKeyValue = 'Krak\\Fun\\mapKeyValue';
817 | const mapOn = 'Krak\\Fun\\mapOn';
818 | const mapAccum = 'Krak\\Fun\\mapAccum';
819 | const withState = 'Krak\\Fun\\withState';
820 | const arrayReindex = 'Krak\\Fun\\arrayReindex';
821 | const reindex = 'Krak\\Fun\\reindex';
822 | const reduce = 'Krak\\Fun\\reduce';
823 | const reduceKeyValue = 'Krak\\Fun\\reduceKeyValue';
824 | const filter = 'Krak\\Fun\\filter';
825 | const filterKeys = 'Krak\\Fun\\filterKeys';
826 | const values = 'Krak\\Fun\\values';
827 | const keys = 'Krak\\Fun\\keys';
828 | const flip = 'Krak\\Fun\\flip';
829 | const curry = 'Krak\\Fun\\curry';
830 | const placeholder = 'Krak\\Fun\\placeholder';
831 | const _ = 'Krak\\Fun\\_';
832 | const partial = 'Krak\\Fun\\partial';
833 | const autoCurry = 'Krak\\Fun\\autoCurry';
834 | const toArray = 'Krak\\Fun\\toArray';
835 | const toArrayWithKeys = 'Krak\\Fun\\toArrayWithKeys';
836 | const id = 'Krak\\Fun\\id';
837 | const tap = 'Krak\\Fun\\tap';
838 | const throwIf = 'Krak\\Fun\\throwIf';
839 | const differenceWith = 'Krak\\Fun\\differenceWith';
840 | const sortFromArray = 'Krak\\Fun\\sortFromArray';
841 | const retry = 'Krak\\Fun\\retry';
842 | const pipe = 'Krak\\Fun\\pipe';
843 | const compose = 'Krak\\Fun\\compose';
844 | const stack = 'Krak\\Fun\\stack';
845 | const each = 'Krak\\Fun\\each';
846 | const onEach = 'Krak\\Fun\\onEach';
847 | const iter = 'Krak\\Fun\\iter';
--------------------------------------------------------------------------------
/src/consts.generated.php:
--------------------------------------------------------------------------------
1 | {$name}(...$optionalArgs);
10 | };
11 | }
12 | function prop(string $key, $else = null)
13 | {
14 | return function ($data) use($key, $else) {
15 | return \property_exists($data, $key) ? $data->{$key} : $else;
16 | };
17 | }
18 | function index($key, $else = null)
19 | {
20 | return function ($data) use($key, $else) {
21 | if (\Krak\Fun\isInstance(\ArrayAccess::class, $data)) {
22 | $exists = $data->offsetExists($key);
23 | } else {
24 | $exists = \array_key_exists($key, $data);
25 | }
26 | return $exists ? $data[$key] : $else;
27 | };
28 | }
29 | function setProp(string $key)
30 | {
31 | return function ($value) use($key) {
32 | return function ($data) use($value, $key) {
33 | $data->{$key} = $value;
34 | return $data;
35 | };
36 | };
37 | }
38 | function setIndex($key)
39 | {
40 | return function ($value) use($key) {
41 | return function (array $data) use($value, $key) {
42 | $data[$key] = $value;
43 | return $data;
44 | };
45 | };
46 | }
47 | function setIndexIn(array $keys)
48 | {
49 | return function ($value) use($keys) {
50 | return function (array $data) use($value, $keys) {
51 | return \Krak\Fun\updateIndexIn($keys, function () use($value) {
52 | return $value;
53 | }, $data);
54 | };
55 | };
56 | }
57 | function propIn(array $props, $else = null)
58 | {
59 | return function ($obj) use($props, $else) {
60 | foreach ($props as $prop) {
61 | if (!\is_object($obj) || !\property_exists($obj, $prop)) {
62 | return $else;
63 | }
64 | $obj = $obj->{$prop};
65 | }
66 | return $obj;
67 | };
68 | }
69 | function indexIn(array $keys, $else = null)
70 | {
71 | return function (array $data) use($keys, $else) {
72 | foreach ($keys as $part) {
73 | if (!\is_array($data) || !\array_key_exists($part, $data)) {
74 | return $else;
75 | }
76 | $data = $data[$part];
77 | }
78 | return $data;
79 | };
80 | }
81 | function hasIndexIn(array $keys)
82 | {
83 | return function (array $data) use($keys) {
84 | foreach ($keys as $key) {
85 | if (!\is_array($data) || !\array_key_exists($key, $data)) {
86 | return false;
87 | }
88 | $data = $data[$key];
89 | }
90 | return true;
91 | };
92 | }
93 | function updateIndexIn(array $keys)
94 | {
95 | return function (callable $update) use($keys) {
96 | return function (array $data) use($update, $keys) {
97 | $curData =& $data;
98 | foreach (\array_slice($keys, 0, -1) as $key) {
99 | if (!\array_key_exists($key, $curData)) {
100 | throw new \RuntimeException('Could not updateIn because the keys ' . \implode(' -> ', $keys) . ' could not be found.');
101 | }
102 | $curData =& $curData[$key];
103 | }
104 | $lastKey = $keys[count($keys) - 1];
105 | $curData[$lastKey] = $update($curData[$lastKey] ?? null);
106 | return $data;
107 | };
108 | };
109 | }
110 | function assign($obj)
111 | {
112 | return function (iterable $iter) use($obj) {
113 | foreach ($iter as $key => $value) {
114 | $obj->{$key} = $value;
115 | }
116 | return $obj;
117 | };
118 | }
119 | function join(string $sep)
120 | {
121 | return function (iterable $iter) use($sep) {
122 | return \Krak\Fun\reduce(function ($acc, $v) use($sep) {
123 | return $acc ? $acc . $sep . $v : $v;
124 | }, $iter, "");
125 | };
126 | }
127 | function construct($className)
128 | {
129 | return function (...$args) use($className) {
130 | return new $className(...$args);
131 | };
132 | }
133 | function spread(callable $fn)
134 | {
135 | return function (array $data) use($fn) {
136 | return $fn(...$data);
137 | };
138 | }
139 | function dd(callable $dump = null, callable $die = null)
140 | {
141 | return function ($value) use($dump, $die) {
142 | $dump = $dump ?: (function_exists('dump') ? 'dump' : 'var_dump');
143 | $dump($value);
144 | ($die ?? function () {
145 | die;
146 | })();
147 | };
148 | }
149 | function takeWhile(callable $predicate)
150 | {
151 | return function (iterable $iter) use($predicate) {
152 | foreach ($iter as $k => $v) {
153 | if ($predicate($v)) {
154 | (yield $k => $v);
155 | } else {
156 | return;
157 | }
158 | }
159 | };
160 | }
161 | function dropWhile(callable $predicate)
162 | {
163 | return function (iterable $iter) use($predicate) {
164 | $stillDropping = true;
165 | foreach ($iter as $k => $v) {
166 | if ($stillDropping && $predicate($v)) {
167 | continue;
168 | } else {
169 | if ($stillDropping) {
170 | $stillDropping = false;
171 | }
172 | }
173 | (yield $k => $v);
174 | }
175 | };
176 | }
177 | function take(int $num)
178 | {
179 | return function (iterable $iter) use($num) {
180 | return \Krak\Fun\slice(0, $iter, $num);
181 | };
182 | }
183 | function drop(int $num)
184 | {
185 | return function (iterable $iter) use($num) {
186 | return \Krak\Fun\slice($num, $iter);
187 | };
188 | }
189 | function slice(int $start, $length = INF)
190 | {
191 | return function (iterable $iter) use($start, $length) {
192 | assert($start >= 0);
193 | $i = 0;
194 | $end = $start + $length - 1;
195 | foreach ($iter as $k => $v) {
196 | if ($start <= $i && $i <= $end) {
197 | (yield $k => $v);
198 | }
199 | $i += 1;
200 | if ($i > $end) {
201 | return;
202 | }
203 | }
204 | };
205 | }
206 | function chunk(int $size)
207 | {
208 | return function (iterable $iter) use($size) {
209 | assert($size > 0);
210 | $chunk = [];
211 | foreach ($iter as $v) {
212 | $chunk[] = $v;
213 | if (\count($chunk) == $size) {
214 | (yield $chunk);
215 | $chunk = [];
216 | }
217 | }
218 | if ($chunk) {
219 | (yield $chunk);
220 | }
221 | };
222 | }
223 | function chunkBy(callable $fn, ?int $maxSize = null)
224 | {
225 | return function (iterable $iter) use($fn, $maxSize) {
226 | assert($maxSize === null || $maxSize > 0);
227 | $group = [];
228 | $groupKey = null;
229 | foreach ($iter as $v) {
230 | $curGroupKey = $fn($v);
231 | $shouldYieldGroup = $groupKey !== null && $groupKey !== $curGroupKey || $maxSize !== null && \count($group) >= $maxSize;
232 | if ($shouldYieldGroup) {
233 | (yield $group);
234 | $group = [];
235 | }
236 | $group[] = $v;
237 | $groupKey = $curGroupKey;
238 | }
239 | if (\count($group)) {
240 | (yield $group);
241 | }
242 | };
243 | }
244 | function groupBy(callable $fn, ?int $maxSize = null)
245 | {
246 | return function (iterable $iter) use($fn, $maxSize) {
247 | return \Krak\Fun\chunkBy($fn, $iter, $maxSize);
248 | };
249 | }
250 | function range($start, $step = null)
251 | {
252 | return function ($end) use($start, $step) {
253 | if ($start == $end) {
254 | (yield $start);
255 | } else {
256 | if ($start < $end) {
257 | $step = $step ?: 1;
258 | if ($step <= 0) {
259 | throw new \InvalidArgumentException('Step must be greater than 0.');
260 | }
261 | for ($i = $start; $i <= $end; $i += $step) {
262 | (yield $i);
263 | }
264 | } else {
265 | $step = $step ?: -1;
266 | if ($step >= 0) {
267 | throw new \InvalidArgumentException('Step must be less than 0.');
268 | }
269 | for ($i = $start; $i >= $end; $i += $step) {
270 | (yield $i);
271 | }
272 | }
273 | }
274 | };
275 | }
276 | function op(string $op)
277 | {
278 | return function ($b) use($op) {
279 | return function ($a) use($b, $op) {
280 | switch ($op) {
281 | case '==':
282 | case 'eq':
283 | return $a == $b;
284 | case '!=':
285 | case 'neq':
286 | return $a != $b;
287 | case '===':
288 | return $a === $b;
289 | case '!==':
290 | return $a !== $b;
291 | case '>':
292 | case 'gt':
293 | return $a > $b;
294 | case '>=':
295 | case 'gte':
296 | return $a >= $b;
297 | case '<':
298 | case 'lt':
299 | return $a < $b;
300 | case '<=':
301 | case 'lte':
302 | return $a <= $b;
303 | case '+':
304 | return $a + $b;
305 | case '-':
306 | return $a - $b;
307 | case '*':
308 | return $a * $b;
309 | case '**':
310 | return $a ** $b;
311 | case '/':
312 | return $a / $b;
313 | case '%':
314 | return $a % $b;
315 | case '.':
316 | return $a . $b;
317 | default:
318 | throw new \LogicException('Invalid operator ' . $op);
319 | }
320 | };
321 | };
322 | }
323 | function flatMap(callable $map)
324 | {
325 | return function (iterable $iter) use($map) {
326 | foreach ($iter as $k => $v) {
327 | foreach ($map($v) as $k => $v) {
328 | (yield $k => $v);
329 | }
330 | }
331 | };
332 | }
333 | function flatten($levels = INF)
334 | {
335 | return function (iterable $iter) use($levels) {
336 | if ($levels == 0) {
337 | yield from $iter;
338 | } else {
339 | if ($levels == 1) {
340 | foreach ($iter as $k => $v) {
341 | if (\is_iterable($v)) {
342 | foreach ($v as $k1 => $v1) {
343 | (yield $k1 => $v1);
344 | }
345 | } else {
346 | (yield $k => $v);
347 | }
348 | }
349 | } else {
350 | foreach ($iter as $k => $v) {
351 | if (\is_iterable($v)) {
352 | foreach (flatten($v, $levels - 1) as $k1 => $v1) {
353 | (yield $k1 => $v1);
354 | }
355 | } else {
356 | (yield $k => $v);
357 | }
358 | }
359 | }
360 | }
361 | };
362 | }
363 | function when(callable $if)
364 | {
365 | return function (callable $then) use($if) {
366 | return function ($value) use($then, $if) {
367 | return $if($value) ? $then($value) : $value;
368 | };
369 | };
370 | }
371 | function pick(iterable $fields)
372 | {
373 | return function (array $data) use($fields) {
374 | $pickedData = [];
375 | foreach ($fields as $field) {
376 | $pickedData[$field] = $data[$field] ?? null;
377 | }
378 | return $pickedData;
379 | };
380 | }
381 | function pickBy(callable $pick)
382 | {
383 | return function (array $data) use($pick) {
384 | $pickedData = [];
385 | foreach ($data as $key => $value) {
386 | if ($pick([$key, $value])) {
387 | $pickedData[$key] = $value;
388 | }
389 | }
390 | return $pickedData;
391 | };
392 | }
393 | function within(array $fields)
394 | {
395 | return function (iterable $iter) use($fields) {
396 | return \Krak\Fun\filterKeys(\Krak\Fun\Curried\inArray($fields), $iter);
397 | };
398 | }
399 | function without(array $fields)
400 | {
401 | return function (iterable $iter) use($fields) {
402 | return \Krak\Fun\filterKeys(\Krak\Fun\Curried\not(\Krak\Fun\Curried\inArray($fields)), $iter);
403 | };
404 | }
405 | function pad(int $size, $padValue = null)
406 | {
407 | return function (iterable $iter) use($size, $padValue) {
408 | $i = 0;
409 | foreach ($iter as $key => $value) {
410 | (yield $value);
411 | $i += 1;
412 | }
413 | if ($i >= $size) {
414 | return;
415 | }
416 | foreach (\Krak\Fun\range($i, $size - 1) as $index) {
417 | (yield $padValue);
418 | }
419 | };
420 | }
421 | function inArray(array $set)
422 | {
423 | return function ($item) use($set) {
424 | return \in_array($item, $set);
425 | };
426 | }
427 | function arrayMap(callable $fn)
428 | {
429 | return function (iterable $data) use($fn) {
430 | return \array_map($fn, \is_array($data) ? $data : \Krak\Fun\toArray($data));
431 | };
432 | }
433 | function arrayFilter(callable $fn)
434 | {
435 | return function (iterable $data) use($fn) {
436 | return \array_filter(\is_array($data) ? $data : \Krak\Fun\toArray($data), $fn);
437 | };
438 | }
439 | function all(callable $predicate)
440 | {
441 | return function (iterable $iter) use($predicate) {
442 | foreach ($iter as $key => $value) {
443 | if (!$predicate($value)) {
444 | return false;
445 | }
446 | }
447 | return true;
448 | };
449 | }
450 | function any(callable $predicate)
451 | {
452 | return function (iterable $iter) use($predicate) {
453 | foreach ($iter as $key => $value) {
454 | if ($predicate($value)) {
455 | return true;
456 | }
457 | }
458 | return false;
459 | };
460 | }
461 | function search(callable $predicate)
462 | {
463 | return function (iterable $iter) use($predicate) {
464 | foreach ($iter as $value) {
465 | if ($predicate($value)) {
466 | return $value;
467 | }
468 | }
469 | };
470 | }
471 | function indexOf(callable $predicate)
472 | {
473 | return function (iterable $iter) use($predicate) {
474 | foreach ($iter as $key => $value) {
475 | if ($predicate($value)) {
476 | return $key;
477 | }
478 | }
479 | };
480 | }
481 | function trans(callable $trans)
482 | {
483 | return function (callable $fn) use($trans) {
484 | return function ($data) use($fn, $trans) {
485 | return $fn($trans($data));
486 | };
487 | };
488 | }
489 | function not(callable $fn)
490 | {
491 | return function (...$args) use($fn) {
492 | return !$fn(...$args);
493 | };
494 | }
495 | function isInstance($class)
496 | {
497 | return function ($item) use($class) {
498 | return $item instanceof $class;
499 | };
500 | }
501 | function nullable(callable $fn)
502 | {
503 | return function ($value) use($fn) {
504 | return $value === null ? $value : $fn($value);
505 | };
506 | }
507 | function partition(callable $partition, int $numParts = 2)
508 | {
509 | return function (iterable $iter) use($partition, $numParts) {
510 | $parts = \array_fill(0, $numParts, []);
511 | foreach ($iter as $val) {
512 | $index = (int) $partition($val);
513 | $parts[$index][] = $val;
514 | }
515 | return $parts;
516 | };
517 | }
518 | function map(callable $predicate)
519 | {
520 | return function (iterable $iter) use($predicate) {
521 | foreach ($iter as $key => $value) {
522 | (yield $key => $predicate($value));
523 | }
524 | };
525 | }
526 | function mapKeys(callable $predicate)
527 | {
528 | return function (iterable $iter) use($predicate) {
529 | foreach ($iter as $key => $value) {
530 | (yield $predicate($key) => $value);
531 | }
532 | };
533 | }
534 | function mapKeyValue(callable $fn)
535 | {
536 | return function (iterable $iter) use($fn) {
537 | foreach ($iter as $key => $value) {
538 | [$key, $value] = $fn([$key, $value]);
539 | (yield $key => $value);
540 | }
541 | };
542 | }
543 | function mapOn(array $maps)
544 | {
545 | return function (iterable $iter) use($maps) {
546 | foreach ($iter as $key => $value) {
547 | if (isset($maps[$key])) {
548 | (yield $key => $maps[$key]($value));
549 | } else {
550 | (yield $key => $value);
551 | }
552 | }
553 | };
554 | }
555 | function mapAccum(callable $fn, $acc = null)
556 | {
557 | return function (iterable $iter) use($fn, $acc) {
558 | $data = [];
559 | foreach ($iter as $key => $value) {
560 | [$acc, $value] = $fn($acc, $value);
561 | $data[] = $value;
562 | }
563 | return [$acc, $data];
564 | };
565 | }
566 | function withState($initialState = null)
567 | {
568 | return function (callable $fn) use($initialState) {
569 | $state = $initialState;
570 | return function (...$args) use($fn, &$state) {
571 | [$state, $res] = $fn($state, ...$args);
572 | return $res;
573 | };
574 | };
575 | }
576 | function arrayReindex(callable $fn)
577 | {
578 | return function (iterable $iter) use($fn) {
579 | $res = [];
580 | foreach ($iter as $key => $value) {
581 | $res[$fn($value)] = $value;
582 | }
583 | return $res;
584 | };
585 | }
586 | function reindex(callable $fn)
587 | {
588 | return function (iterable $iter) use($fn) {
589 | foreach ($iter as $key => $value) {
590 | (yield $fn($value) => $value);
591 | }
592 | };
593 | }
594 | function reduce(callable $reduce, $acc = null)
595 | {
596 | return function (iterable $iter) use($reduce, $acc) {
597 | foreach ($iter as $key => $value) {
598 | $acc = $reduce($acc, $value);
599 | }
600 | return $acc;
601 | };
602 | }
603 | function reduceKeyValue(callable $reduce, $acc = null)
604 | {
605 | return function (iterable $iter) use($reduce, $acc) {
606 | foreach ($iter as $key => $value) {
607 | $acc = $reduce($acc, [$key, $value]);
608 | }
609 | return $acc;
610 | };
611 | }
612 | function filter(callable $predicate)
613 | {
614 | return function (iterable $iter) use($predicate) {
615 | foreach ($iter as $key => $value) {
616 | if ($predicate($value)) {
617 | (yield $key => $value);
618 | }
619 | }
620 | };
621 | }
622 | function filterKeys(callable $predicate)
623 | {
624 | return function (iterable $iter) use($predicate) {
625 | foreach ($iter as $key => $value) {
626 | if ($predicate($key)) {
627 | (yield $key => $value);
628 | }
629 | }
630 | };
631 | }
632 | function partial(callable $fn)
633 | {
634 | return function (...$appliedArgs) use($fn) {
635 | return function (...$args) use($fn, $appliedArgs) {
636 | list($appliedArgs, $args) = \array_reduce($appliedArgs, function ($acc, $arg) {
637 | list($appliedArgs, $args) = $acc;
638 | if ($arg === \Krak\Fun\placeholder()) {
639 | $arg = array_shift($args);
640 | }
641 | $appliedArgs[] = $arg;
642 | return [$appliedArgs, $args];
643 | }, [[], $args]);
644 | return $fn(...$appliedArgs, ...$args);
645 | };
646 | };
647 | }
648 | function tap(callable $tap)
649 | {
650 | return function ($value) use($tap) {
651 | $tap($value);
652 | return $value;
653 | };
654 | }
655 | function throwIf(callable $throw)
656 | {
657 | return function (callable $if) use($throw) {
658 | return function ($value) use($if, $throw) {
659 | if ($if($value)) {
660 | throw $throw($value);
661 | }
662 | return $value;
663 | };
664 | };
665 | }
666 | function differenceWith(callable $cmp)
667 | {
668 | return function (iterable $a) use($cmp) {
669 | return function (iterable $b) use($a, $cmp) {
670 | return \Krak\Fun\filter(function ($aItem) use($cmp, $b) {
671 | return \Krak\Fun\indexOf(\Krak\Fun\partial($cmp, $aItem), $b) === null;
672 | }, $a);
673 | };
674 | };
675 | }
676 | function sortFromArray(callable $fn)
677 | {
678 | return function (array $orderedElements) use($fn) {
679 | return function (iterable $iter) use($orderedElements, $fn) {
680 | $data = [];
681 | $flippedElements = \array_flip($orderedElements);
682 | foreach ($iter as $value) {
683 | $key = $fn($value);
684 | if (!\array_key_exists($key, $flippedElements)) {
685 | throw new \LogicException('Cannot sort element key ' . $key . ' because it does not exist in the ordered elements.');
686 | }
687 | $data[$flippedElements[$key]] = $value;
688 | }
689 | ksort($data);
690 | return $data;
691 | };
692 | };
693 | }
694 | function retry($shouldRetry = null)
695 | {
696 | return function (callable $fn) use($shouldRetry) {
697 | if (\is_null($shouldRetry)) {
698 | $shouldRetry = function ($numRetries, \Throwable $t = null) {
699 | return true;
700 | };
701 | }
702 | if (\is_int($shouldRetry)) {
703 | $maxTries = $shouldRetry;
704 | if ($maxTries < 0) {
705 | throw new \LogicException("maxTries must be greater than or equal to 0");
706 | }
707 | $shouldRetry = function ($numRetries, \Throwable $t = null) use($maxTries) {
708 | return $numRetries <= $maxTries;
709 | };
710 | }
711 | if (!\is_callable($shouldRetry)) {
712 | throw new \InvalidArgumentException('shouldRetry must be an int or callable');
713 | }
714 | $numRetries = 0;
715 | do {
716 | try {
717 | return $fn($numRetries);
718 | } catch (\Throwable $t) {
719 | }
720 | $numRetries += 1;
721 | } while ($shouldRetry($numRetries, $t));
722 | throw $t;
723 | };
724 | }
725 | function stack(callable $last = null, callable $resolve = null)
726 | {
727 | return function (array $funcs) use($last, $resolve) {
728 | return function (...$args) use($funcs, $resolve, $last) {
729 | return \Krak\Fun\reduce(function ($acc, $func) use($resolve) {
730 | return function (...$args) use($acc, $func, $resolve) {
731 | $args[] = $acc;
732 | $func = $resolve ? $resolve($func) : $func;
733 | return $func(...$args);
734 | };
735 | }, $funcs, $last ?: function () {
736 | throw new \LogicException('No stack handler was able to capture this request');
737 | });
738 | };
739 | };
740 | }
741 | function each(callable $handle)
742 | {
743 | return function (iterable $iter) use($handle) {
744 | foreach ($iter as $v) {
745 | $handle($v);
746 | }
747 | };
748 | }
749 | function onEach(callable $handle)
750 | {
751 | return function (iterable $iter) use($handle) {
752 | foreach ($iter as $v) {
753 | $handle($v);
754 | }
755 | };
756 | }
--------------------------------------------------------------------------------
/src/f.generated.php:
--------------------------------------------------------------------------------
1 | {$name}(...$optionalArgs);
10 | }
11 | function prop(string $key, $data, $else = null)
12 | {
13 | return \property_exists($data, $key) ? $data->{$key} : $else;
14 | }
15 | function index($key, $data, $else = null)
16 | {
17 | if (\Krak\Fun\isInstance(\ArrayAccess::class, $data)) {
18 | $exists = $data->offsetExists($key);
19 | } else {
20 | $exists = \array_key_exists($key, $data);
21 | }
22 | return $exists ? $data[$key] : $else;
23 | }
24 | function setProp(string $key, $value, $data)
25 | {
26 | $data->{$key} = $value;
27 | return $data;
28 | }
29 | function setIndex($key, $value, array $data)
30 | {
31 | $data[$key] = $value;
32 | return $data;
33 | }
34 | function setIndexIn(array $keys, $value, array $data)
35 | {
36 | return \Krak\Fun\updateIndexIn($keys, function () use($value) {
37 | return $value;
38 | }, $data);
39 | }
40 | function propIn(array $props, $obj, $else = null)
41 | {
42 | foreach ($props as $prop) {
43 | if (!\is_object($obj) || !\property_exists($obj, $prop)) {
44 | return $else;
45 | }
46 | $obj = $obj->{$prop};
47 | }
48 | return $obj;
49 | }
50 | function indexIn(array $keys, array $data, $else = null)
51 | {
52 | foreach ($keys as $part) {
53 | if (!\is_array($data) || !\array_key_exists($part, $data)) {
54 | return $else;
55 | }
56 | $data = $data[$part];
57 | }
58 | return $data;
59 | }
60 | function hasIndexIn(array $keys, array $data) : bool
61 | {
62 | foreach ($keys as $key) {
63 | if (!\is_array($data) || !\array_key_exists($key, $data)) {
64 | return false;
65 | }
66 | $data = $data[$key];
67 | }
68 | return true;
69 | }
70 | function updateIndexIn(array $keys, callable $update, array $data) : array
71 | {
72 | $curData =& $data;
73 | foreach (\array_slice($keys, 0, -1) as $key) {
74 | if (!\array_key_exists($key, $curData)) {
75 | throw new \RuntimeException('Could not updateIn because the keys ' . \implode(' -> ', $keys) . ' could not be found.');
76 | }
77 | $curData =& $curData[$key];
78 | }
79 | $lastKey = $keys[count($keys) - 1];
80 | $curData[$lastKey] = $update($curData[$lastKey] ?? null);
81 | return $data;
82 | }
83 | // UTILITY
84 | function assign($obj, iterable $iter)
85 | {
86 | foreach ($iter as $key => $value) {
87 | $obj->{$key} = $value;
88 | }
89 | return $obj;
90 | }
91 | function join(string $sep, iterable $iter)
92 | {
93 | return \Krak\Fun\reduce(function ($acc, $v) use($sep) {
94 | return $acc ? $acc . $sep . $v : $v;
95 | }, $iter, "");
96 | }
97 | function construct($className, ...$args)
98 | {
99 | return new $className(...$args);
100 | }
101 | function spread(callable $fn, array $data)
102 | {
103 | return $fn(...$data);
104 | }
105 | function dd($value, callable $dump = null, callable $die = null)
106 | {
107 | $dump = $dump ?: (function_exists('dump') ? 'dump' : 'var_dump');
108 | $dump($value);
109 | ($die ?? function () {
110 | die;
111 | })();
112 | }
113 | // SLICING
114 | function takeWhile(callable $predicate, iterable $iter) : iterable
115 | {
116 | foreach ($iter as $k => $v) {
117 | if ($predicate($v)) {
118 | (yield $k => $v);
119 | } else {
120 | return;
121 | }
122 | }
123 | }
124 | function dropWhile(callable $predicate, iterable $iter) : iterable
125 | {
126 | $stillDropping = true;
127 | foreach ($iter as $k => $v) {
128 | if ($stillDropping && $predicate($v)) {
129 | continue;
130 | } else {
131 | if ($stillDropping) {
132 | $stillDropping = false;
133 | }
134 | }
135 | (yield $k => $v);
136 | }
137 | }
138 | function take(int $num, iterable $iter) : iterable
139 | {
140 | return \Krak\Fun\slice(0, $iter, $num);
141 | }
142 | function drop(int $num, iterable $iter) : iterable
143 | {
144 | return \Krak\Fun\slice($num, $iter);
145 | }
146 | function slice(int $start, iterable $iter, $length = INF) : iterable
147 | {
148 | assert($start >= 0);
149 | $i = 0;
150 | $end = $start + $length - 1;
151 | foreach ($iter as $k => $v) {
152 | if ($start <= $i && $i <= $end) {
153 | (yield $k => $v);
154 | }
155 | $i += 1;
156 | if ($i > $end) {
157 | return;
158 | }
159 | }
160 | }
161 | function head(iterable $iter)
162 | {
163 | foreach ($iter as $v) {
164 | return $v;
165 | }
166 | }
167 | function chunk(int $size, iterable $iter) : iterable
168 | {
169 | assert($size > 0);
170 | $chunk = [];
171 | foreach ($iter as $v) {
172 | $chunk[] = $v;
173 | if (\count($chunk) == $size) {
174 | (yield $chunk);
175 | $chunk = [];
176 | }
177 | }
178 | if ($chunk) {
179 | (yield $chunk);
180 | }
181 | }
182 | function chunkBy(callable $fn, iterable $iter, ?int $maxSize = null) : iterable
183 | {
184 | assert($maxSize === null || $maxSize > 0);
185 | $group = [];
186 | $groupKey = null;
187 | foreach ($iter as $v) {
188 | $curGroupKey = $fn($v);
189 | $shouldYieldGroup = $groupKey !== null && $groupKey !== $curGroupKey || $maxSize !== null && \count($group) >= $maxSize;
190 | if ($shouldYieldGroup) {
191 | (yield $group);
192 | $group = [];
193 | }
194 | $group[] = $v;
195 | $groupKey = $curGroupKey;
196 | }
197 | if (\count($group)) {
198 | (yield $group);
199 | }
200 | }
201 | function groupBy(callable $fn, iterable $iter, ?int $maxSize = null) : iterable
202 | {
203 | return \Krak\Fun\chunkBy($fn, $iter, $maxSize);
204 | }
205 | // GENERATORS
206 | function range($start, $end, $step = null)
207 | {
208 | if ($start == $end) {
209 | (yield $start);
210 | } else {
211 | if ($start < $end) {
212 | $step = $step ?: 1;
213 | if ($step <= 0) {
214 | throw new \InvalidArgumentException('Step must be greater than 0.');
215 | }
216 | for ($i = $start; $i <= $end; $i += $step) {
217 | (yield $i);
218 | }
219 | } else {
220 | $step = $step ?: -1;
221 | if ($step >= 0) {
222 | throw new \InvalidArgumentException('Step must be less than 0.');
223 | }
224 | for ($i = $start; $i >= $end; $i += $step) {
225 | (yield $i);
226 | }
227 | }
228 | }
229 | }
230 | // OPERATORS
231 | function op(string $op, $b, $a)
232 | {
233 | switch ($op) {
234 | case '==':
235 | case 'eq':
236 | return $a == $b;
237 | case '!=':
238 | case 'neq':
239 | return $a != $b;
240 | case '===':
241 | return $a === $b;
242 | case '!==':
243 | return $a !== $b;
244 | case '>':
245 | case 'gt':
246 | return $a > $b;
247 | case '>=':
248 | case 'gte':
249 | return $a >= $b;
250 | case '<':
251 | case 'lt':
252 | return $a < $b;
253 | case '<=':
254 | case 'lte':
255 | return $a <= $b;
256 | case '+':
257 | return $a + $b;
258 | case '-':
259 | return $a - $b;
260 | case '*':
261 | return $a * $b;
262 | case '**':
263 | return $a ** $b;
264 | case '/':
265 | return $a / $b;
266 | case '%':
267 | return $a % $b;
268 | case '.':
269 | return $a . $b;
270 | default:
271 | throw new \LogicException('Invalid operator ' . $op);
272 | }
273 | }
274 | function andf(callable ...$fns)
275 | {
276 | return function ($el) use($fns) {
277 | foreach ($fns as $fn) {
278 | if (!$fn($el)) {
279 | return false;
280 | }
281 | }
282 | return true;
283 | };
284 | }
285 | function orf(callable ...$fns)
286 | {
287 | return function ($el) use($fns) {
288 | foreach ($fns as $fn) {
289 | if ($fn($el)) {
290 | return true;
291 | }
292 | }
293 | return false;
294 | };
295 | }
296 | function chain(iterable ...$iters)
297 | {
298 | foreach ($iters as $iter) {
299 | foreach ($iter as $k => $v) {
300 | (yield $k => $v);
301 | }
302 | }
303 | }
304 | function zip(iterable ...$iters) : \Iterator
305 | {
306 | if (count($iters) == 0) {
307 | return;
308 | }
309 | $iters = \array_map(iter::class, $iters);
310 | while (true) {
311 | $tup = [];
312 | foreach ($iters as $iter) {
313 | if (!$iter->valid()) {
314 | return;
315 | }
316 | $tup[] = $iter->current();
317 | $iter->next();
318 | }
319 | (yield $tup);
320 | }
321 | }
322 | function flatMap(callable $map, iterable $iter) : iterable
323 | {
324 | foreach ($iter as $k => $v) {
325 | foreach ($map($v) as $k => $v) {
326 | (yield $k => $v);
327 | }
328 | }
329 | }
330 | function flatten(iterable $iter, $levels = INF) : iterable
331 | {
332 | if ($levels == 0) {
333 | yield from $iter;
334 | } else {
335 | if ($levels == 1) {
336 | foreach ($iter as $k => $v) {
337 | if (\is_iterable($v)) {
338 | foreach ($v as $k1 => $v1) {
339 | (yield $k1 => $v1);
340 | }
341 | } else {
342 | (yield $k => $v);
343 | }
344 | }
345 | } else {
346 | foreach ($iter as $k => $v) {
347 | if (\is_iterable($v)) {
348 | foreach (flatten($v, $levels - 1) as $k1 => $v1) {
349 | (yield $k1 => $v1);
350 | }
351 | } else {
352 | (yield $k => $v);
353 | }
354 | }
355 | }
356 | }
357 | }
358 | function product(iterable ...$iters) : iterable
359 | {
360 | if (count($iters) === 0) {
361 | yield from [];
362 | return;
363 | }
364 | if (count($iters) === 1) {
365 | yield from \Krak\Fun\map(function ($v) {
366 | return [$v];
367 | }, $iters[0]);
368 | return;
369 | }
370 | foreach ($iters[0] as $value) {
371 | yield from \Krak\Fun\map(function (array $tup) use($value) {
372 | array_unshift($tup, $value);
373 | return $tup;
374 | }, \Krak\Fun\product(...\array_slice($iters, 1)));
375 | }
376 | }
377 | function when(callable $if, callable $then, $value)
378 | {
379 | return $if($value) ? $then($value) : $value;
380 | }
381 | function toPairs(iterable $iter) : iterable
382 | {
383 | foreach ($iter as $key => $val) {
384 | (yield [$key, $val]);
385 | }
386 | }
387 | function fromPairs(iterable $iter) : iterable
388 | {
389 | foreach ($iter as list($key, $val)) {
390 | (yield $key => $val);
391 | }
392 | }
393 | function pick(iterable $fields, array $data) : array
394 | {
395 | $pickedData = [];
396 | foreach ($fields as $field) {
397 | $pickedData[$field] = $data[$field] ?? null;
398 | }
399 | return $pickedData;
400 | }
401 | function pickBy(callable $pick, array $data) : array
402 | {
403 | $pickedData = [];
404 | foreach ($data as $key => $value) {
405 | if ($pick([$key, $value])) {
406 | $pickedData[$key] = $value;
407 | }
408 | }
409 | return $pickedData;
410 | }
411 | function within(array $fields, iterable $iter) : \Iterator
412 | {
413 | return \Krak\Fun\filterKeys(\Krak\Fun\Curried\inArray($fields), $iter);
414 | }
415 | function without(array $fields, iterable $iter) : \Iterator
416 | {
417 | return \Krak\Fun\filterKeys(\Krak\Fun\Curried\not(\Krak\Fun\Curried\inArray($fields)), $iter);
418 | }
419 | function compact(iterable $iter) : iterable
420 | {
421 | foreach ($iter as $key => $val) {
422 | if ($val !== null) {
423 | (yield $key => $val);
424 | }
425 | }
426 | }
427 | function arrayCompact(iterable $iter) : array
428 | {
429 | $vals = [];
430 | foreach ($iter as $key => $val) {
431 | if ($val !== null) {
432 | $vals[$key] = $val;
433 | }
434 | }
435 | return $vals;
436 | }
437 | function pad(int $size, iterable $iter, $padValue = null) : iterable
438 | {
439 | $i = 0;
440 | foreach ($iter as $key => $value) {
441 | (yield $value);
442 | $i += 1;
443 | }
444 | if ($i >= $size) {
445 | return;
446 | }
447 | foreach (\Krak\Fun\range($i, $size - 1) as $index) {
448 | (yield $padValue);
449 | }
450 | }
451 | // ALIASES
452 | function inArray(array $set, $item) : bool
453 | {
454 | return \in_array($item, $set);
455 | }
456 | function arrayMap(callable $fn, iterable $data) : array
457 | {
458 | return \array_map($fn, \is_array($data) ? $data : \Krak\Fun\toArray($data));
459 | }
460 | function arrayFilter(callable $fn, iterable $data) : array
461 | {
462 | return \array_filter(\is_array($data) ? $data : \Krak\Fun\toArray($data), $fn);
463 | }
464 | function arrayWrap($value)
465 | {
466 | return is_array($value) && array_is_list($value) ? $value : [$value];
467 | }
468 | function all(callable $predicate, iterable $iter) : bool
469 | {
470 | foreach ($iter as $key => $value) {
471 | if (!$predicate($value)) {
472 | return false;
473 | }
474 | }
475 | return true;
476 | }
477 | function any(callable $predicate, iterable $iter) : bool
478 | {
479 | foreach ($iter as $key => $value) {
480 | if ($predicate($value)) {
481 | return true;
482 | }
483 | }
484 | return false;
485 | }
486 | function search(callable $predicate, iterable $iter)
487 | {
488 | foreach ($iter as $value) {
489 | if ($predicate($value)) {
490 | return $value;
491 | }
492 | }
493 | }
494 | function indexOf(callable $predicate, iterable $iter)
495 | {
496 | foreach ($iter as $key => $value) {
497 | if ($predicate($value)) {
498 | return $key;
499 | }
500 | }
501 | }
502 | function trans(callable $trans, callable $fn, $data)
503 | {
504 | return $fn($trans($data));
505 | }
506 | function not(callable $fn, ...$args) : bool
507 | {
508 | return !$fn(...$args);
509 | }
510 | function isInstance($class, $item)
511 | {
512 | return $item instanceof $class;
513 | }
514 | function isNull($val)
515 | {
516 | return \is_null($val);
517 | }
518 | function nullable(callable $fn, $value)
519 | {
520 | return $value === null ? $value : $fn($value);
521 | }
522 | function partition(callable $partition, iterable $iter, int $numParts = 2) : array
523 | {
524 | $parts = \array_fill(0, $numParts, []);
525 | foreach ($iter as $val) {
526 | $index = (int) $partition($val);
527 | $parts[$index][] = $val;
528 | }
529 | return $parts;
530 | }
531 | function map(callable $predicate, iterable $iter) : iterable
532 | {
533 | foreach ($iter as $key => $value) {
534 | (yield $key => $predicate($value));
535 | }
536 | }
537 | function mapKeys(callable $predicate, iterable $iter) : iterable
538 | {
539 | foreach ($iter as $key => $value) {
540 | (yield $predicate($key) => $value);
541 | }
542 | }
543 | function mapKeyValue(callable $fn, iterable $iter) : iterable
544 | {
545 | foreach ($iter as $key => $value) {
546 | [$key, $value] = $fn([$key, $value]);
547 | (yield $key => $value);
548 | }
549 | }
550 | function mapOn(array $maps, iterable $iter) : iterable
551 | {
552 | foreach ($iter as $key => $value) {
553 | if (isset($maps[$key])) {
554 | (yield $key => $maps[$key]($value));
555 | } else {
556 | (yield $key => $value);
557 | }
558 | }
559 | }
560 | function mapAccum(callable $fn, iterable $iter, $acc = null)
561 | {
562 | $data = [];
563 | foreach ($iter as $key => $value) {
564 | [$acc, $value] = $fn($acc, $value);
565 | $data[] = $value;
566 | }
567 | return [$acc, $data];
568 | }
569 | function withState(callable $fn, $initialState = null)
570 | {
571 | $state = $initialState;
572 | return function (...$args) use($fn, &$state) {
573 | [$state, $res] = $fn($state, ...$args);
574 | return $res;
575 | };
576 | }
577 | function arrayReindex(callable $fn, iterable $iter) : array
578 | {
579 | $res = [];
580 | foreach ($iter as $key => $value) {
581 | $res[$fn($value)] = $value;
582 | }
583 | return $res;
584 | }
585 | function reindex(callable $fn, iterable $iter) : iterable
586 | {
587 | foreach ($iter as $key => $value) {
588 | (yield $fn($value) => $value);
589 | }
590 | }
591 | function reduce(callable $reduce, iterable $iter, $acc = null)
592 | {
593 | foreach ($iter as $key => $value) {
594 | $acc = $reduce($acc, $value);
595 | }
596 | return $acc;
597 | }
598 | function reduceKeyValue(callable $reduce, iterable $iter, $acc = null)
599 | {
600 | foreach ($iter as $key => $value) {
601 | $acc = $reduce($acc, [$key, $value]);
602 | }
603 | return $acc;
604 | }
605 | function filter(callable $predicate, iterable $iter) : iterable
606 | {
607 | foreach ($iter as $key => $value) {
608 | if ($predicate($value)) {
609 | (yield $key => $value);
610 | }
611 | }
612 | }
613 | function filterKeys(callable $predicate, iterable $iter) : iterable
614 | {
615 | foreach ($iter as $key => $value) {
616 | if ($predicate($key)) {
617 | (yield $key => $value);
618 | }
619 | }
620 | }
621 | function values(iterable $iter) : iterable
622 | {
623 | foreach ($iter as $v) {
624 | (yield $v);
625 | }
626 | }
627 | function keys(iterable $iter) : iterable
628 | {
629 | foreach ($iter as $k => $v) {
630 | (yield $k);
631 | }
632 | }
633 | function flip(iterable $iter) : iterable
634 | {
635 | foreach ($iter as $k => $v) {
636 | (yield $v => $k);
637 | }
638 | }
639 | function curry(callable $fn, int $num = 1)
640 | {
641 | if ($num == 0) {
642 | return $fn;
643 | }
644 | return function ($arg1) use($fn, $num) {
645 | return curry(function (...$args) use($fn, $arg1) {
646 | return $fn($arg1, ...$args);
647 | }, $num - 1);
648 | };
649 | }
650 | function placeholder()
651 | {
652 | static $v;
653 | $v = $v ?: new class
654 | {
655 | };
656 | return $v;
657 | }
658 | function _()
659 | {
660 | return placeholder();
661 | }
662 | function partial(callable $fn, ...$appliedArgs)
663 | {
664 | return function (...$args) use($fn, $appliedArgs) {
665 | list($appliedArgs, $args) = \array_reduce($appliedArgs, function ($acc, $arg) {
666 | list($appliedArgs, $args) = $acc;
667 | if ($arg === \Krak\Fun\placeholder()) {
668 | $arg = array_shift($args);
669 | }
670 | $appliedArgs[] = $arg;
671 | return [$appliedArgs, $args];
672 | }, [[], $args]);
673 | return $fn(...$appliedArgs, ...$args);
674 | };
675 | }
676 | function autoCurry(array $args, $numArgs, callable $fn)
677 | {
678 | if (\count($args) >= $numArgs) {
679 | return $fn(...$args);
680 | }
681 | if (\count($args) == $numArgs - 1) {
682 | return \Krak\Fun\partial($fn, ...$args);
683 | }
684 | if (\count($args) == 0) {
685 | return \Krak\Fun\curry($fn, $numArgs - 1);
686 | }
687 | return \Krak\Fun\curry(\Krak\Fun\partial($fn, ...$args), $numArgs - 1 - \count($args));
688 | }
689 | function toArray(iterable $iter) : array
690 | {
691 | $data = [];
692 | foreach ($iter as $key => $val) {
693 | $data[] = $val;
694 | }
695 | return $data;
696 | }
697 | function toArrayWithKeys(iterable $iter) : array
698 | {
699 | $data = [];
700 | foreach ($iter as $key => $val) {
701 | $data[$key] = $val;
702 | }
703 | return $data;
704 | }
705 | function id($v)
706 | {
707 | return $v;
708 | }
709 | // UTILITY
710 | function tap(callable $tap, $value)
711 | {
712 | $tap($value);
713 | return $value;
714 | }
715 | function throwIf(callable $throw, callable $if, $value)
716 | {
717 | if ($if($value)) {
718 | throw $throw($value);
719 | }
720 | return $value;
721 | }
722 | function differenceWith(callable $cmp, iterable $a, iterable $b)
723 | {
724 | return \Krak\Fun\filter(function ($aItem) use($cmp, $b) {
725 | return \Krak\Fun\indexOf(\Krak\Fun\partial($cmp, $aItem), $b) === null;
726 | }, $a);
727 | }
728 | function sortFromArray(callable $fn, array $orderedElements, iterable $iter) : array
729 | {
730 | $data = [];
731 | $flippedElements = \array_flip($orderedElements);
732 | foreach ($iter as $value) {
733 | $key = $fn($value);
734 | if (!\array_key_exists($key, $flippedElements)) {
735 | throw new \LogicException('Cannot sort element key ' . $key . ' because it does not exist in the ordered elements.');
736 | }
737 | $data[$flippedElements[$key]] = $value;
738 | }
739 | ksort($data);
740 | return $data;
741 | }
742 | function retry(callable $fn, $shouldRetry = null)
743 | {
744 | if (\is_null($shouldRetry)) {
745 | $shouldRetry = function ($numRetries, \Throwable $t = null) {
746 | return true;
747 | };
748 | }
749 | if (\is_int($shouldRetry)) {
750 | $maxTries = $shouldRetry;
751 | if ($maxTries < 0) {
752 | throw new \LogicException("maxTries must be greater than or equal to 0");
753 | }
754 | $shouldRetry = function ($numRetries, \Throwable $t = null) use($maxTries) {
755 | return $numRetries <= $maxTries;
756 | };
757 | }
758 | if (!\is_callable($shouldRetry)) {
759 | throw new \InvalidArgumentException('shouldRetry must be an int or callable');
760 | }
761 | $numRetries = 0;
762 | do {
763 | try {
764 | return $fn($numRetries);
765 | } catch (\Throwable $t) {
766 | }
767 | $numRetries += 1;
768 | } while ($shouldRetry($numRetries, $t));
769 | throw $t;
770 | }
771 | function pipe(callable ...$fns)
772 | {
773 | return function (...$args) use($fns) {
774 | $isFirstPass = true;
775 | foreach ($fns as $fn) {
776 | if ($isFirstPass) {
777 | $arg = $fn(...$args);
778 | $isFirstPass = false;
779 | } else {
780 | $arg = $fn($arg);
781 | }
782 | }
783 | return $arg;
784 | };
785 | }
786 | function compose(callable ...$fns)
787 | {
788 | return \Krak\Fun\pipe(...\array_reverse($fns));
789 | }
790 | function stack(array $funcs, callable $last = null, callable $resolve = null)
791 | {
792 | return function (...$args) use($funcs, $resolve, $last) {
793 | return \Krak\Fun\reduce(function ($acc, $func) use($resolve) {
794 | return function (...$args) use($acc, $func, $resolve) {
795 | $args[] = $acc;
796 | $func = $resolve ? $resolve($func) : $func;
797 | return $func(...$args);
798 | };
799 | }, $funcs, $last ?: function () {
800 | throw new \LogicException('No stack handler was able to capture this request');
801 | });
802 | };
803 | }
804 | function each(callable $handle, iterable $iter)
805 | {
806 | foreach ($iter as $v) {
807 | $handle($v);
808 | }
809 | }
810 | /** @deprecated */
811 | function onEach(callable $handle, iterable $iter)
812 | {
813 | foreach ($iter as $v) {
814 | $handle($v);
815 | }
816 | }
817 | function iter($iter) : \Iterator
818 | {
819 | if (\is_array($iter)) {
820 | return new \ArrayIterator($iter);
821 | } else {
822 | if ($iter instanceof \Iterator) {
823 | return $iter;
824 | } else {
825 | if (\is_object($iter) || \is_iterable($iter)) {
826 | return (function ($iter) {
827 | foreach ($iter as $key => $value) {
828 | (yield $key => $value);
829 | }
830 | })($iter);
831 | } else {
832 | if (\is_string($iter)) {
833 | return (function ($s) {
834 | for ($i = 0; $i < \strlen($s); $i++) {
835 | (yield $i => $s[$i]);
836 | }
837 | })($iter);
838 | }
839 | }
840 | }
841 | }
842 | throw new \LogicException('Iter could not be converted into an iterable.');
843 | }
--------------------------------------------------------------------------------
/src/fn.php:
--------------------------------------------------------------------------------
1 | {$name}(...$optionalArgs);
9 | }
10 | function prop(string $key, /* object */ $data, $else = null) {
11 | return \property_exists($data, $key) ? $data->{$key} : $else;
12 | }
13 | function index(/* string|int */ $key, /* array|ArrayAccess */ $data, $else = null) {
14 | if (\Krak\Fun\isInstance(\ArrayAccess::class, $data)) {
15 | $exists = $data->offsetExists($key);
16 | }
17 | else {
18 | $exists = \array_key_exists($key, $data);
19 | }
20 | return $exists ? $data[$key] : $else;
21 | }
22 |
23 | function setProp(string $key, $value, /* object */ $data) {
24 | $data->{$key} = $value;
25 | return $data;
26 | }
27 |
28 | function setIndex(/* string|int */ $key, $value, array $data) {
29 | $data[$key] = $value;
30 | return $data;
31 | }
32 |
33 | function setIndexIn(array $keys, $value, array $data) {
34 | return \Krak\Fun\updateIndexIn($keys, function() use ($value) {
35 | return $value;
36 | }, $data);
37 | }
38 |
39 | function propIn(array $props, /* object */ $obj, $else = null) {
40 | foreach ($props as $prop) {
41 | if (!\is_object($obj) || !\property_exists($obj, $prop)) {
42 | return $else;
43 | }
44 |
45 | $obj = $obj->{$prop};
46 | }
47 |
48 | return $obj;
49 | }
50 |
51 | function indexIn(array $keys, array $data, $else = null) {
52 | foreach ($keys as $part) {
53 | if (!\is_array($data) || !\array_key_exists($part, $data)) {
54 | return $else;
55 | }
56 |
57 | $data = $data[$part];
58 | }
59 |
60 | return $data;
61 | }
62 |
63 | function hasIndexIn(array $keys, array $data): bool {
64 | foreach ($keys as $key) {
65 | if (!\is_array($data) || !\array_key_exists($key, $data)) {
66 | return false;
67 | }
68 | $data = $data[$key];
69 | }
70 |
71 | return true;
72 | }
73 |
74 | function updateIndexIn(array $keys, callable $update, array $data): array {
75 | $curData = &$data;
76 | foreach (\array_slice($keys, 0, -1) as $key) {
77 | if (!\array_key_exists($key, $curData)) {
78 | throw new \RuntimeException('Could not updateIn because the keys ' . \implode(' -> ', $keys) . ' could not be found.');
79 | }
80 | $curData = &$curData[$key];
81 | }
82 |
83 | $lastKey = $keys[count($keys) - 1];
84 | $curData[$lastKey] = $update($curData[$lastKey] ?? null);
85 |
86 | return $data;
87 | }
88 |
89 | // UTILITY
90 |
91 | function assign($obj, iterable $iter) {
92 | foreach ($iter as $key => $value) {
93 | $obj->{$key} = $value;
94 | }
95 | return $obj;
96 | }
97 |
98 | function join(string $sep, iterable $iter) {
99 | return \Krak\Fun\reduce(function($acc, $v) use ($sep) {
100 | return $acc ? $acc . $sep . $v : $v;
101 | }, $iter, "");
102 | }
103 |
104 | function construct($className, ...$args) {
105 | return new $className(...$args);
106 | }
107 |
108 | function spread(callable $fn, array $data) {
109 | return $fn(...$data);
110 | }
111 |
112 | function dd($value, callable $dump = null, callable $die = null) {
113 | $dump = $dump ?: (function_exists('dump') ? 'dump' : 'var_dump');
114 | $dump($value);
115 | ($die ?? function() { die; })();
116 | }
117 |
118 | // SLICING
119 |
120 | function takeWhile(callable $predicate, iterable $iter): iterable {
121 | foreach ($iter as $k => $v) {
122 | if ($predicate($v)) {
123 | yield $k => $v;
124 | } else {
125 | return;
126 | }
127 | }
128 | }
129 |
130 | function dropWhile(callable $predicate, iterable $iter): iterable {
131 | $stillDropping = true;
132 | foreach ($iter as $k => $v) {
133 | if ($stillDropping && $predicate($v)) {
134 | continue;
135 | } else if ($stillDropping) {
136 | $stillDropping = false;
137 | }
138 |
139 | yield $k => $v;
140 | }
141 | }
142 |
143 | function take(int $num, iterable $iter): iterable {
144 | return \Krak\Fun\slice(0, $iter, $num);
145 | }
146 |
147 | function drop(int $num, iterable $iter): iterable {
148 | return \Krak\Fun\slice($num, $iter);
149 | }
150 |
151 | function slice(int $start, iterable $iter, $length = INF): iterable {
152 | assert($start >= 0);
153 |
154 | $i = 0;
155 | $end = $start + $length - 1;
156 | foreach ($iter as $k => $v) {
157 | if ($start <= $i && $i <= $end) {
158 | yield $k => $v;
159 | }
160 |
161 | $i += 1;
162 | if ($i > $end) {
163 | return;
164 | }
165 | }
166 | }
167 |
168 | function head(iterable $iter) {
169 | foreach ($iter as $v) {
170 | return $v;
171 | }
172 | }
173 |
174 | function chunk(int $size, iterable $iter): iterable {
175 | assert($size > 0);
176 |
177 | $chunk = [];
178 | foreach ($iter as $v) {
179 | $chunk[] = $v;
180 | if (\count($chunk) == $size) {
181 | yield $chunk;
182 | $chunk = [];
183 | }
184 | }
185 |
186 | if ($chunk) {
187 | yield $chunk;
188 | }
189 | }
190 |
191 | function chunkBy(callable $fn, iterable $iter, ?int $maxSize = null): iterable {
192 | assert($maxSize === null || $maxSize > 0);
193 | $group = [];
194 | $groupKey = null;
195 | foreach ($iter as $v) {
196 | $curGroupKey = $fn($v);
197 | $shouldYieldGroup = ($groupKey !== null && $groupKey !== $curGroupKey)
198 | || ($maxSize !== null && \count($group) >= $maxSize);
199 | if ($shouldYieldGroup) {
200 | yield $group;
201 | $group = [];
202 | }
203 |
204 | $group[] = $v;
205 | $groupKey = $curGroupKey;
206 | }
207 |
208 | if (\count($group)) {
209 | yield $group;
210 | }
211 | }
212 |
213 | function groupBy(callable $fn, iterable $iter, ?int $maxSize = null): iterable {
214 | return \Krak\Fun\chunkBy($fn, $iter, $maxSize);
215 | }
216 |
217 |
218 | // GENERATORS
219 |
220 | function range($start, $end, $step = null) {
221 | if ($start == $end) {
222 | yield $start;
223 | } else if ($start < $end) {
224 | $step = $step ?: 1;
225 | if ($step <= 0) {
226 | throw new \InvalidArgumentException('Step must be greater than 0.');
227 | }
228 | for ($i = $start; $i <= $end; $i += $step) {
229 | yield $i;
230 | }
231 | } else {
232 | $step = $step ?: -1;
233 | if ($step >= 0) {
234 | throw new \InvalidArgumentException('Step must be less than 0.');
235 | }
236 | for ($i = $start; $i >= $end; $i += $step) {
237 | yield $i;
238 | }
239 | }
240 | }
241 |
242 | // OPERATORS
243 |
244 | function op(string $op, $b, $a) {
245 | switch ($op) {
246 | case '==':
247 | case 'eq':
248 | return $a == $b;
249 | case '!=':
250 | case 'neq':
251 | return $a != $b;
252 | case '===':
253 | return $a === $b;
254 | case '!==':
255 | return $a !== $b;
256 | case '>':
257 | case 'gt':
258 | return $a > $b;
259 | case '>=':
260 | case 'gte':
261 | return $a >= $b;
262 | case '<':
263 | case 'lt':
264 | return $a < $b;
265 | case '<=':
266 | case 'lte':
267 | return $a <= $b;
268 | case '+':
269 | return $a + $b;
270 | case '-':
271 | return $a - $b;
272 | case '*':
273 | return $a * $b;
274 | case '**':
275 | return $a ** $b;
276 | case '/':
277 | return $a / $b;
278 | case '%':
279 | return $a % $b;
280 | case '.':
281 | return $a . $b;
282 | default:
283 | throw new \LogicException('Invalid operator '.$op);
284 | }
285 | }
286 |
287 | function andf(callable ...$fns) {
288 | return function($el) use ($fns) {
289 | foreach ($fns as $fn) {
290 | if (!$fn($el)) {
291 | return false;
292 | }
293 | }
294 | return true;
295 | };
296 | }
297 | function orf(callable ...$fns) {
298 | return function($el) use ($fns) {
299 | foreach ($fns as $fn) {
300 | if ($fn($el)) {
301 | return true;
302 | }
303 | }
304 | return false;
305 | };
306 | }
307 |
308 | function chain(iterable ...$iters) {
309 | foreach ($iters as $iter) {
310 | foreach ($iter as $k => $v) {
311 | yield $k => $v;
312 | }
313 | }
314 | }
315 |
316 | function zip(iterable ...$iters): \Iterator {
317 | if (count($iters) == 0) {
318 | return;
319 | }
320 |
321 | $iters = \array_map(iter::class, $iters);
322 |
323 | while (true) {
324 | $tup = [];
325 | foreach ($iters as $iter) {
326 | if (!$iter->valid()) {
327 | return;
328 | }
329 | $tup[] = $iter->current();
330 | $iter->next();
331 | }
332 | yield $tup;
333 | }
334 | }
335 |
336 |
337 | function flatMap(callable $map, iterable $iter): iterable {
338 | foreach ($iter as $k => $v) {
339 | foreach ($map($v) as $k => $v) {
340 | yield $k => $v;
341 | }
342 | }
343 | }
344 |
345 | function flatten(iterable $iter, $levels = INF): iterable {
346 | if ($levels == 0) {
347 | yield from $iter;
348 | } else if ($levels == 1) {
349 | foreach ($iter as $k => $v) {
350 | if (\is_iterable($v)) {
351 | foreach ($v as $k1 => $v1) {
352 | yield $k1 => $v1;
353 | }
354 | } else {
355 | yield $k => $v;
356 | }
357 | }
358 | } else {
359 | foreach ($iter as $k => $v) {
360 | if (\is_iterable($v)) {
361 | foreach (flatten($v, $levels - 1) as $k1 => $v1) {
362 | yield $k1 => $v1;
363 | }
364 | } else {
365 | yield $k => $v;
366 | }
367 | }
368 | }
369 | }
370 |
371 |
372 | function product(iterable ...$iters): iterable {
373 | if (count($iters) === 0) {
374 | yield from [];
375 | return;
376 | }
377 | if (count($iters) === 1) {
378 | yield from \Krak\Fun\map(function($v) { return [$v]; }, $iters[0]);
379 | return;
380 | }
381 |
382 | foreach ($iters[0] as $value) {
383 | yield from \Krak\Fun\map(function(array $tup) use ($value) {
384 | array_unshift($tup, $value);
385 | return $tup;
386 | }, \Krak\Fun\product(...\array_slice($iters, 1)));
387 | }
388 | }
389 |
390 |
391 | function when(callable $if, callable $then, $value) {
392 | return $if($value) ? $then($value) : $value;
393 | }
394 |
395 | function toPairs(iterable $iter): iterable {
396 | foreach ($iter as $key => $val) {
397 | yield [$key, $val];
398 | }
399 | }
400 | function fromPairs(iterable $iter): iterable {
401 | foreach ($iter as list($key, $val)) {
402 | yield $key => $val;
403 | }
404 | }
405 |
406 | function pick(iterable $fields, array $data): array {
407 | $pickedData = [];
408 | foreach ($fields as $field) {
409 | $pickedData[$field] = $data[$field] ?? null;
410 | }
411 | return $pickedData;
412 | }
413 | function pickBy(callable $pick, array $data): array {
414 | $pickedData = [];
415 | foreach ($data as $key => $value) {
416 | if ($pick([$key, $value])) {
417 | $pickedData[$key] = $value;
418 | }
419 | }
420 | return $pickedData;
421 | }
422 |
423 | function within(array $fields, iterable $iter): \Iterator {
424 | return \Krak\Fun\filterKeys(\Krak\Fun\Curried\inArray($fields), $iter);
425 | }
426 | function without(array $fields, iterable $iter): \Iterator {
427 | return \Krak\Fun\filterKeys(\Krak\Fun\Curried\not(\Krak\Fun\Curried\inArray($fields)), $iter);
428 | }
429 |
430 | function compact(iterable $iter): iterable {
431 | foreach ($iter as $key => $val) {
432 | if ($val !== null) {
433 | yield $key => $val;
434 | }
435 | }
436 | }
437 |
438 | function arrayCompact(iterable $iter): array {
439 | $vals = [];
440 | foreach ($iter as $key => $val) {
441 | if ($val !== null) {
442 | $vals[$key] = $val;
443 | }
444 | }
445 | return $vals;
446 | }
447 |
448 | function pad(int $size, iterable $iter, $padValue = null): iterable {
449 | $i = 0;
450 | foreach ($iter as $key => $value) {
451 | yield $value;
452 | $i += 1;
453 | }
454 |
455 | if ($i >= $size) {
456 | return;
457 | }
458 |
459 | foreach (\Krak\Fun\range($i, $size - 1) as $index) {
460 | yield $padValue;
461 | }
462 | }
463 |
464 |
465 | // ALIASES
466 |
467 | function inArray(array $set, $item): bool {
468 | return \in_array($item, $set);
469 | }
470 |
471 | function arrayMap(callable $fn, iterable $data): array {
472 | return \array_map($fn, \is_array($data) ? $data : \Krak\Fun\toArray($data));
473 | }
474 |
475 | function arrayFilter(callable $fn, iterable $data): array {
476 | return \array_filter(\is_array($data) ? $data : \Krak\Fun\toArray($data), $fn);
477 | }
478 |
479 | function arrayWrap($value) {
480 | return is_array($value) && array_is_list($value) ? $value : [$value];
481 | }
482 |
483 | function all(callable $predicate, iterable $iter): bool {
484 | foreach ($iter as $key => $value) {
485 | if (!$predicate($value)) {
486 | return false;
487 | }
488 | }
489 |
490 | return true;
491 | }
492 | function any(callable $predicate, iterable $iter): bool {
493 | foreach ($iter as $key => $value) {
494 | if ($predicate($value)) {
495 | return true;
496 | }
497 | }
498 |
499 | return false;
500 | }
501 | function search(callable $predicate, iterable $iter) {
502 | foreach ($iter as $value) {
503 | if ($predicate($value)) {
504 | return $value;
505 | }
506 | }
507 | }
508 | function indexOf(callable $predicate, iterable $iter) {
509 | foreach ($iter as $key => $value) {
510 | if ($predicate($value)) {
511 | return $key;
512 | }
513 | }
514 | }
515 |
516 | function trans(callable $trans, callable $fn, $data) {
517 | return $fn($trans($data));
518 | }
519 | function not(callable $fn, ...$args): bool {
520 | return !$fn(...$args);
521 | }
522 | function isInstance($class, $item) {
523 | return $item instanceof $class;
524 | }
525 |
526 | function isNull($val) {
527 | return \is_null($val);
528 | }
529 | function nullable(callable $fn, $value) {
530 | return $value === null ? $value : $fn($value);
531 | }
532 |
533 | function partition(callable $partition, iterable $iter, int $numParts = 2): array {
534 | $parts = \array_fill(0, $numParts, []);
535 | foreach ($iter as $val) {
536 | $index = (int) $partition($val);
537 | $parts[$index][] = $val;
538 | }
539 |
540 | return $parts;
541 | }
542 |
543 | function map(callable $predicate, iterable $iter): iterable {
544 | foreach ($iter as $key => $value) {
545 | yield $key => $predicate($value);
546 | }
547 | }
548 |
549 | function mapKeys(callable $predicate, iterable $iter): iterable {
550 | foreach ($iter as $key => $value) {
551 | yield $predicate($key) => $value;
552 | }
553 | }
554 |
555 | function mapKeyValue(callable $fn , iterable $iter): iterable {
556 | foreach ($iter as $key => $value) {
557 | [$key, $value] = $fn([$key, $value]);
558 | yield $key => $value;
559 | }
560 | }
561 |
562 | function mapOn(array $maps, iterable $iter): iterable {
563 | foreach ($iter as $key => $value) {
564 | if (isset($maps[$key])) {
565 | yield $key => $maps[$key]($value);
566 | } else {
567 | yield $key => $value;
568 | }
569 | }
570 | }
571 |
572 | function mapAccum(callable $fn, iterable $iter, $acc = null) {
573 | $data = [];
574 | foreach ($iter as $key => $value) {
575 | [$acc, $value] = $fn($acc, $value);
576 | $data[] = $value;
577 | }
578 |
579 | return [$acc, $data];
580 | }
581 |
582 | function withState(callable $fn, $initialState = null) {
583 | $state = $initialState;
584 | return function(...$args) use ($fn, &$state) {
585 | [$state, $res] = $fn($state, ...$args);
586 | return $res;
587 | };
588 | }
589 |
590 | function arrayReindex(callable $fn, iterable $iter): array {
591 | $res = [];
592 | foreach ($iter as $key => $value) {
593 | $res[$fn($value)] = $value;
594 | }
595 | return $res;
596 | }
597 |
598 | function reindex(callable $fn, iterable $iter): iterable {
599 | foreach ($iter as $key => $value) {
600 | yield $fn($value) => $value;
601 | }
602 | }
603 |
604 | function reduce(callable $reduce, iterable $iter, $acc = null) {
605 | foreach ($iter as $key => $value) {
606 | $acc = $reduce($acc, $value);
607 | }
608 | return $acc;
609 | }
610 |
611 | function reduceKeyValue(callable $reduce, iterable $iter, $acc = null) {
612 | foreach ($iter as $key => $value) {
613 | $acc = $reduce($acc, [$key, $value]);
614 | }
615 | return $acc;
616 | }
617 |
618 | function filter(callable $predicate, iterable $iter): iterable {
619 | foreach ($iter as $key => $value) {
620 | if ($predicate($value)) {
621 | yield $key => $value;
622 | }
623 | }
624 | }
625 | function filterKeys(callable $predicate, iterable $iter): iterable {
626 | foreach ($iter as $key => $value) {
627 | if ($predicate($key)) {
628 | yield $key => $value;
629 | }
630 | }
631 | }
632 |
633 | function values(iterable $iter): iterable {
634 | foreach ($iter as $v) {
635 | yield $v;
636 | }
637 | }
638 |
639 | function keys(iterable $iter): iterable {
640 | foreach ($iter as $k => $v) {
641 | yield $k;
642 | }
643 | }
644 |
645 | function flip(iterable $iter): iterable {
646 | foreach ($iter as $k => $v) {
647 | yield $v => $k;
648 | }
649 | }
650 |
651 | function curry(callable $fn, int $num = 1) {
652 | if ($num == 0) {
653 | return $fn;
654 | }
655 |
656 | return function($arg1) use ($fn, $num) {
657 | return curry(function(...$args) use ($fn, $arg1) {
658 | return $fn($arg1, ...$args);
659 | }, $num - 1);
660 | };
661 | }
662 |
663 | function placeholder() {
664 | static $v;
665 |
666 | $v = $v ?: new class {};
667 | return $v;
668 | }
669 | function _() {
670 | return placeholder();
671 | }
672 |
673 | function partial(callable $fn, ...$appliedArgs) {
674 | return function(...$args) use ($fn, $appliedArgs) {
675 | list($appliedArgs, $args) = \array_reduce($appliedArgs, function($acc, $arg) {
676 | list($appliedArgs, $args) = $acc;
677 | if ($arg === \Krak\Fun\placeholder()) {
678 | $arg = array_shift($args);
679 | }
680 |
681 | $appliedArgs[] = $arg;
682 | return [$appliedArgs, $args];
683 | }, [[], $args]);
684 |
685 | return $fn(...$appliedArgs, ...$args);
686 | };
687 | }
688 |
689 | function autoCurry(array $args, $numArgs, callable $fn) {
690 | if (\count($args) >= $numArgs) {
691 | return $fn(...$args);
692 | }
693 | if (\count($args) == $numArgs - 1) {
694 | return \Krak\Fun\partial($fn, ...$args);
695 | }
696 | if (\count($args) == 0) {
697 | return \Krak\Fun\curry($fn, $numArgs - 1);
698 | }
699 |
700 | return \Krak\Fun\curry(
701 | \Krak\Fun\partial($fn, ...$args),
702 | ($numArgs - 1 - \count($args))
703 | );
704 | }
705 |
706 | function toArray(iterable $iter): array {
707 | $data = [];
708 | foreach ($iter as $key => $val) {
709 | $data[] = $val;
710 | }
711 | return $data;
712 | }
713 |
714 | function toArrayWithKeys(iterable $iter): array {
715 | $data = [];
716 | foreach ($iter as $key => $val) {
717 | $data[$key] = $val;
718 | }
719 | return $data;
720 | }
721 |
722 | function id($v) {
723 | return $v;
724 | }
725 |
726 |
727 | // UTILITY
728 |
729 | function tap(callable $tap, $value) {
730 | $tap($value);
731 | return $value;
732 | }
733 |
734 | function throwIf(callable $throw, callable $if, $value) {
735 | if ($if($value)) {
736 | throw $throw($value);
737 | }
738 |
739 | return $value;
740 | }
741 |
742 | function differenceWith(callable $cmp, iterable $a, iterable $b) {
743 | return \Krak\Fun\filter(function($aItem) use ($cmp, $b) {
744 | return \Krak\Fun\indexOf(\Krak\Fun\partial($cmp, $aItem), $b) === null;
745 | }, $a);
746 | }
747 |
748 | function sortFromArray(callable $fn, array $orderedElements, iterable $iter): array {
749 | $data = [];
750 | $flippedElements = \array_flip($orderedElements);
751 |
752 | foreach ($iter as $value) {
753 | $key = $fn($value);
754 | if (!\array_key_exists($key, $flippedElements)) {
755 | throw new \LogicException('Cannot sort element key ' . $key . ' because it does not exist in the ordered elements.');
756 | }
757 |
758 | $data[$flippedElements[$key]] = $value;
759 | }
760 |
761 | ksort($data);
762 | return $data;
763 | }
764 |
765 | function retry(callable $fn, $shouldRetry = null) {
766 | if (\is_null($shouldRetry)) {
767 | $shouldRetry = function($numRetries, \Throwable $t = null) { return true; };
768 | }
769 | if (\is_int($shouldRetry)) {
770 | $maxTries = $shouldRetry;
771 | if ($maxTries < 0) {
772 | throw new \LogicException("maxTries must be greater than or equal to 0");
773 | }
774 | $shouldRetry = function($numRetries, \Throwable $t = null) use ($maxTries) { return $numRetries <= $maxTries; };
775 | }
776 | if (!\is_callable($shouldRetry)) {
777 | throw new \InvalidArgumentException('shouldRetry must be an int or callable');
778 | }
779 |
780 | $numRetries = 0;
781 | do {
782 | try {
783 | return $fn($numRetries);
784 | } catch (\Throwable $t) {}
785 | $numRetries += 1;
786 | } while ($shouldRetry($numRetries, $t));
787 |
788 | throw $t;
789 | }
790 |
791 | function pipe(callable ...$fns) {
792 | return function(...$args) use ($fns) {
793 | $isFirstPass = true;
794 | foreach ($fns as $fn) {
795 | if ($isFirstPass) {
796 | $arg = $fn(...$args);
797 | $isFirstPass = false;
798 | } else {
799 | $arg = $fn($arg);
800 | }
801 |
802 | }
803 | return $arg;
804 | };
805 | }
806 |
807 | function compose(callable ...$fns) {
808 | return \Krak\Fun\pipe(...\array_reverse($fns));
809 | }
810 |
811 | function stack(array $funcs, callable $last = null, callable $resolve = null) {
812 | return function(...$args) use ($funcs, $resolve, $last) {
813 | return \Krak\Fun\reduce(function($acc, $func) use ($resolve) {
814 | return function(...$args) use ($acc, $func, $resolve) {
815 | $args[] = $acc;
816 | $func = $resolve ? $resolve($func) : $func;
817 | return $func(...$args);
818 | };
819 | }, $funcs, $last ?: function() { throw new \LogicException('No stack handler was able to capture this request'); });
820 | };
821 | }
822 |
823 | function each(callable $handle, iterable $iter) {
824 | foreach ($iter as $v) {
825 | $handle($v);
826 | }
827 | }
828 | /** @deprecated */
829 | function onEach(callable $handle, iterable $iter) {
830 | foreach ($iter as $v) {
831 | $handle($v);
832 | }
833 | }
834 |
835 | function iter($iter): \Iterator {
836 | if (\is_array($iter)) {
837 | return new \ArrayIterator($iter);
838 | } else if ($iter instanceof \Iterator) {
839 | return $iter;
840 | } else if (\is_object($iter) || \is_iterable($iter)) {
841 | return (function($iter) {
842 | foreach ($iter as $key => $value) {
843 | yield $key => $value;
844 | }
845 | })($iter);
846 | } else if (\is_string($iter)) {
847 | return (function($s) {
848 | for ($i = 0; $i < \strlen($s); $i++) {
849 | yield $i => $s[$i];
850 | }
851 | })($iter);
852 | }
853 |
854 | throw new \LogicException('Iter could not be converted into an iterable.');
855 | }
856 |
--------------------------------------------------------------------------------
/src/generate.php:
--------------------------------------------------------------------------------
1 | name . '\\' . $fn->name;
27 | return new Stmt\Const_([
28 | new Const_($fn->name, BuilderHelpers::normalizeValue($constVal))
29 | ]);
30 | }
31 |
32 | function curryFunction(Function_ $fn) {
33 | list($params, $optionalParams) = partition(function($param) {
34 | return $param->default
35 | || ($param->variadic && strpos((string) $param->var->name, 'optional') === 0);
36 | }, $fn->params);
37 |
38 | // return all params but the first and reverse them
39 | $revParams = array_merge(array_reverse($params), $optionalParams);
40 |
41 | // if we have only one requried arg, then we allow zero required args in curried implementation
42 | $numParams = count($params) > 1 ? count($params) - 2 : count($params) - 1;
43 | $stmts = array_reduce(range(0, $numParams), function($stmts, $i) use ($revParams) {
44 | $remainingArgsForUse = array_slice($revParams, $i + 1);
45 | $curParam = $revParams[$i];
46 | return [new Return_(new Closure([
47 | 'params' => [clone $curParam],
48 | 'uses' => array_map(function($param) {
49 | return new ClosureUse(clone $param->var);
50 | }, $remainingArgsForUse),
51 | 'stmts' => $stmts,
52 | ]))];
53 | }, $fn->getStmts());
54 |
55 | return new Function_($fn->name, [
56 | 'params' => array_merge(
57 | count($params) > 1 ? [$params[0]] : [],
58 | $optionalParams
59 | ),
60 | 'stmts' => $stmts,
61 | ]);
62 | }
63 |
64 | function isCurryable($func) {
65 | if (in_array((string) $func->name, ['curry', 'autoCurry'])) {
66 | return false;
67 | }
68 | $numReqParams = count(toArray(filter(function($param) {
69 | return $param->default === null;
70 | }, $func->params)));
71 | return $numReqParams > 1 || ($numReqParams == 1 && count($func->params) > 1);
72 | }
73 |
--------------------------------------------------------------------------------
/test/api.spec.php:
--------------------------------------------------------------------------------
1 | equal('2,4,6,8');
14 | });
15 | it('can utilize the pre-1.0 generated api helpers', function() {
16 | $res = _f\compose(
17 | _c\join(','),
18 | _f\toArray,
19 | _c\map(_c\op('*')(2))
20 | )([1,2,3,4]);
21 | expect($res)->equal('2,4,6,8');
22 | });
23 | it('can run the home page example', function() {
24 | $res = f\compose(
25 | c\toArray,
26 | c\map(function($tup) {
27 | return $tup[0] + $tup[1];
28 | }),
29 | c\toPairs
30 | )([1,2,3]);
31 | expect($res)->equal([1,3,5]);
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/test/fn.spec.php:
--------------------------------------------------------------------------------
1 | ', 2)))([1,2,3,4]);
16 | expect($res)->equal([9, 12]);
17 | });
18 |
19 | describe('autoCurry', function() {
20 | it('can call a function if all args are available', function() {
21 | $res = autoCurry([1,2,3], 3, _idArgs::class);
22 | expect($res)->equal([1,2,3]);
23 | });
24 | it('can partially apply a function if all but one arg is available', function() {
25 | $res = autoCurry([1,2], 3, _idArgs::class)(3);
26 | expect($res)->equal([1,2,3]);
27 | });
28 | it('can curry a partially applied function if less than n - 1 args are available', function() {
29 | $res = autoCurry([1], 3, _idArgs::class)(2)(3);
30 | expect($res)->equal([1,2,3]);
31 | });
32 | it('can curry a function if no args are available', function() {
33 | $res = autoCurry([], 3, _idArgs::class)(1)(2)(3);
34 | expect($res)->equal([1,2,3]);
35 | });
36 | });
37 | describe('all', function() {
38 | docFn(all::class);
39 | test('Returns true if the predicate returns true on all of the items', function() {
40 | $res = all(function($v) { return $v % 2 == 0; }, [2,4,6]);
41 | expect($res)->equal(true);
42 | });
43 | test('Returns false if the predicate returns false on any of the items', function() {
44 | $res = all(function($v) { return $v % 2 == 0; }, [1,2,4,6]);
45 | expect($res)->equal(false);
46 | });
47 | });
48 | describe('any', function() {
49 | docFn(any::class);
50 | test('Returns true if the predicate returns true on any of the items', function() {
51 | $res = any(function($v) { return $v % 2 == 0; }, [1,3,4,5]);
52 | expect($res)->equal(true);
53 | });
54 | test('Returns false if the predicate returns false on all of the items', function() {
55 | $res = any(function($v) { return $v % 2 == 0; }, [1,3,5]);
56 | expect($res)->equal(false);
57 | });
58 | });
59 | describe('arrayCompact', function() {
60 | docFn(arrayCompact::class);
61 | test('It will remove all nulls from an iterable and return an array', function() {
62 | $res = arrayCompact([1,2,null,null,3]);
63 | expect(\array_values($res))->equal([1,2,3]);
64 | });
65 | docOutro('Keep in mind that the keys will be preserved when using arrayCompact, so make sure to use array_values if you want to ignore keys.');
66 | });
67 | describe('arrayFilter', function() {
68 | docFn(arrayFilter::class);
69 | test('Alias of array_filter', function() {
70 | $res = arrayFilter(partial(op, '<', 2), [1,2,3]);
71 | expect($res)->equal([1]);
72 | });
73 | test('Filters iterables as well as arrays', function() {
74 | $res = arrayFilter(partial(op, '<', 2), range(1, 3));
75 | expect($res)->equal([1]);
76 | });
77 | });
78 | describe('arrayMap', function() {
79 | docFn(arrayMap::class);
80 | test('Alias of array_map', function() {
81 | $res = arrayMap(partial(op, '*', 2), [1,2,3]);
82 | expect($res)->equal([2,4,6]);
83 | });
84 | test('Maps iterables as well as arrays', function() {
85 | $res = arrayMap(partial(op, '*', 2), range(1, 3));
86 | expect($res)->equal([2,4,6]);
87 | });
88 | });
89 | describe('arrayReindex', function() {
90 | docFn(arrayReindex::class);
91 | test('Re-indexes a collection via a callable into an associative array', function() {
92 | $res = arrayReindex(function($v) {
93 | return $v['id'];
94 | }, [['id' => 2], ['id' => 3], ['id' => 1]]);
95 |
96 | expect($res)->equal([
97 | 2 => ['id' => 2],
98 | 3 => ['id' => 3],
99 | 1 => ['id' => 1],
100 | ]);
101 | });
102 | });
103 | describe('arrayWrap', function() {
104 | docFn(arrayWrap::class);
105 | test('Wraps any non list array into an array', function() {
106 | $results = arrayMap(arrayWrap, [
107 | 1,
108 | 'abc',
109 | ['a' => 1],
110 | ]);
111 |
112 | expect($results)->equal([
113 | [1],
114 | ['abc'],
115 | [['a' => 1]],
116 | ]);
117 | });
118 | test('List based arrays are left as is', function() {
119 | $results = arrayMap(arrayWrap, [
120 | [],
121 | [1,2,3],
122 | ]);
123 | expect($results)->equal([
124 | [],
125 | [1,2,3],
126 | ]);
127 | });
128 | docOutro('Note: `array_is_list` which requires php 8.1 or symfony/polyfill-php81');
129 | });
130 | describe('assign', function() {
131 | docFn(assign::class);
132 | test('Assigns iterable keys and values to an object', function() {
133 | $obj = new \StdClass();
134 | $obj = assign($obj, ['a' => 1, 'b' => 2]);
135 | expect($obj->a)->equal(1);
136 | expect($obj->b)->equal(2);
137 | });
138 | });
139 |
140 | describe('chain', function() {
141 | docFn(chain::class);
142 | test('Chains iterables together into one iterable', function() {
143 | $res = chain([1], range(2, 3));
144 | expect(toArray($res))->equal([1,2,3]);
145 | });
146 | });
147 | describe('chunk', function() {
148 | docFn(chunk::class);
149 | test('Chunks an iterable into equal sized chunks.', function() {
150 | $res = chunk(2, [1,2,3,4]);
151 | expect(toArray($res))->equal([[1,2], [3,4]]);
152 | });
153 | test('If there is any remainder, it is yielded as is', function() {
154 | $res = chunk(3, [1,2,3,4]);
155 | expect(toArray($res))->equal([[1,2,3], [4]]);
156 | });
157 | });
158 | describe('chunkBy', function() {
159 | docFn(chunkBy::class);
160 | test('Chunks items together off of the result from the callable', function() {
161 | $items = ['aa', 'ab', 'ac', 'ba', 'bb', 'bc', 'ca', 'cb', 'cc'];
162 | $chunks = chunkBy(function(string $item) {
163 | return $item[0]; // return first char
164 | }, $items);
165 | expect(toArray($chunks))->equal([
166 | ['aa', 'ab', 'ac'],
167 | ['ba', 'bb', 'bc'],
168 | ['ca', 'cb', 'cc']
169 | ]);
170 | });
171 | test('Allows a maxSize to prevent chunks from exceeding a limit', function() {
172 | $items = ['aa', 'ab', 'ac', 'ba', 'bb', 'bc', 'ca', 'cb', 'cc'];
173 | $chunks = chunkBy(function(string $item) {
174 | return $item[0]; // return first char
175 | }, $items, 2);
176 | expect(toArray($chunks))->equal([
177 | ['aa', 'ab'], ['ac'],
178 | ['ba', 'bb'], ['bc'],
179 | ['ca', 'cb'], ['cc']
180 | ]);
181 | });
182 | });
183 | describe('compact', function() {
184 | docFn(compact::class);
185 |
186 | test('Removes all null values from an iterable', function() {
187 | $res = compact([1,null,2,3,null,null,4]);
188 | expect(toArray($res))->equal([1,2,3,4]);
189 | });
190 | });
191 | describe('compose', function() {
192 | docFn(compose::class);
193 |
194 | test('Composes functions together. compose(f, g)(x) == f(g(x))', function() {
195 | $mul2 = Curried\op('*')(2);
196 | $add3 = Curried\op('+')(3);
197 |
198 | $add3ThenMul2 = compose($mul2, $add3);
199 | $res = $add3ThenMul2(5);
200 | expect($res)->equal(16);
201 | });
202 | test('Allows an empty initial argument', function() {
203 | $res = compose(
204 | Curried\reduce(function($acc, $v) { return $acc + $v; }, 0),
205 | function() { yield from [1,2,3]; }
206 | )();
207 | expect($res)->equal(6);
208 | });
209 | });
210 | describe('construct', function() {
211 | docFn(construct::class);
212 | test('Constructs (instantiates) a new class with the given arguments', function() {
213 | $res = construct(\ArrayObject::class, [1,2,3]);
214 | expect($res->count())->equal(3);
215 | });
216 | });
217 | describe('curry', function() {
218 | docFn(curry::class);
219 |
220 | test('currys the given function $n times', function() {
221 | $res = curry(_idArgs::class, 2)(1)(2)(3);
222 | expect($res)->equal([1,2,3]);
223 | });
224 | docOutro('Given a function definition: (a, b) -> c. A curried version will look like (a) -> (b) -> c');
225 | });
226 | describe('differenceWith', function() {
227 | docFn(differenceWith::class);
228 | test('Takes the difference between two iterables with a given comparator', function() {
229 | $res = differenceWith(partial(op, '==='), [1,2,3,4,5], [2,3,4]);
230 | expect(toArray($res))->equal([1,5]);
231 | });
232 | });
233 | describe('dd', function() {
234 | docFn(dd::class);
235 | test('dumps and dies', function() {
236 | $res = null;
237 | $died = false;
238 | $dump = function($v) use (&$res) {$res = $v;};
239 | $die = function() use (&$died) { $died = true; };
240 | dd(1, $dump, $die);
241 | expect($res)->equal(1);
242 | expect($died)->equal(true);
243 | });
244 | });
245 | describe('drop', function() {
246 | docFn(drop::class);
247 | test('Drops the first num items from an iterable', function() {
248 | $res = drop(2, range(0, 3));
249 | expect(toArray($res))->equal([2, 3]);
250 | });
251 | });
252 | describe('dropWhile', function() {
253 | docFn(dropWhile::class);
254 | test('Drops elements from the iterable while the predicate returns true', function() {
255 | $res = dropWhile(Curried\op('>')(0), [2, 1, 0, 1, 2]);
256 | expect(toArray($res))->equal([0, 1, 2]);
257 | });
258 | });
259 | describe('each', function() {
260 | docFn(each::class);
261 | test('Invokes a callable on each item in an iterable', function() {
262 | $state = [
263 | (object) ['id' => 1],
264 | (object) ['id' => 2],
265 | ];
266 | each(function($item) {
267 | $item->id += 1;
268 | }, $state);
269 |
270 | expect([$state[0]->id, $state[1]->id])->equal([2,3]);
271 | });
272 | docOutro('Normally using php foreach should suffice for iterating over an iterable; however, php variables in foreach loops are not scoped whereas closures are.');
273 | });
274 | describe('filter', function() {
275 | docFn(filter::class);
276 | it('Lazily filters an iterable off of a predicate that should return true or false. If true, keep the data, else remove the data from the iterable', function() {
277 | $values = filter(partial(op, '>', 2), [1,2,3,4]); // keep all items that are greater than 2
278 | expect(toArray($values))->equal([3,4]);
279 | });
280 | });
281 | describe('filterKeys', function() {
282 | docFn(filterKeys::class);
283 | test('Filters an iterable off of the keys', function() {
284 | $res = filterKeys(Curried\inArray(['a', 'b']), [
285 | 'a' => 1,
286 | 'b' => 2,
287 | 'c' => 3,
288 | ]);
289 | expect(toArrayWithKeys($res))->equal(['a' => 1, 'b' => 2]);
290 | });
291 | });
292 | describe('flatMap', function() {
293 | docFn(flatMap::class);
294 |
295 | test('Maps and then flattens an iterable', function() {
296 | $res = flatMap(function($v) {
297 | return [-$v, $v];
298 | }, range(1, 3));
299 |
300 | expect(toArray($res))->equal([-1, 1, -2, 2, -3, 3]);
301 | });
302 |
303 | docOutro('flatMap is perfect for when you want to map an iterable and also add elements to the resulting iterable.');
304 | });
305 | describe('flatten', function() {
306 | docFn(flatten::class);
307 | test('Flattens nested iterables into a flattened set of elements', function() {
308 | $res = flatten([1, [2, [3, [4]]]]);
309 | expect(toArray($res))->equal([1,2,3,4]);
310 | });
311 | test('Can flatten a specific number of levels', function() {
312 | $res = flatten([1,[2, [3]]], 1);
313 | expect(toArray($res))->equal([1, 2, [3]]);
314 | });
315 | test('Flattening zero levels does nothing', function() {
316 | $res = flatten([1, [2]], 0);
317 | expect(toArray($res))->equal([1,[2]]);
318 | });
319 | });
320 | describe('flip', function() {
321 | docFn(flip::class);
322 | test('Flips the keys => values of an iterable to values => keys', function() {
323 | $res = flip(['a' => 0, 'b' => 1]);
324 | expect(toArray($res))->equal(['a', 'b']);
325 | });
326 | });
327 | describe('fromPairs', function() {
328 | docFn(fromPairs::class);
329 | test('Converts an iterable of tuples [$key, $value] into an associative iterable', function() {
330 | $res = fromPairs([
331 | ['a', 1],
332 | ['b', 2]
333 | ]);
334 | expect(toArrayWithKeys($res))->equal([
335 | 'a' => 1,
336 | 'b' => 2
337 | ]);
338 | });
339 | });
340 | describe('groupBy', function() {
341 | docFn(groupBy::class);
342 | docIntro('Alias of chunkBy');
343 | test('Groups items together off of the result from the callable', function() {
344 | $items = ['aa', 'ab', 'ac', 'ba', 'bb', 'bc', 'ca', 'cb', 'cc'];
345 | $groupedItems = groupBy(function(string $item) {
346 | return $item[0]; // return first char
347 | }, $items);
348 | expect(toArray($groupedItems))->equal([
349 | ['aa', 'ab', 'ac'],
350 | ['ba', 'bb', 'bc'],
351 | ['ca', 'cb', 'cc']
352 | ]);
353 | });
354 | test('Allows a maxSize to prevent groups from exceeding a limit', function() {
355 | $items = ['aa', 'ab', 'ac', 'ba', 'bb', 'bc', 'ca', 'cb', 'cc'];
356 | $groupedItems = groupBy(function(string $item) {
357 | return $item[0]; // return first char
358 | }, $items, 2);
359 | expect(toArray($groupedItems))->equal([
360 | ['aa', 'ab'], ['ac'],
361 | ['ba', 'bb'], ['bc'],
362 | ['ca', 'cb'], ['cc']
363 | ]);
364 | });
365 | });
366 | describe('hasIndexIn', function() {
367 | docFn(hasIndexIn::class);
368 |
369 | test('Checks if a nested index exists in the given data', function() {
370 | $res = hasIndexIn(['a', 'b', 'c'], [
371 | 'a' => ['b' => ['c' => null]]
372 | ]);
373 |
374 | expect($res)->equal(true);
375 | });
376 | test('Returns false if any of the indexes do not exist in the data', function() {
377 | $res = hasIndexIn(['a', 'b', 'c'], [
378 | 'a' => ['b' => []]
379 | ]);
380 |
381 | expect($res)->equal(false);
382 | });
383 | });
384 | describe('head', function() {
385 | docFn(head::class);
386 | test('Returns the fist element in an iterable', function() {
387 | $res = head([1,2,3]);
388 | expect($res)->equal(1);
389 | });
390 | test('But returns null if the iterable is empty', function() {
391 | $res = head([]);
392 | expect($res)->equal(null);
393 | });
394 | });
395 | describe('inArray', function() {
396 | docFn(inArray::class);
397 | it('Checks if an item is within an array of items', function() {
398 | $res = inArray([1,2,3], 2);
399 | expect($res)->equal(true);
400 | });
401 | });
402 | describe('index', function() {
403 | docFn(index::class);
404 | test('Accesses an index in an array', function() {
405 | $res = index('a', ['a' => 1]);
406 | expect($res)->equal(1);
407 | });
408 | test('If no value exists at the given index, $else will be returned', function() {
409 | $res = index('a', ['b' => 1], 2);
410 | expect($res)->equal(2);
411 | });
412 | test('Also works with objects that implement ArrayAccess', function() {
413 | class MyClass implements \ArrayAccess
414 | {
415 | private $container = [];
416 |
417 | public function __construct() {
418 | $this->container = ['one' => 1, 'two' => 2];
419 | }
420 |
421 | public function offsetExists($offset) {
422 | return isset($this->container[$offset]);
423 | }
424 |
425 | public function offsetGet($offset) {
426 | return isset($this->container[$offset]) ? $this->container[$offset] : null;
427 | }
428 |
429 | public function offsetSet($offset, $value) {
430 | /* ... */
431 | }
432 |
433 | public function offsetUnset($offset) {
434 | /* ... */
435 | }
436 | }
437 |
438 | $object = new MyClass();
439 | expect(index('two', $object))->equal(2);
440 | expect(index('three', $object, 'else'))->equal('else');
441 | });
442 | });
443 | describe('indexIn', function() {
444 | docFn(indexIn::class);
445 | test('Accesses a nested index in a deep array structure', function() {
446 | $res = indexIn(['a', 'b'], ['a' => ['b' => 1]]);
447 | expect($res)->equal(1);
448 | });
449 | test('If any of the indexes do not exist, $else will be returned', function() {
450 | $res = indexIn(['a', 'b'], ['a' => ['c' => 1]], 2);
451 | expect($res)->equal(2);
452 | });
453 | });
454 | describe('indexOf', function() {
455 | docFn(indexOf::class);
456 | test('Searches for an element and returns the key if found', function() {
457 | $res = indexOf(partial(op, '==', 'b'), ['a', 'b', 'c']);
458 | expect($res)->equal(1);
459 | });
460 | });
461 | describe('isNull', function() {
462 | docFn(isNull::class);
463 | test('alias for is_null', function() {
464 | expect(isNull(null))->equal(true);
465 | expect(isNull(0))->equal(false);
466 | });
467 | });
468 | describe('iter', function() {
469 | docFn(iter::class);
470 | docIntro('Converts any iterable into a proper instance of Iterator.');
471 | test('Can convert arrays', function() {
472 | expect(iter([1,2,3]))->instanceof('Iterator');
473 | });
474 | test('Can convert an Iterator', function() {
475 | expect(iter(new \ArrayIterator([1,2,3])))->instanceof('Iterator');
476 | });
477 | test('Can convert objects', function() {
478 | $obj = (object) ['a' => 1, 'b' => 2];
479 | expect(iter($obj))->instanceof('Iterator');
480 | expect(toArrayWithKeys(iter($obj)))->equal(['a' => 1, 'b' => 2]);
481 | });
482 | test('Can convert any iterable', function() {
483 | $a = new class() implements \IteratorAggregate {
484 | public function getIterator() {
485 | return new \ArrayIterator([1,2,3]);
486 | }
487 | };
488 | expect(iter($a))->instanceof('Iterator');
489 | expect(toArray(iter($a)))->equal([1,2,3]);
490 | });
491 | test('Can convert strings', function() {
492 | expect(iter('abc'))->instanceof('Iterator');
493 | expect(toArray(iter('abc')))->equal(['a', 'b', 'c']);
494 | });
495 | test('Will throw an exception otherwise', function() {
496 | expect(function() {
497 | iter(1);
498 | })->throw('LogicException', 'Iter could not be converted into an iterable.');
499 | });
500 | });
501 | describe('join', function() {
502 | docFn(join::class);
503 | test('Joins an iterable with a given separator', function() {
504 | $res = join(",", range(1,3));
505 | expect($res)->equal("1,2,3");
506 | });
507 | });
508 | describe('keys', function() {
509 | docFn(keys::class);
510 | test('Yields only the keys of an in iterable', function() {
511 | $keys = keys(['a' => 1, 'b' => 2]);
512 | expect(toArray($keys))->equal(['a', 'b']);
513 | });
514 | });
515 | describe('map', function() {
516 | docFn(map::class);
517 | it('Lazily maps an iterable\'s values to a different set', function() {
518 | $values = map(partial(op, '*', 2), [1,2,3,4]);
519 | expect(toArray($values))->equal([2,4,6,8]);
520 | });
521 | });
522 | describe('mapAccum', function() {
523 | docFn(mapAccum::class);
524 |
525 | test('Maps a function to each element of a list while passing in an accumulator to accumulate over every iteration', function() {
526 | $data = iter('abcd');
527 | [$totalSort, $values] = mapAccum(function($acc, $value) {
528 | return [$acc + 1, ['name' => $value, 'sort' => $acc]];
529 | }, iter('abcd'), 0);
530 |
531 | expect($totalSort)->equal(4);
532 | expect($values)->equal([
533 | ['name' => 'a', 'sort' => 0],
534 | ['name' => 'b', 'sort' => 1],
535 | ['name' => 'c', 'sort' => 2],
536 | ['name' => 'd', 'sort' => 3],
537 | ]);
538 | });
539 |
540 | docOutro('Note: mapAccum converts the interable into an array and is not lazy like most of the other functions in this library');
541 | });
542 | describe('mapKeys', function() {
543 | docFn(mapKeys::class);
544 | it('Lazily maps an iterable\'s keys to a different set', function() {
545 | $keys = mapKeys(partial(op, '.', '_'), ['a' => 1, 'b' => 2]);
546 | expect(toArrayWithKeys($keys))->equal(['a_' => 1, 'b_' => 2]);
547 | });
548 | });
549 | describe('mapKeyValue', function() {
550 | docFn(mapKeyValue::class);
551 | it('Lazily maps an iterable\'s key/value tuples to a different set', function() {
552 | $keys = mapKeyValue(function($kv) {
553 | [$key, $value] = $kv;
554 | return ["{$key}_", $value * $value];
555 | }, ['a' => 1, 'b' => 2]);
556 | expect(toArrayWithKeys($keys))->equal(['a_' => 1, 'b_' => 4]);
557 | });
558 | });
559 | describe('mapOn', function() {
560 | docFn(mapOn::class);
561 | it('Maps values on specific keys', function() {
562 | $values = mapOn([
563 | 'a' => partial(op, '*', 3),
564 | 'b' => partial(op, '+', 1),
565 | ], [
566 | 'a' => 1,
567 | 'b' => 2,
568 | 'c' => 3,
569 | ]);
570 |
571 | expect(toArray($values))->equal([3,3,3]);
572 | });
573 | });
574 | describe('nullable', function() {
575 | docFn(nullable::class);
576 | test('Performs the callable if the value is not null', function() {
577 | expect(nullable('intval', '0'))->equal(0);
578 | });
579 | test('Returns null if the value is null', function() {
580 | expect(nullable('intval', null))->equal(null);
581 | });
582 | });
583 | describe('onEach', function() {
584 | docFn(onEach::class);
585 | docIntro('Duplicate of each.');
586 | test('Invokes a callable on each item in an iterable', function() {
587 | $state = [
588 | (object) ['id' => 1],
589 | (object) ['id' => 2],
590 | ];
591 | onEach(function($item) {
592 | $item->id += 1;
593 | }, $state);
594 |
595 | expect([$state[0]->id, $state[1]->id])->equal([2,3]);
596 | });
597 | docOutro('Normally using php foreach should suffice for iterating over an iterable; however, php variables in foreach loops are not scoped whereas closures are.');
598 | });
599 | describe('op', function() {
600 | docFn(op::class);
601 |
602 | $intro = <<<'INTRO'
603 | op evaluates binary operations. It expects the right hand operator first which makes most sense when currying or partially applying the op function.
604 | When reading the op func, it should be read: `evaluate $op with $b with $a` e.g.:
605 |
606 | ```
607 | op('+', 2, 3) -> add 2 with 3
608 | op('-', 2, 3) -> subtract 2 from 3
609 | op('>', 2, 3) => compare greater than 2 with 3
610 | ```
611 | INTRO;
612 | docIntro($intro);
613 | test('Evaluates two values with a given operator', function() {
614 | $res = op('<', 2, 1);
615 | expect($res)->equal(true);
616 | });
617 | test('Supports equality operators', function() {
618 | $obj = new stdClass();
619 | $ops = [
620 | ['==', [1, 1]],
621 | ['eq', [2, 2]],
622 | ['!=', [1, 2]],
623 | ['neq', [2, 3]],
624 | ['===', [$obj, $obj]],
625 | ['!==', [new stdClass(), new stdClass()]],
626 | ['>', [1, 2]],
627 | ['gt', [1, 3]],
628 | ['>=', [1, 2]],
629 | ['gte', [1, 1]],
630 | ['<', [2, 1]],
631 | ['lt', [3, 1]],
632 | ['<=', [2, 1]],
633 | ['lte', [1, 1]],
634 | ];
635 |
636 | foreach ($ops as list($op, list($b, $a))) {
637 | $res = op($op, $b, $a);
638 | expect($res)->equal(true);
639 | }
640 | });
641 | test('Supports other operators', function() {
642 | $ops = [
643 | ['+', [2, 3], 5],
644 | ['-', [2, 3], 1],
645 | ['*', [2, 3], 6],
646 | ['**', [2, 3], 9],
647 | ['/', [2, 3], 1.5],
648 | ['%', [2, 3], 1],
649 | ['.', ['b', 'a'], 'ab'],
650 | ];
651 |
652 | foreach ($ops as list($op, list($b, $a), $expected)) {
653 | $res = op($op, $b, $a);
654 | expect($res)->equal($expected);
655 | }
656 | });
657 | test('Is more useful partially applied or curried', function() {
658 | $add2 = Curried\op('+')(2);
659 | $mul3 = partial(op, '*', 3);
660 | $sub4 = Curried\op('-')(4);
661 |
662 | // ((2 + 2) * 3) - 4
663 | $res = compose($sub4, $mul3, $add2)(2);
664 | expect($res)->equal(8);
665 | });
666 | });
667 | describe('pad', function() {
668 | docFn(pad::class);
669 | test('Pads an iterable to a specific size', function() {
670 | $res = pad(5, [1,2,3]);
671 | expect(toArray($res))->equal([1,2,3,null,null]);
672 | });
673 | test('Allows custom pad values', function() {
674 | $res = pad(5, [1,2,3], 0);
675 | expect(toArray($res))->equal([1,2,3,0,0]);
676 | });
677 | test('Pads nothing if iterable is the same size as pad size', function() {
678 | $res = pad(5, [1,2,3,4,5]);
679 | expect(toArray($res))->equal([1,2,3,4,5]);
680 | });
681 | test('Pads nothing if iterable is greater than pad size', function() {
682 | $res = pad(5, [1,2,3,4,5,6]);
683 | expect(toArray($res))->equal([1,2,3,4,5,6]);
684 | });
685 | test('Ignores keys of original iterable', function() {
686 | $res = pad(3, ['a' => 1, 'b' => 2]);
687 | expect(toArrayWithKeys($res))->equal([1,2,null]);
688 | });
689 | });
690 | describe('partial', function() {
691 | docFn(partial::class);
692 | test('Partially applies arguments to a function. Given a function signature like f = (a, b, c) -> d, partial(f, a, b) -> (c) -> d', function() {
693 | $fn = function($a, $b, $c) {
694 | return ($a + $b) * $c;
695 | };
696 | $fn = partial($fn, 1, 2); // apply the two arguments (a, b) and return a new function with signature (c) -> d
697 | expect($fn(3))->equal(9);
698 | });
699 | test('You can also use place holders when partially applying', function() {
700 | $fn = function($a, $b, $c) { return ($a + $b) * $c; };
701 |
702 | // _() represents a placeholder for parameter b.
703 | $fn = partial($fn, 1, _(), 3); // create the new func with signature (b) -> d
704 |
705 | expect($fn(2))->equal(9);
706 | });
707 | test('Full partial application also works', function() {
708 | $fn = function($a, $b) { return [$a, $b]; };
709 | $fn = partial($fn, 1, 2);
710 | expect($fn())->equal([1,2]);
711 | });
712 | });
713 | describe('partition', function() {
714 | docFn(partition::class);
715 |
716 | it('Splits an iterable into different arrays based off of a predicate. The predicate should return the index to partition the data into', function() {
717 | list($left, $right) = partition(function($v) {
718 | return $v < 3 ? 0 : 1;
719 | }, [1,2,3,4]);
720 |
721 | expect([$left, $right])->equal([[1,2], [3,4]]);
722 | });
723 | });
724 | describe('pick', function() {
725 | docFn(pick::class);
726 | test('Picks only the given fields from a structured array', function() {
727 | $res = pick(['a', 'b'], [
728 | 'a' => 1,
729 | 'b' => 2,
730 | 'c' => 3,
731 | ]);
732 | expect($res)->equal(['a' => 1, 'b' => 2]);
733 | });
734 | test('Can be used in curried form', function() {
735 | $res = arrayMap(Curried\pick(['id', 'name']), [
736 | ['id' => 1, 'name' => 'Foo', 'slug' => 'foo'],
737 | ['id' => 2, 'name' => 'Bar', 'slug' => 'bar'],
738 | ]);
739 | expect($res)->equal([
740 | ['id' => 1, 'name' => 'Foo'],
741 | ['id' => 2, 'name' => 'Bar'],
742 | ]);
743 | });
744 | });
745 | describe('pickBy', function() {
746 | docFn(pickBy::class);
747 | test('Picks only the fields that match the pick function from a structured array', function() {
748 | $res = pickBy(Curried\spread(function(string $key, int $value): bool {
749 | return $value % 2 === 0;
750 | }), [
751 | 'a' => 1,
752 | 'b' => 2,
753 | 'c' => 3,
754 | ]);
755 | expect($res)->equal(['b' => 2]);
756 | });
757 | });
758 | describe('pipe', function() {
759 | docFn(pipe::class);
760 |
761 | test('Creates a function that pipes values from one func to the next.', function() {
762 | $add3 = Curried\op('+')(3);
763 | $mul2 = Curried\op('*')(2);
764 |
765 | $add3ThenMul2 = pipe($add3, $mul2);
766 | $res = $add3ThenMul2(5);
767 | expect($res)->equal(16);
768 | });
769 | test('Allows an empty initial argument', function() {
770 | $res = pipe(
771 | function() { yield from [1,2,3]; },
772 | Curried\reduce(function($acc, $v) { return $acc + $v; }, 0)
773 | )();
774 | expect($res)->equal(6);
775 | });
776 |
777 | docOutro('`pipe` and `compose` are sister functions and do the same thing except the functions are composed in reverse order. pipe(f, g)(x) = g(f(x))');
778 | });
779 | describe('product', function() {
780 | docFn(product::class);
781 |
782 | test('Creates a cartesian product of multiple sets', function() {
783 | $res = product([1,2], [3,4], [5, 6]);
784 | expect(toArray($res))->equal([
785 | [1,3,5],
786 | [1,3,6],
787 | [1,4,5],
788 | [1,4,6],
789 | [2,3,5],
790 | [2,3,6],
791 | [2,4,5],
792 | [2,4,6],
793 | ]);
794 | });
795 | });
796 | describe('prop', function() {
797 | docFn(prop::class);
798 |
799 | test('Accesses a property from an object', function() {
800 | $obj = new \StdClass();
801 | $obj->id = 1;
802 | $res = prop('id', $obj);
803 | expect($res)->equal(1);
804 | });
805 | test('If no property exists, it will return the $else value', function() {
806 | $obj = new \StdClass();
807 | $res = prop('id', $obj, 2);
808 | expect($res)->equal(2);
809 | });
810 | });
811 | describe('propIn', function() {
812 | docFn(propIn::class);
813 |
814 | test('Accesses a property deep in an object tree', function() {
815 | $obj = new \StdClass();
816 | $obj->id = 1;
817 | $obj->child = new \StdClass();
818 | $obj->child->id = 2;
819 | $res = propIn(['child', 'id'], $obj);
820 | expect($res)->equal(2);
821 | });
822 | test('If any property is missing in the tree, it will return the $else value', function() {
823 | $obj = new \StdClass();
824 | $obj->id = 1;
825 | $obj->child = new \StdClass();
826 | $res = propIn(['child', 'id'], $obj, 3);
827 | expect($res)->equal(3);
828 | });
829 | });
830 | describe('range', function() {
831 | docFn(range::class);
832 | test('Creates an iterable of a range of values starting from $start going to $end inclusively incrementing by $step', function() {
833 | $res = range(1, 3);
834 | expect(toArray($res))->equal([1,2,3]);
835 | });
836 | test('It also allows a decreasing range', function() {
837 | $res = range(3, 1);
838 | expect(toArray($res))->equal([3,2,1]);
839 | });
840 | test('An exception will be thrown if the $step provided goes in the wrong direction', function() {
841 | expect(function() {
842 | toArray(range(1, 2, -1));
843 | })->throw(\InvalidArgumentException::class);
844 | expect(function() {
845 | toArray(range(2, 1, 1));
846 | })->throw(\InvalidArgumentException::class);
847 | });
848 | });
849 | describe('reduce', function() {
850 | docFn(reduce::class);
851 | test('Reduces an iterable into a single value', function() {
852 | $res = reduce(function($acc, $v) {
853 | return $acc + $v;
854 | }, range(1,3), 0);
855 | expect($res)->equal(6);
856 | });
857 | });
858 | describe('reduceKeyValue', function() {
859 | docFn(reduceKeyValue::class);
860 | test('Reduces an iterables key value pairs into a value', function() {
861 | $res = reduceKeyValue(function($acc, $kv) {
862 | [$key, $value] = $kv;
863 | return $acc . $key . $value;
864 | }, fromPairs([['a', 1], ['b', 2]]), "");
865 | expect($res)->equal("a1b2");
866 | });
867 | });
868 | describe('reindex', function() {
869 | docFn(reindex::class);
870 | test('Re-indexes a collection via a callable', function() {
871 | $res = reindex(function($v) {
872 | return $v['id'];
873 | }, [['id' => 2], ['id' => 3], ['id' => 1]]);
874 |
875 | expect(toArrayWithKeys($res))->equal([
876 | 2 => ['id' => 2],
877 | 3 => ['id' => 3],
878 | 1 => ['id' => 1],
879 | ]);
880 | });
881 | });
882 | describe('retry', function() {
883 | docFn(retry::class);
884 | test('Executes a function and retries if an exception is thrown', function() {
885 | $i = 0;
886 | $res = retry(function() use (&$i) {
887 | $i += 1;
888 | if ($i <= 1) {
889 | throw new \Exception('bad');
890 | }
891 |
892 | return $i;
893 | });
894 |
895 | expect($res)->equal(2);
896 | });
897 | test('Only retries $maxTries times else it gives up and bubbles the exception', function() {
898 | expect(function() {
899 | $i = 0;
900 | retry(function() use (&$i) {
901 | $i += 1;
902 | throw new \Exception((string) $i);
903 | }, 5);
904 | })->throw('Exception', '6');
905 | });
906 | test('Retries until $shouldRetry returns false', function() {
907 | $i = 0;
908 |
909 | expect(function() {
910 | $res = retry(function() use (&$i) {
911 | $i += 1;
912 | throw new \Exception((string) $i);
913 | }, function($numRetries, \Throwable $t = null) {
914 | return $numRetries < 2;
915 | });
916 | })->throw('Exception', '2');
917 | });
918 | test("Sends numRetries into the main fn", function() {
919 | $res = retry(function($numRetries) {
920 | if (!$numRetries) {
921 | throw new Exception('bad');
922 | }
923 |
924 | return $numRetries;
925 | }, 2);
926 | expect($res)->equal(1);
927 | });
928 | docOutro('Keep in mind that maxTries determines the number of *re*-tries. This means the function will execute maxTries + 1 times since the first invocation is not a retry.');
929 | });
930 | describe('search', function() {
931 | docFn(search::class);
932 | test('Searches for an element in a collection where the callable returns true', function() {
933 | $res = search(function($v) {
934 | return $v['id'] == 2;
935 | }, [['id' => 1], ['id' => 2], ['id' => 3]]);
936 | expect($res)->equal(['id' => 2]);
937 | });
938 | test('Returns null if no element was found', function() {
939 | $res = search(function($v) {
940 | return false;
941 | }, [['id' => 1], ['id' => 2], ['id' => 3]]);
942 | expect($res)->equal(null);
943 | });
944 | });
945 | describe('setIndex', function() {
946 | docFn(setIndex::class);
947 | test('Sets an index in an array', function() {
948 | $res = setIndex('a', 1, []);
949 | expect($res['a'])->equal(1);
950 | });
951 | });
952 | describe('setIndexIn', function() {
953 | docFn(setIndexIn::class);
954 | test('Sets a nested index in an array', function() {
955 | $res = setIndexIn(['a', 'b'], 1, ['a' => []]);
956 | expect($res['a']['b'])->equal(1);
957 | });
958 | });
959 | describe('setProp', function() {
960 | docFn(setProp::class);
961 | test('Sets a property in an object', function() {
962 | $res = setProp('a', 1, (object) []);
963 | expect($res->a)->equal(1);
964 | });
965 | });
966 | describe('slice', function() {
967 | docFn(slice::class);
968 | test('It takes an inclusive slice from start to a given length of an interable', function() {
969 | $sliced = slice(1, range(0, 4), 2);
970 | expect(toArray($sliced))->equal([1, 2]);
971 | });
972 | test('If length is not supplied it default to the end of the iterable', function() {
973 | $sliced = slice(2, range(0, 4));
974 | expect(toArray($sliced))->equal([2,3,4]);
975 | });
976 | test('will not consume the iterator once the slice has been yielded', function() {
977 | $i = 0;
978 | $gen = function() use (&$i) {
979 | foreach (range(0, 4) as $v) {
980 | $i = $v;
981 | yield $i;
982 | }
983 | };
984 | $sliced = toArray(slice(1, $gen(), 2));
985 | expect($sliced)->equal([1, 2]);
986 | expect($i)->equal(2);
987 | });
988 | });
989 | describe('sortFromArray', function() {
990 | docFn(sortFromArray::class);
991 | test("Sort an iterable with a given array of ordered elements to sort by", function() {
992 | $data = [
993 | ['id' => 1, 'name' => 'A'],
994 | ['id' => 2, 'name' => 'B'],
995 | ['id' => 3, 'name' => 'C'],
996 | ];
997 | $res = sortFromArray(Curried\index('id'), [2,3,1], $data);
998 | expect(arrayMap(Curried\index('name'), $res))->equal(['B', 'C', 'A']);
999 | });
1000 | test('Throws an exception if any item in the iterable is not within the orderedElements', function() {
1001 | expect(function() {
1002 | $data = [['id' => 1]];
1003 | $res = sortFromArray(Curried\index('id'), [], $data);
1004 | })->throw(\LogicException::class, 'Cannot sort element key 1 because it does not exist in the ordered elements.');
1005 | });
1006 |
1007 | docOutro("I've found this to be very useful when you fetch records from a database with a WHERE IN clause, and you need to make sure the results are in the same order as the ids in the WHERE IN clause.");
1008 | });
1009 | describe('spread', function() {
1010 | docFn(spread::class);
1011 | test("Spreads an array of arguments to a callable", function() {
1012 | $res = spread(function($a, $b) {
1013 | return $a . $b;
1014 | }, ['a', 'b']);
1015 | expect($res)->equal('ab');
1016 | });
1017 | test('Can be used in the curried form to unpack tuple arguments', function() {
1018 | $res = arrayMap(Curried\spread(function(string $first, int $second) {
1019 | return $first . $second;
1020 | }), [['a', 1], ['b', 2]]);
1021 | expect($res)->equal(['a1', 'b2']);
1022 | });
1023 | docOutro("Note: this is basically just an alias for `call_user_func_array` or simply a functional wrapper around the `...` (spread) operator.");
1024 | });
1025 | describe('take', function() {
1026 | docFn(take::class);
1027 | test('Takes the first num items from an iterable', function() {
1028 | $res = take(2, range(0, 10));
1029 | expect(toArray($res))->equal([0, 1]);
1030 | });
1031 | });
1032 | describe('takeWhile', function() {
1033 | docFn(takeWhile::class);
1034 | test('Takes elements from an iterable while the $predicate returns true', function() {
1035 | $res = takeWhile(Curried\op('>')(0), [2, 1, 0, 1, 2]);
1036 | expect(toArray($res))->equal([2,1]);
1037 | });
1038 | });
1039 | describe('tap', function() {
1040 | docFn(tap::class);
1041 | it('Calls given tap function on value and returns value', function() {
1042 | $loggedValues = [];
1043 | $res = tap(function(string $v) use (&$loggedValues) {
1044 | $loggedValues[] = $v;
1045 | }, 'abc');
1046 | expect([$loggedValues[0], $res])->equal(['abc', 'abc']);
1047 | });
1048 | docOutro('`tap` is useful anytime you need to operate on a value and do not want to modify the return value.');
1049 | });
1050 | describe('throwIf', function() {
1051 | docFn(throwIf::class);
1052 | it('Throws the given exception if value given evaluates to true', function() {
1053 | expect(function() {
1054 | throwIf(
1055 | function(int $value) { return new RuntimeException('Error: ' . $value); },
1056 | function(int $value) { return $value === 0; },
1057 | 0
1058 | );
1059 | })->throw(RuntimeException::class, 'Error: 0');
1060 | });
1061 | it('Returns given value if value evaluates to false', function() {
1062 | $res = throwIf(
1063 | function(int $value) { return new RuntimeException('Error: ' . $value); },
1064 | function(int $value) { return $value === 0; },
1065 | 1
1066 | );
1067 | expect($res)->equal(1);
1068 | });
1069 | docOutro('Note: works best with short closures!');
1070 | });
1071 | describe('toArray', function() {
1072 | docFn(toArray::class);
1073 | it('will tranform any iterable into an array', function() {
1074 | $res = toArray((function() { yield 1; yield 2; yield 3; })());
1075 | expect($res)->equal([1,2,3]);
1076 | });
1077 | it('can also be used as a constant', function() {
1078 | $res = compose(toArray, id)((function() {yield 1; yield 2; yield 3;})());
1079 | expect($res)->equal([1,2,3]);
1080 | });
1081 | });
1082 | describe('toArrayWithKeys', function() {
1083 | docFn(toArrayWithKeys::class);
1084 | it('can convert to an array and keep the keys', function() {
1085 | $gen = function() { yield 'a' => 1; yield 'b' => 2; };
1086 | expect(toArrayWithKeys($gen()))->equal(['a' => 1, 'b' => 2]);
1087 | });
1088 | });
1089 | describe('toPairs', function() {
1090 | docFn(toPairs::class);
1091 | test('Transforms an associative array into an iterable of tuples [$key, $value]', function() {
1092 | $res = toPairs([
1093 | 'a' => 1,
1094 | 'b' => 2,
1095 | ]);
1096 | expect(toArray($res))->equal([
1097 | ['a', 1],
1098 | ['b', 2]
1099 | ]);
1100 | });
1101 | });
1102 | describe('updateIndexIn', function() {
1103 | docFn(updateIndexIn::class);
1104 |
1105 | test('Updates a nested element within a deep array structure', function() {
1106 | $data = ['a' => ['b' => ['c' => 3]]];
1107 |
1108 | $data = updateIndexIn(['a', 'b', 'c'], function($v) {
1109 | return $v * $v;
1110 | }, $data);
1111 |
1112 | expect($data)->equal(['a' => ['b' => ['c' => 9]]]);
1113 | });
1114 | test('Throws an exception if nested key does not exist', function() {
1115 | expect(function() {
1116 | $data = ['a' => ['b' => ['c' => 9]]];
1117 | updateIndexIn(['a', 'c', 'c'], function() {}, $data);
1118 | })->throw(\RuntimeException::class, 'Could not updateIn because the keys a -> c -> c could not be found.');
1119 | });
1120 | });
1121 | describe('values', function() {
1122 | docFn(values::class);
1123 | test('Exports only the values of an iterable', function() {
1124 | $res = values(['a' => 1, 'b' => 2]);
1125 | expect(toArrayWithKeys($res))->equal([1,2]);
1126 | });
1127 | });
1128 | describe('when', function() {
1129 | docFn(when::class);
1130 | it('Evaluates the given value with the $then callable if the predicate returns true', function() {
1131 | $if = function($v) { return $v == 3; };
1132 | $then = function($v) { return $v * $v; };
1133 | $res = when($if, $then, 3);
1134 | expect($res)->equal(9);
1135 | });
1136 | test('But will return the given value if the predicate returns false', function() {
1137 | $if = function($v) { return $v == 3; };
1138 | $then = function($v) { return $v * $v; };
1139 | $res = when($if, $then, 4);
1140 | expect($res)->equal(4);
1141 | });
1142 | });
1143 | describe('withState', function() {
1144 | docFn(withState::class);
1145 |
1146 | test('Decorate a function with accumulating state', function() {
1147 | $fn = withState(function($state, $v) {
1148 | return [$state + 1, $state . ': ' . $v];
1149 | }, 1);
1150 | $res = arrayMap($fn, iter('abcd'));
1151 | expect($res)->equal(['1: a', '2: b', '3: c', '4: d']);
1152 | });
1153 | });
1154 | describe('within', function() {
1155 | docFn(within::class);
1156 |
1157 | test('Only allows keys within the given array to stay', function() {
1158 | $data = flip(iter('abcd'));
1159 | $res = within(['a', 'c'], $data);
1160 | expect(toArrayWithKeys($res))->equal(['a' => 0, 'c' => 2]);
1161 | });
1162 | });
1163 | describe('without', function() {
1164 | docFn(without::class);
1165 |
1166 | test('Filters an iterable to be without the given keys', function() {
1167 | $data = flip(iter('abcd'));
1168 | $res = without(['a', 'c'], $data);
1169 | expect(toArrayWithKeys($res))->equal(['b' => 1, 'd' => 3]);
1170 | });
1171 | });
1172 | describe('zip', function() {
1173 | docFn(zip::class);
1174 | test('Zips multiple iterables into an iterable n-tuples', function() {
1175 | $res = zip(iter('abc'), range(1,3), [4,5,6]);
1176 | expect(toArray($res))->equal([
1177 | ['a', 1, 4],
1178 | ['b', 2, 5],
1179 | ['c', 3, 6],
1180 | ]);
1181 | });
1182 | test('Returns an empty iterable if no iters are present', function() {
1183 | expect(toArray(zip()))->equal([]);
1184 | });
1185 | });
1186 | });
1187 |
--------------------------------------------------------------------------------