2017 © Pedro Peláez
 

library fn

Functional library for php with proper currying

image

krak/fn

Functional library for php with proper currying

  • Wednesday, June 27, 2018
  • by ragboyjr
  • Repository
  • 1 Watchers
  • 0 Stars
  • 1,177 Installations
  • PHP
  • 1 Dependents
  • 0 Suggesters
  • 0 Forks
  • 0 Open issues
  • 20 Versions
  • 57 % Grown

The README.md

Fun

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., (*1)

Installation

Install with composer at krak/fn, (*2)

Usage

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., (*3)

<?php

use function Krak\Fun\Curried\{filter, map, op};
use function Krak\Fun\{compose};
use const Krak\Fun\Consts\{toArray};

$res = compose(
    toArray,
    map(op('*')(3)),
    filter(op('>')(2))
)([1,2,3,4]);
assert($res == [9, 12]);

Check the src/fn.php for examples of all the functions., (*4)

Fun API

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., (*5)

<?php

use Krak\Fun\{f, c};

$res = f\compose(
    c\toArray,
    c\map(c\op('*')(3)),
    c\filter(c\op('>')(2))
)([1,2,3,4]);
assert($res == [9, 12]);

The f namespace holds the standard functions basically copied over verbatim from the Krak\Fun namespace., (*6)

The c namespace contains all of the curried functions and constant definitions., (*7)

One great way to use the consts is with compose or pipe chains:, (*8)

use Krak\Fun\{f, c};

$res = f\compose(
    c\toArray,
    c\map(function($tup) {
        return $tup[0] + $tup[1];
    }),
    c\toPairs
)([1,2,3]);
// $res == [1, 3, 5]

Constants

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., (*9)

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., (*10)

<?php

use Krak\Fun\{f, c};

function getArrayRange(callable $toArray): array {
    $toArray(f\range(1,3));
}

getArrayRange(c\toArray);

The above is valid php and will work because c\toArray resolves to Krak\\Fun\\toArray which php will treat as a valid callable., (*11)

This is great for compose chains and partial application:, (*12)

use Krak\Fun\{f, c};

$res = f\compose(
    c\toArray,
    map(partial(c\op, '*', 3))
)([1,2,3]);
assert($res == [3,6,9]);

The op function is defined as op($operator, $b, $a). Essentially, what we did was call: partial('Krak\\Fun\\op', '*', 3)., (*13)

Currying

All functions that are curryable have generated curry functions. A function is curryable if it has more than one required argument or one required argument with any number of optional arguments., (*14)

These function definitions aren't curryable:, (*15)

func()
func($arg1)
func($oarg = null, $oarg1 = null)

These are:, (*16)

func($arg1, $arg2)
func($arg1, $oarg = null)

Given a function definition like:, (*17)

(a, b, c = null) -> Void

the curried verison would look like:, (*18)

(a, c = null) -> (b) -> Void

Debugging

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:, (*19)

  1. Debug a single value:, (*20)

    f\compose(
        function() {}, // do something else
        c\dd(), // debug result here
        function() {}, // do another thing that returns a single value
        function() {} // do something
    );
    
  2. Debug an iterable:, (*21)

    f\compose(
        function() {}, // do something else
        c\dd(), c\toArray, // debug result here
        c\map(function() {}) // do something
    );
    

Using Compose Chains for Readable Code

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., (*22)


use Krak\Fun\{f, c}; /** * Fetches orders based off of the arguments, filters data, and imports to database */ final class ImportOrdersFromApi { public function __construct(ApiClient $client, OrderRepository $orderRepository, SaveOrders $saveOrders) { // ... } public function __invoke(ImportOrderRequest $req): void { f\compose( $this->persistApiOrders(), $this->removeAlreadyImportedOrders(), $this->fetchOrdersFromApi() )($req); } private function persistApiOrders(): callable { // important that this is an c\each so that it will consume the iterable chain return f\compose( c\each($this->saveOrders), // saveOrders has a signature of `__invoke(iterable Order[]) => void` c\chunk(50), // chunk to persist many at once c\map(function(array $apiOrder) { return Order::createFromApiData($apiOrder); }) ); } private function removeAlreadyImportedOrders(): callable { return f\compose( c\flatMap(function(array $apiOrders) { $apiOrderIds = array_column($apiOrders, 'order_id'); /** array of order id => order entity */ $orders = $this->orderRepository->findByApiOrderIds($ids); return f\filter(function(array $apiOrder) use ($orders) { return !array_key_exists($apiOrder['order_id'], $orders); }, $apiOrders); }), // chunk by 50 to save on database requests c\chunk(50) ); } /** Returns an iterable of api orders */ private function fetchOrdersFromApi(): callable { return function(ImportOrderRequest $req) { yield from $this->apiClient->fetch(/* pass in req args */); }; } }

Docs

Docs are generated with make docs. This uses Krak Peridocs to actually generate the documentation from the peridot tests., (*23)

Code Generation

The constants and curried functions are generated with make code., (*24)

Tests

Tests are run via make test and are stored in the test directory. We use peridot for testing., (*25)

API

all any arrayCompact arrayFilter arrayMap arrayReindex
arrayWrap assign chain chunk chunkBy compact
compose construct curry differenceWith dd drop
dropWhile each filter filterKeys flatMap flatten
flip fromPairs groupBy hasIndexIn head inArray
index indexIn indexOf isNull iter join
keys map mapAccum mapKeys mapKeyValue mapOn
nullable onEach op pad partial partition
pick pickBy pipe product prop propIn
range reduce reduceKeyValue reindex retry search
setIndex setIndexIn setProp slice sortFromArray spread
take takeWhile tap throwIf toArray toArrayWithKeys
toPairs updateIndexIn values when withState within
without zip

all(callable $predicate, iterable $iter): bool

Name: Krak\Fun\all, (*26)

Returns true if the predicate returns true on all of the items:, (*27)

$res = all(function ($v) {
    return $v % 2 == 0;
}, [2, 4, 6]);
expect($res)->equal(true);

Returns false if the predicate returns false on any of the items:, (*28)

$res = all(function ($v) {
    return $v % 2 == 0;
}, [1, 2, 4, 6]);
expect($res)->equal(false);

any(callable $predicate, iterable $iter): bool

Name: Krak\Fun\any, (*29)

Returns true if the predicate returns true on any of the items:, (*30)

$res = any(function ($v) {
    return $v % 2 == 0;
}, [1, 3, 4, 5]);
expect($res)->equal(true);

Returns false if the predicate returns false on all of the items:, (*31)

$res = any(function ($v) {
    return $v % 2 == 0;
}, [1, 3, 5]);
expect($res)->equal(false);

arrayCompact(iterable $iter): array

Name: Krak\Fun\arrayCompact, (*32)

It will remove all nulls from an iterable and return an array:, (*33)

$res = arrayCompact([1, 2, null, null, 3]);
expect(\array_values($res))->equal([1, 2, 3]);

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., (*34)

arrayFilter(callable $fn, iterable $data): array

Name: Krak\Fun\arrayFilter, (*35)

Alias of array_filter:, (*36)

$res = arrayFilter(partial(op, '<', 2), [1, 2, 3]);
expect($res)->equal([1]);

Filters iterables as well as arrays:, (*37)

$res = arrayFilter(partial(op, '<', 2), range(1, 3));
expect($res)->equal([1]);

arrayMap(callable $fn, iterable $data): array

Name: Krak\Fun\arrayMap, (*38)

Alias of array_map:, (*39)

$res = arrayMap(partial(op, '*', 2), [1, 2, 3]);
expect($res)->equal([2, 4, 6]);

Maps iterables as well as arrays:, (*40)

$res = arrayMap(partial(op, '*', 2), range(1, 3));
expect($res)->equal([2, 4, 6]);

arrayReindex(callable $fn, iterable $iter): array

Name: Krak\Fun\arrayReindex, (*41)

Re-indexes a collection via a callable into an associative array:, (*42)

$res = arrayReindex(function ($v) {
    return $v['id'];
}, [['id' => 2], ['id' => 3], ['id' => 1]]);
expect($res)->equal([2 => ['id' => 2], 3 => ['id' => 3], 1 => ['id' => 1]]);

arrayWrap($value)

Name: Krak\Fun\arrayWrap, (*43)

Wraps any non list array into an array:, (*44)

$results = arrayMap(arrayWrap, [1, 'abc', ['a' => 1]]);
expect($results)->equal([[1], ['abc'], [['a' => 1]]]);

List based arrays are left as is:, (*45)

$results = arrayMap(arrayWrap, [[], [1, 2, 3]]);
expect($results)->equal([[], [1, 2, 3]]);

Note: array_is_list which requires php 8.1 or symfony/polyfill-php81, (*46)

assign($obj, iterable $iter)

Name: Krak\Fun\assign, (*47)

Assigns iterable keys and values to an object:, (*48)

$obj = new \StdClass();
$obj = assign($obj, ['a' => 1, 'b' => 2]);
expect($obj->a)->equal(1);
expect($obj->b)->equal(2);

chain(iterable ...$iters)

Name: Krak\Fun\chain, (*49)

Chains iterables together into one iterable:, (*50)

$res = chain([1], range(2, 3));
expect(toArray($res))->equal([1, 2, 3]);

chunk(int $size, iterable $iter): iterable

Name: Krak\Fun\chunk, (*51)

Chunks an iterable into equal sized chunks.:, (*52)

$res = chunk(2, [1, 2, 3, 4]);
expect(toArray($res))->equal([[1, 2], [3, 4]]);

If there is any remainder, it is yielded as is:, (*53)

$res = chunk(3, [1, 2, 3, 4]);
expect(toArray($res))->equal([[1, 2, 3], [4]]);

chunkBy(callable $fn, iterable $iter, ?int $maxSize = null): iterable

Name: Krak\Fun\chunkBy, (*54)

Chunks items together off of the result from the callable:, (*55)

$items = ['aa', 'ab', 'ac', 'ba', 'bb', 'bc', 'ca', 'cb', 'cc'];
$chunks = chunkBy(function (string $item) {
    return $item[0];
    // return first char
}, $items);
expect(toArray($chunks))->equal([['aa', 'ab', 'ac'], ['ba', 'bb', 'bc'], ['ca', 'cb', 'cc']]);

Allows a maxSize to prevent chunks from exceeding a limit:, (*56)

$items = ['aa', 'ab', 'ac', 'ba', 'bb', 'bc', 'ca', 'cb', 'cc'];
$chunks = chunkBy(function (string $item) {
    return $item[0];
    // return first char
}, $items, 2);
expect(toArray($chunks))->equal([['aa', 'ab'], ['ac'], ['ba', 'bb'], ['bc'], ['ca', 'cb'], ['cc']]);

compact(iterable $iter): iterable

Name: Krak\Fun\compact, (*57)

Removes all null values from an iterable:, (*58)

$res = compact([1, null, 2, 3, null, null, 4]);
expect(toArray($res))->equal([1, 2, 3, 4]);

compose(callable ...$fns)

Name: Krak\Fun\compose, (*59)

Composes functions together. compose(f, g)(x) == f(g(x)):, (*60)

$mul2 = Curried\op('*')(2);
$add3 = Curried\op('+')(3);
$add3ThenMul2 = compose($mul2, $add3);
$res = $add3ThenMul2(5);
expect($res)->equal(16);

Allows an empty initial argument:, (*61)

$res = compose(Curried\reduce(function ($acc, $v) {
    return $acc + $v;
}, 0), function () {
    yield from [1, 2, 3];
})();
expect($res)->equal(6);

construct($className, ...$args)

Name: Krak\Fun\construct, (*62)

Constructs (instantiates) a new class with the given arguments:, (*63)

$res = construct(\ArrayObject::class, [1, 2, 3]);
expect($res->count())->equal(3);

curry(callable $fn, int $num = 1)

Name: Krak\Fun\curry, (*64)

currys the given function $n times:, (*65)

$res = curry(_idArgs::class, 2)(1)(2)(3);
expect($res)->equal([1, 2, 3]);

Given a function definition: (a, b) -> c. A curried version will look like (a) -> (b) -> c, (*66)

differenceWith(callable $cmp, iterable $a, iterable $b)

Name: Krak\Fun\differenceWith, (*67)

Takes the difference between two iterables with a given comparator:, (*68)

$res = differenceWith(partial(op, '==='), [1, 2, 3, 4, 5], [2, 3, 4]);
expect(toArray($res))->equal([1, 5]);

dd($value, callable $dump = null, callable $die = null)

Name: Krak\Fun\dd, (*69)

dumps and dies:, (*70)

$res = null;
$died = false;
$dump = function ($v) use(&$res) {
    $res = $v;
};
$die = function () use(&$died) {
    $died = true;
};
dd(1, $dump, $die);
expect($res)->equal(1);
expect($died)->equal(true);

drop(int $num, iterable $iter): iterable

Name: Krak\Fun\drop, (*71)

Drops the first num items from an iterable:, (*72)

$res = drop(2, range(0, 3));
expect(toArray($res))->equal([2, 3]);

dropWhile(callable $predicate, iterable $iter): iterable

Name: Krak\Fun\dropWhile, (*73)

Drops elements from the iterable while the predicate returns true:, (*74)

$res = dropWhile(Curried\op('>')(0), [2, 1, 0, 1, 2]);
expect(toArray($res))->equal([0, 1, 2]);

each(callable $handle, iterable $iter)

Name: Krak\Fun\each, (*75)

Invokes a callable on each item in an iterable:, (*76)

$state = [(object) ['id' => 1], (object) ['id' => 2]];
each(function ($item) {
    $item->id += 1;
}, $state);
expect([$state[0]->id, $state[1]->id])->equal([2, 3]);

Normally using php foreach should suffice for iterating over an iterable; however, php variables in foreach loops are not scoped whereas closures are., (*77)

filter(callable $predicate, iterable $iter): iterable

Name: Krak\Fun\filter, (*78)

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:, (*79)

$values = filter(partial(op, '>', 2), [1, 2, 3, 4]);
// keep all items that are greater than 2
expect(toArray($values))->equal([3, 4]);

filterKeys(callable $predicate, iterable $iter): iterable

Name: Krak\Fun\filterKeys, (*80)

Filters an iterable off of the keys:, (*81)

$res = filterKeys(Curried\inArray(['a', 'b']), ['a' => 1, 'b' => 2, 'c' => 3]);
expect(toArrayWithKeys($res))->equal(['a' => 1, 'b' => 2]);

flatMap(callable $map, iterable $iter): iterable

Name: Krak\Fun\flatMap, (*82)

Maps and then flattens an iterable:, (*83)

$res = flatMap(function ($v) {
    return [-$v, $v];
}, range(1, 3));
expect(toArray($res))->equal([-1, 1, -2, 2, -3, 3]);

flatMap is perfect for when you want to map an iterable and also add elements to the resulting iterable., (*84)

flatten(iterable $iter, $levels = INF): iterable

Name: Krak\Fun\flatten, (*85)

Flattens nested iterables into a flattened set of elements:, (*86)

$res = flatten([1, [2, [3, [4]]]]);
expect(toArray($res))->equal([1, 2, 3, 4]);

Can flatten a specific number of levels:, (*87)

$res = flatten([1, [2, [3]]], 1);
expect(toArray($res))->equal([1, 2, [3]]);

Flattening zero levels does nothing:, (*88)

$res = flatten([1, [2]], 0);
expect(toArray($res))->equal([1, [2]]);

flip(iterable $iter): iterable

Name: Krak\Fun\flip, (*89)

Flips the keys => values of an iterable to values => keys:, (*90)

$res = flip(['a' => 0, 'b' => 1]);
expect(toArray($res))->equal(['a', 'b']);

fromPairs(iterable $iter): iterable

Name: Krak\Fun\fromPairs, (*91)

Converts an iterable of tuples [$key, $value] into an associative iterable:, (*92)

$res = fromPairs([['a', 1], ['b', 2]]);
expect(toArrayWithKeys($res))->equal(['a' => 1, 'b' => 2]);

groupBy(callable $fn, iterable $iter, ?int $maxSize = null): iterable

Name: Krak\Fun\groupBy, (*93)

Alias of chunkBy, (*94)

Groups items together off of the result from the callable:, (*95)

$items = ['aa', 'ab', 'ac', 'ba', 'bb', 'bc', 'ca', 'cb', 'cc'];
$groupedItems = groupBy(function (string $item) {
    return $item[0];
    // return first char
}, $items);
expect(toArray($groupedItems))->equal([['aa', 'ab', 'ac'], ['ba', 'bb', 'bc'], ['ca', 'cb', 'cc']]);

Allows a maxSize to prevent groups from exceeding a limit:, (*96)

$items = ['aa', 'ab', 'ac', 'ba', 'bb', 'bc', 'ca', 'cb', 'cc'];
$groupedItems = groupBy(function (string $item) {
    return $item[0];
    // return first char
}, $items, 2);
expect(toArray($groupedItems))->equal([['aa', 'ab'], ['ac'], ['ba', 'bb'], ['bc'], ['ca', 'cb'], ['cc']]);

hasIndexIn(array $keys, array $data): bool

Name: Krak\Fun\hasIndexIn, (*97)

Checks if a nested index exists in the given data:, (*98)

$res = hasIndexIn(['a', 'b', 'c'], ['a' => ['b' => ['c' => null]]]);
expect($res)->equal(true);

Returns false if any of the indexes do not exist in the data:, (*99)

$res = hasIndexIn(['a', 'b', 'c'], ['a' => ['b' => []]]);
expect($res)->equal(false);

head(iterable $iter)

Name: Krak\Fun\head, (*100)

Returns the fist element in an iterable:, (*101)

$res = head([1, 2, 3]);
expect($res)->equal(1);

But returns null if the iterable is empty:, (*102)

$res = head([]);
expect($res)->equal(null);

inArray(array $set, $item): bool

Name: Krak\Fun\inArray, (*103)

Checks if an item is within an array of items:, (*104)

$res = inArray([1, 2, 3], 2);
expect($res)->equal(true);

index($key, $data, $else = null)

Name: Krak\Fun\index, (*105)

Accesses an index in an array:, (*106)

$res = index('a', ['a' => 1]);
expect($res)->equal(1);

If no value exists at the given index, $else will be returned:, (*107)

$res = index('a', ['b' => 1], 2);
expect($res)->equal(2);

Also works with objects that implement ArrayAccess:, (*108)

class MyClass implements \ArrayAccess
{
    private $container = [];
    public function __construct()
    {
        $this->container = ['one' => 1, 'two' => 2];
    }
    public function offsetExists($offset)
    {
        return isset($this->container[$offset]);
    }
    public function offsetGet($offset)
    {
        return isset($this->container[$offset]) ? $this->container[$offset] : null;
    }
    public function offsetSet($offset, $value)
    {
        /* ... */
    }
    public function offsetUnset($offset)
    {
        /* ... */
    }
}
$object = new MyClass();
expect(index('two', $object))->equal(2);
expect(index('three', $object, 'else'))->equal('else');

indexIn(array $keys, array $data, $else = null)

Name: Krak\Fun\indexIn, (*109)

Accesses a nested index in a deep array structure:, (*110)

$res = indexIn(['a', 'b'], ['a' => ['b' => 1]]);
expect($res)->equal(1);

If any of the indexes do not exist, $else will be returned:, (*111)

$res = indexIn(['a', 'b'], ['a' => ['c' => 1]], 2);
expect($res)->equal(2);

indexOf(callable $predicate, iterable $iter)

Name: Krak\Fun\indexOf, (*112)

Searches for an element and returns the key if found:, (*113)

$res = indexOf(partial(op, '==', 'b'), ['a', 'b', 'c']);
expect($res)->equal(1);

isNull($val)

Name: Krak\Fun\isNull, (*114)

alias for is_null:, (*115)

expect(isNull(null))->equal(true);
expect(isNull(0))->equal(false);

iter($iter): \Iterator

Name: Krak\Fun\iter, (*116)

Converts any iterable into a proper instance of Iterator., (*117)

Can convert arrays:, (*118)

expect(iter([1, 2, 3]))->instanceof('Iterator');

Can convert an Iterator:, (*119)

expect(iter(new \ArrayIterator([1, 2, 3])))->instanceof('Iterator');

Can convert objects:, (*120)

$obj = (object) ['a' => 1, 'b' => 2];
expect(iter($obj))->instanceof('Iterator');
expect(toArrayWithKeys(iter($obj)))->equal(['a' => 1, 'b' => 2]);

Can convert any iterable:, (*121)

$a = new class implements \IteratorAggregate
{
    public function getIterator()
    {
        return new \ArrayIterator([1, 2, 3]);
    }
};
expect(iter($a))->instanceof('Iterator');
expect(toArray(iter($a)))->equal([1, 2, 3]);

Can convert strings:, (*122)

expect(iter('abc'))->instanceof('Iterator');
expect(toArray(iter('abc')))->equal(['a', 'b', 'c']);

Will throw an exception otherwise:, (*123)

expect(function () {
    iter(1);
})->throw('LogicException', 'Iter could not be converted into an iterable.');

join(string $sep, iterable $iter)

Name: Krak\Fun\join, (*124)

Joins an iterable with a given separator:, (*125)

$res = join(",", range(1, 3));
expect($res)->equal("1,2,3");

keys(iterable $iter): iterable

Name: Krak\Fun\keys, (*126)

Yields only the keys of an in iterable:, (*127)

$keys = keys(['a' => 1, 'b' => 2]);
expect(toArray($keys))->equal(['a', 'b']);

map(callable $predicate, iterable $iter): iterable

Name: Krak\Fun\map, (*128)

Lazily maps an iterable's values to a different set:, (*129)

$values = map(partial(op, '*', 2), [1, 2, 3, 4]);
expect(toArray($values))->equal([2, 4, 6, 8]);

mapAccum(callable $fn, iterable $iter, $acc = null)

Name: Krak\Fun\mapAccum, (*130)

Maps a function to each element of a list while passing in an accumulator to accumulate over every iteration:, (*131)

$data = iter('abcd');
[$totalSort, $values] = mapAccum(function ($acc, $value) {
    return [$acc + 1, ['name' => $value, 'sort' => $acc]];
}, iter('abcd'), 0);
expect($totalSort)->equal(4);
expect($values)->equal([['name' => 'a', 'sort' => 0], ['name' => 'b', 'sort' => 1], ['name' => 'c', 'sort' => 2], ['name' => 'd', 'sort' => 3]]);

Note: mapAccum converts the interable into an array and is not lazy like most of the other functions in this library, (*132)

mapKeys(callable $predicate, iterable $iter): iterable

Name: Krak\Fun\mapKeys, (*133)

Lazily maps an iterable's keys to a different set:, (*134)

$keys = mapKeys(partial(op, '.', '_'), ['a' => 1, 'b' => 2]);
expect(toArrayWithKeys($keys))->equal(['a_' => 1, 'b_' => 2]);

mapKeyValue(callable $fn, iterable $iter): iterable

Name: Krak\Fun\mapKeyValue, (*135)

Lazily maps an iterable's key/value tuples to a different set:, (*136)

$keys = mapKeyValue(function ($kv) {
    [$key, $value] = $kv;
    return ["{$key}_", $value * $value];
}, ['a' => 1, 'b' => 2]);
expect(toArrayWithKeys($keys))->equal(['a_' => 1, 'b_' => 4]);

mapOn(array $maps, iterable $iter): iterable

Name: Krak\Fun\mapOn, (*137)

Maps values on specific keys:, (*138)

$values = mapOn(['a' => partial(op, '*', 3), 'b' => partial(op, '+', 1)], ['a' => 1, 'b' => 2, 'c' => 3]);
expect(toArray($values))->equal([3, 3, 3]);

nullable(callable $fn, $value)

Name: Krak\Fun\nullable, (*139)

Performs the callable if the value is not null:, (*140)

expect(nullable('intval', '0'))->equal(0);

Returns null if the value is null:, (*141)

expect(nullable('intval', null))->equal(null);

onEach(callable $handle, iterable $iter)

Name: Krak\Fun\onEach, (*142)

Duplicate of each., (*143)

Invokes a callable on each item in an iterable:, (*144)

$state = [(object) ['id' => 1], (object) ['id' => 2]];
onEach(function ($item) {
    $item->id += 1;
}, $state);
expect([$state[0]->id, $state[1]->id])->equal([2, 3]);

Normally using php foreach should suffice for iterating over an iterable; however, php variables in foreach loops are not scoped whereas closures are., (*145)

op(string $op, $b, $a)

Name: Krak\Fun\op, (*146)

op evaluates binary operations. It expects the right hand operator first which makes most sense when currying or partially applying the op function. When reading the op func, it should be read: evaluate $op with $b with $a e.g.:, (*147)

op('+', 2, 3) -> add 2 with 3
op('-', 2, 3) -> subtract 2 from 3
op('>', 2, 3) => compare greater than 2 with 3

Evaluates two values with a given operator:, (*148)

$res = op('<', 2, 1);
expect($res)->equal(true);

Supports equality operators:, (*149)

$obj = new stdClass();
$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]]];
foreach ($ops as list($op, list($b, $a))) {
    $res = op($op, $b, $a);
    expect($res)->equal(true);
}

Supports other operators:, (*150)

$ops = [['+', [2, 3], 5], ['-', [2, 3], 1], ['*', [2, 3], 6], ['**', [2, 3], 9], ['/', [2, 3], 1.5], ['%', [2, 3], 1], ['.', ['b', 'a'], 'ab']];
foreach ($ops as list($op, list($b, $a), $expected)) {
    $res = op($op, $b, $a);
    expect($res)->equal($expected);
}

Is more useful partially applied or curried:, (*151)

$add2 = Curried\op('+')(2);
$mul3 = partial(op, '*', 3);
$sub4 = Curried\op('-')(4);
// ((2 + 2) * 3) - 4
$res = compose($sub4, $mul3, $add2)(2);
expect($res)->equal(8);

pad(int $size, iterable $iter, $padValue = null): iterable

Name: Krak\Fun\pad, (*152)

Pads an iterable to a specific size:, (*153)

$res = pad(5, [1, 2, 3]);
expect(toArray($res))->equal([1, 2, 3, null, null]);

Allows custom pad values:, (*154)

$res = pad(5, [1, 2, 3], 0);
expect(toArray($res))->equal([1, 2, 3, 0, 0]);

Pads nothing if iterable is the same size as pad size:, (*155)

$res = pad(5, [1, 2, 3, 4, 5]);
expect(toArray($res))->equal([1, 2, 3, 4, 5]);

Pads nothing if iterable is greater than pad size:, (*156)

$res = pad(5, [1, 2, 3, 4, 5, 6]);
expect(toArray($res))->equal([1, 2, 3, 4, 5, 6]);

Ignores keys of original iterable:, (*157)

$res = pad(3, ['a' => 1, 'b' => 2]);
expect(toArrayWithKeys($res))->equal([1, 2, null]);

partial(callable $fn, ...$appliedArgs)

Name: Krak\Fun\partial, (*158)

Partially applies arguments to a function. Given a function signature like f = (a, b, c) -> d, partial(f, a, b) -> (c) -> d:, (*159)

$fn = function ($a, $b, $c) {
    return ($a + $b) * $c;
};
$fn = partial($fn, 1, 2);
// apply the two arguments (a, b) and return a new function with signature (c) -> d
expect($fn(3))->equal(9);

You can also use place holders when partially applying:, (*160)

$fn = function ($a, $b, $c) {
    return ($a + $b) * $c;
};
// _() represents a placeholder for parameter b.
$fn = partial($fn, 1, _(), 3);
// create the new func with signature (b) -> d
expect($fn(2))->equal(9);

Full partial application also works:, (*161)

$fn = function ($a, $b) {
    return [$a, $b];
};
$fn = partial($fn, 1, 2);
expect($fn())->equal([1, 2]);

partition(callable $partition, iterable $iter, int $numParts = 2): array

Name: Krak\Fun\partition, (*162)

Splits an iterable into different arrays based off of a predicate. The predicate should return the index to partition the data into:, (*163)

list($left, $right) = partition(function ($v) {
    return $v < 3 ? 0 : 1;
}, [1, 2, 3, 4]);
expect([$left, $right])->equal([[1, 2], [3, 4]]);

pick(iterable $fields, array $data): array

Name: Krak\Fun\pick, (*164)

Picks only the given fields from a structured array:, (*165)

$res = pick(['a', 'b'], ['a' => 1, 'b' => 2, 'c' => 3]);
expect($res)->equal(['a' => 1, 'b' => 2]);

Can be used in curried form:, (*166)

$res = arrayMap(Curried\pick(['id', 'name']), [['id' => 1, 'name' => 'Foo', 'slug' => 'foo'], ['id' => 2, 'name' => 'Bar', 'slug' => 'bar']]);
expect($res)->equal([['id' => 1, 'name' => 'Foo'], ['id' => 2, 'name' => 'Bar']]);

pickBy(callable $pick, array $data): array

Name: Krak\Fun\pickBy, (*167)

Picks only the fields that match the pick function from a structured array:, (*168)

$res = pickBy(Curried\spread(function (string $key, int $value) : bool {
    return $value % 2 === 0;
}), ['a' => 1, 'b' => 2, 'c' => 3]);
expect($res)->equal(['b' => 2]);

pipe(callable ...$fns)

Name: Krak\Fun\pipe, (*169)

Creates a function that pipes values from one func to the next.:, (*170)

$add3 = Curried\op('+')(3);
$mul2 = Curried\op('*')(2);
$add3ThenMul2 = pipe($add3, $mul2);
$res = $add3ThenMul2(5);
expect($res)->equal(16);

Allows an empty initial argument:, (*171)

$res = pipe(function () {
    yield from [1, 2, 3];
}, Curried\reduce(function ($acc, $v) {
    return $acc + $v;
}, 0))();
expect($res)->equal(6);

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)), (*172)

product(iterable ...$iters): iterable

Name: Krak\Fun\product, (*173)

Creates a cartesian product of multiple sets:, (*174)

$res = product([1, 2], [3, 4], [5, 6]);
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]]);

prop(string $key, $data, $else = null)

Name: Krak\Fun\prop, (*175)

Accesses a property from an object:, (*176)

$obj = new \StdClass();
$obj->id = 1;
$res = prop('id', $obj);
expect($res)->equal(1);

If no property exists, it will return the $else value:, (*177)

$obj = new \StdClass();
$res = prop('id', $obj, 2);
expect($res)->equal(2);

propIn(array $props, $obj, $else = null)

Name: Krak\Fun\propIn, (*178)

Accesses a property deep in an object tree:, (*179)

$obj = new \StdClass();
$obj->id = 1;
$obj->child = new \StdClass();
$obj->child->id = 2;
$res = propIn(['child', 'id'], $obj);
expect($res)->equal(2);

If any property is missing in the tree, it will return the $else value:, (*180)

$obj = new \StdClass();
$obj->id = 1;
$obj->child = new \StdClass();
$res = propIn(['child', 'id'], $obj, 3);
expect($res)->equal(3);

range($start, $end, $step = null)

Name: Krak\Fun\range, (*181)

Creates an iterable of a range of values starting from $start going to $end inclusively incrementing by $step:, (*182)

$res = range(1, 3);
expect(toArray($res))->equal([1, 2, 3]);

It also allows a decreasing range:, (*183)

$res = range(3, 1);
expect(toArray($res))->equal([3, 2, 1]);

An exception will be thrown if the $step provided goes in the wrong direction:, (*184)

expect(function () {
    toArray(range(1, 2, -1));
})->throw(\InvalidArgumentException::class);
expect(function () {
    toArray(range(2, 1, 1));
})->throw(\InvalidArgumentException::class);

reduce(callable $reduce, iterable $iter, $acc = null)

Name: Krak\Fun\reduce, (*185)

Reduces an iterable into a single value:, (*186)

$res = reduce(function ($acc, $v) {
    return $acc + $v;
}, range(1, 3), 0);
expect($res)->equal(6);

reduceKeyValue(callable $reduce, iterable $iter, $acc = null)

Name: Krak\Fun\reduceKeyValue, (*187)

Reduces an iterables key value pairs into a value:, (*188)

$res = reduceKeyValue(function ($acc, $kv) {
    [$key, $value] = $kv;
    return $acc . $key . $value;
}, fromPairs([['a', 1], ['b', 2]]), "");
expect($res)->equal("a1b2");

reindex(callable $fn, iterable $iter): iterable

Name: Krak\Fun\reindex, (*189)

Re-indexes a collection via a callable:, (*190)

$res = reindex(function ($v) {
    return $v['id'];
}, [['id' => 2], ['id' => 3], ['id' => 1]]);
expect(toArrayWithKeys($res))->equal([2 => ['id' => 2], 3 => ['id' => 3], 1 => ['id' => 1]]);

retry(callable $fn, $shouldRetry = null)

Name: Krak\Fun\retry, (*191)

Executes a function and retries if an exception is thrown:, (*192)

$i = 0;
$res = retry(function () use(&$i) {
    $i += 1;
    if ($i <= 1) {
        throw new \Exception('bad');
    }
    return $i;
});
expect($res)->equal(2);

Only retries $maxTries times else it gives up and bubbles the exception:, (*193)

expect(function () {
    $i = 0;
    retry(function () use(&$i) {
        $i += 1;
        throw new \Exception((string) $i);
    }, 5);
})->throw('Exception', '6');

Retries until $shouldRetry returns false:, (*194)

$i = 0;
expect(function () {
    $res = retry(function () use(&$i) {
        $i += 1;
        throw new \Exception((string) $i);
    }, function ($numRetries, \Throwable $t = null) {
        return $numRetries < 2;
    });
})->throw('Exception', '2');

Sends numRetries into the main fn:, (*195)

$res = retry(function ($numRetries) {
    if (!$numRetries) {
        throw new Exception('bad');
    }
    return $numRetries;
}, 2);
expect($res)->equal(1);

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., (*196)

Name: Krak\Fun\search, (*197)

Searches for an element in a collection where the callable returns true:, (*198)

$res = search(function ($v) {
    return $v['id'] == 2;
}, [['id' => 1], ['id' => 2], ['id' => 3]]);
expect($res)->equal(['id' => 2]);

Returns null if no element was found:, (*199)

$res = search(function ($v) {
    return false;
}, [['id' => 1], ['id' => 2], ['id' => 3]]);
expect($res)->equal(null);

setIndex($key, $value, array $data)

Name: Krak\Fun\setIndex, (*200)

Sets an index in an array:, (*201)

$res = setIndex('a', 1, []);
expect($res['a'])->equal(1);

setIndexIn(array $keys, $value, array $data)

Name: Krak\Fun\setIndexIn, (*202)

Sets a nested index in an array:, (*203)

$res = setIndexIn(['a', 'b'], 1, ['a' => []]);
expect($res['a']['b'])->equal(1);

setProp(string $key, $value, $data)

Name: Krak\Fun\setProp, (*204)

Sets a property in an object:, (*205)

$res = setProp('a', 1, (object) []);
expect($res->a)->equal(1);

slice(int $start, iterable $iter, $length = INF): iterable

Name: Krak\Fun\slice, (*206)

It takes an inclusive slice from start to a given length of an interable:, (*207)

$sliced = slice(1, range(0, 4), 2);
expect(toArray($sliced))->equal([1, 2]);

If length is not supplied it default to the end of the iterable:, (*208)

$sliced = slice(2, range(0, 4));
expect(toArray($sliced))->equal([2, 3, 4]);

will not consume the iterator once the slice has been yielded:, (*209)

$i = 0;
$gen = function () use(&$i) {
    foreach (range(0, 4) as $v) {
        $i = $v;
        (yield $i);
    }
};
$sliced = toArray(slice(1, $gen(), 2));
expect($sliced)->equal([1, 2]);
expect($i)->equal(2);

sortFromArray(callable $fn, array $orderedElements, iterable $iter): array

Name: Krak\Fun\sortFromArray, (*210)

Sort an iterable with a given array of ordered elements to sort by:, (*211)

$data = [['id' => 1, 'name' => 'A'], ['id' => 2, 'name' => 'B'], ['id' => 3, 'name' => 'C']];
$res = sortFromArray(Curried\index('id'), [2, 3, 1], $data);
expect(arrayMap(Curried\index('name'), $res))->equal(['B', 'C', 'A']);

Throws an exception if any item in the iterable is not within the orderedElements:, (*212)

expect(function () {
    $data = [['id' => 1]];
    $res = sortFromArray(Curried\index('id'), [], $data);
})->throw(\LogicException::class, 'Cannot sort element key 1 because it does not exist in the ordered elements.');

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., (*213)

spread(callable $fn, array $data)

Name: Krak\Fun\spread, (*214)

Spreads an array of arguments to a callable:, (*215)

$res = spread(function ($a, $b) {
    return $a . $b;
}, ['a', 'b']);
expect($res)->equal('ab');

Can be used in the curried form to unpack tuple arguments:, (*216)

$res = arrayMap(Curried\spread(function (string $first, int $second) {
    return $first . $second;
}), [['a', 1], ['b', 2]]);
expect($res)->equal(['a1', 'b2']);

Note: this is basically just an alias for call_user_func_array or simply a functional wrapper around the ... (spread) operator., (*217)

take(int $num, iterable $iter): iterable

Name: Krak\Fun\take, (*218)

Takes the first num items from an iterable:, (*219)

$res = take(2, range(0, 10));
expect(toArray($res))->equal([0, 1]);

takeWhile(callable $predicate, iterable $iter): iterable

Name: Krak\Fun\takeWhile, (*220)

Takes elements from an iterable while the $predicate returns true:, (*221)

$res = takeWhile(Curried\op('>')(0), [2, 1, 0, 1, 2]);
expect(toArray($res))->equal([2, 1]);

tap(callable $tap, $value)

Name: Krak\Fun\tap, (*222)

Calls given tap function on value and returns value:, (*223)

$loggedValues = [];
$res = tap(function (string $v) use(&$loggedValues) {
    $loggedValues[] = $v;
}, 'abc');
expect([$loggedValues[0], $res])->equal(['abc', 'abc']);

tap is useful anytime you need to operate on a value and do not want to modify the return value., (*224)

throwIf(callable $throw, callable $if, $value)

Name: Krak\Fun\throwIf, (*225)

Throws the given exception if value given evaluates to true:, (*226)

expect(function () {
    throwIf(function (int $value) {
        return new RuntimeException('Error: ' . $value);
    }, function (int $value) {
        return $value === 0;
    }, 0);
})->throw(RuntimeException::class, 'Error: 0');

Returns given value if value evaluates to false:, (*227)

$res = throwIf(function (int $value) {
    return new RuntimeException('Error: ' . $value);
}, function (int $value) {
    return $value === 0;
}, 1);
expect($res)->equal(1);

Note: works best with short closures!, (*228)

toArray(iterable $iter): array

Name: Krak\Fun\toArray, (*229)

will tranform any iterable into an array:, (*230)

$res = toArray((function () {
    (yield 1);
    (yield 2);
    (yield 3);
})());
expect($res)->equal([1, 2, 3]);

can also be used as a constant:, (*231)

$res = compose(toArray, id)((function () {
    (yield 1);
    (yield 2);
    (yield 3);
})());
expect($res)->equal([1, 2, 3]);

toArrayWithKeys(iterable $iter): array

Name: Krak\Fun\toArrayWithKeys, (*232)

can convert to an array and keep the keys:, (*233)

$gen = function () {
    (yield 'a' => 1);
    (yield 'b' => 2);
};
expect(toArrayWithKeys($gen()))->equal(['a' => 1, 'b' => 2]);

toPairs(iterable $iter): iterable

Name: Krak\Fun\toPairs, (*234)

Transforms an associative array into an iterable of tuples [$key, $value]:, (*235)

$res = toPairs(['a' => 1, 'b' => 2]);
expect(toArray($res))->equal([['a', 1], ['b', 2]]);

updateIndexIn(array $keys, callable $update, array $data): array

Name: Krak\Fun\updateIndexIn, (*236)

Updates a nested element within a deep array structure:, (*237)

$data = ['a' => ['b' => ['c' => 3]]];
$data = updateIndexIn(['a', 'b', 'c'], function ($v) {
    return $v * $v;
}, $data);
expect($data)->equal(['a' => ['b' => ['c' => 9]]]);

Throws an exception if nested key does not exist:, (*238)

expect(function () {
    $data = ['a' => ['b' => ['c' => 9]]];
    updateIndexIn(['a', 'c', 'c'], function () {
    }, $data);
})->throw(\RuntimeException::class, 'Could not updateIn because the keys a -> c -> c could not be found.');

values(iterable $iter): iterable

Name: Krak\Fun\values, (*239)

Exports only the values of an iterable:, (*240)

$res = values(['a' => 1, 'b' => 2]);
expect(toArrayWithKeys($res))->equal([1, 2]);

when(callable $if, callable $then, $value)

Name: Krak\Fun\when, (*241)

Evaluates the given value with the $then callable if the predicate returns true:, (*242)

$if = function ($v) {
    return $v == 3;
};
$then = function ($v) {
    return $v * $v;
};
$res = when($if, $then, 3);
expect($res)->equal(9);

But will return the given value if the predicate returns false:, (*243)

$if = function ($v) {
    return $v == 3;
};
$then = function ($v) {
    return $v * $v;
};
$res = when($if, $then, 4);
expect($res)->equal(4);

withState(callable $fn, $initialState = null)

Name: Krak\Fun\withState, (*244)

Decorate a function with accumulating state:, (*245)

$fn = withState(function ($state, $v) {
    return [$state + 1, $state . ': ' . $v];
}, 1);
$res = arrayMap($fn, iter('abcd'));
expect($res)->equal(['1: a', '2: b', '3: c', '4: d']);

within(array $fields, iterable $iter): \Iterator

Name: Krak\Fun\within, (*246)

Only allows keys within the given array to stay:, (*247)

$data = flip(iter('abcd'));
$res = within(['a', 'c'], $data);
expect(toArrayWithKeys($res))->equal(['a' => 0, 'c' => 2]);

without(array $fields, iterable $iter): \Iterator

Name: Krak\Fun\without, (*248)

Filters an iterable to be without the given keys:, (*249)

$data = flip(iter('abcd'));
$res = without(['a', 'c'], $data);
expect(toArrayWithKeys($res))->equal(['b' => 1, 'd' => 3]);

zip(iterable ...$iters): \Iterator

Name: Krak\Fun\zip, (*250)

Zips multiple iterables into an iterable n-tuples:, (*251)

$res = zip(iter('abc'), range(1, 3), [4, 5, 6]);
expect(toArray($res))->equal([['a', 1, 4], ['b', 2, 5], ['c', 3, 6]]);

Returns an empty iterable if no iters are present:, (*252)

expect(toArray(zip()))->equal([]);

The Versions

27/06 2018

dev-master

9999999-dev

Functional library for php with proper currying

  Sources   Download

MIT

The Development Requires

php functional curry partial-application

27/06 2018

v0.1.18

0.1.18.0

Functional library for php with proper currying

  Sources   Download

MIT

The Development Requires

php functional curry partial-application

26/06 2018

v0.1.17

0.1.17.0

Functional library for php with proper currying

  Sources   Download

MIT

The Development Requires

php functional curry partial-application

11/05 2018

v0.1.16

0.1.16.0

Functional library for php with proper currying

  Sources   Download

MIT

The Development Requires

php functional curry partial-application

07/05 2018

v0.1.15

0.1.15.0

Functional library for php with proper currying

  Sources   Download

MIT

The Development Requires

php functional curry partial-application

07/05 2018

v0.1.14

0.1.14.0

Functional library for php with proper currying

  Sources   Download

MIT

The Development Requires

php functional curry partial-application

06/05 2018

v0.1.13

0.1.13.0

Functional library for php with proper currying

  Sources   Download

MIT

The Development Requires

php functional curry partial-application

06/05 2018

v0.1.12

0.1.12.0

Functional library for php with proper currying

  Sources   Download

MIT

The Development Requires

php functional curry partial-application

18/04 2018

v0.1.11

0.1.11.0

Functional library for php with proper currying

  Sources   Download

MIT

The Development Requires

php functional curry partial-application

13/04 2018

v0.1.10

0.1.10.0

Functional library for php with proper currying

  Sources   Download

MIT

The Development Requires

php functional curry partial-application

12/04 2018

v0.1.9

0.1.9.0

Functional library for php with proper currying

  Sources   Download

MIT

The Development Requires

php functional curry partial-application

21/03 2018

v0.1.8

0.1.8.0

Functional library for php with proper currying

  Sources   Download

MIT

The Development Requires

php functional curry partial-application

15/03 2018

v0.1.7

0.1.7.0

Functional library for php with proper currying

  Sources   Download

MIT

The Development Requires

php functional curry partial-application

12/03 2018

v0.1.6

0.1.6.0

Functional library for php with proper currying

  Sources   Download

MIT

The Development Requires

php functional curry partial-application

12/03 2018

v0.1.5

0.1.5.0

Functional library for php with proper currying

  Sources   Download

MIT

The Development Requires

php functional curry partial-application

21/02 2018

v0.1.4

0.1.4.0

Functional library for php with proper currying

  Sources   Download

MIT

The Development Requires

php functional curry partial-application

21/02 2018

v0.1.3

0.1.3.0

Functional library for php with proper currying

  Sources   Download

MIT

The Development Requires

php functional curry partial-application

16/02 2018

v0.1.2

0.1.2.0

Functional library for php with proper currying

  Sources   Download

MIT

The Development Requires

php functional curry partial-application

14/02 2018

v0.1.1

0.1.1.0

Functional library for php with proper currying

  Sources   Download

MIT

The Development Requires

php functional curry partial-application

07/02 2018

v0.1.0

0.1.0.0

Functional library for php with proper currying

  Sources   Download

MIT

The Development Requires

php functional curry partial-application