krak/fn

Functional library for php with proper currying

v0.1.21 2018-09-16 17:15 UTC

README

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.

Installation

Install with composer at krak/fn

Usage

All functions are defined in Krak\Fn, are not curried, and are data last. Curried versions of functions are defined Kran\Fn\Curried. Constants are also generated per function in Krak\Fn and Krak\Fn\Consts.

<?php

use function Krak\Fn\Curried\{filter, map, op};
use function Krak\Fn\{compose};
use const Krak\Fn\{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.

Constants

All functions have equivalent constants generated. These constants are defined as the fully qualified name of the function and in the Consts namespace.

namespace Krak\Fn {
    function toArray($data) {};
    const toArray = 'Krak\\Fn\\toArray';
};
namespace Krak\Fn\Consts {
    const toArray = 'Krak\\Fn\\toArray';
}

One great way to use the consts is with compose or pipe chains:

use Krak\Fn\{Curried as c, Consts as cn};
use function Krak\Fn\{compose};

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

Another great example is partial application.

use function Krak\Fn\{partial, map, toArray};
use const Krak\Fn\{op};

$res = toArray(map(partial(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\\Fn\\op', '*', 3).

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.

These function definitions aren't curryable:

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

These are:

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

Given a function definition like:

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

the curried verison would look like:

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

Importing

I've found the most practical way to import functions and constants from the Fn library is as follows:

<?php

use Krak\{Fn as f, Fn\Curried as c, Fn\Consts as cn};

$res = f\compose(
    cn\toArray,
    cn\fromPairs,
    c\map(function($tup) {
        return [$tup[0], $tup[1] * $tup[1]];
    }),
)(f\zip(['a', 'b', 'c'], [1,2,3]))

Docs

Docs are generated with make docs. This uses Krak Peridocs to actually generate the documentation from the peridot tests.

Code Generation

The constants and curried functions are generated with make code.

Tests

Tests are run via make test and are stored in the test directory. We use peridot for testing.

API

arrayFilterarrayMaparrayReindexassignchainchunkcomposeconstruct
currydifferenceWithdropdropWhileeachfilterfilterKeysflatMap
flattenflipfromPairshasIndexInheadinArrayindexindexIn
indexOfiterjoinkeysmapmapAccummapKeysmapKeyValue
mapOnonEachoppartialpartitionpipeproppropIn
rangereducereduceKeyValuereindexretrysearchsetIndexsetIndexIn
setPropslicesortFromArrayspreadtaketakeWhiletoArraytoArrayWithKeys
toPairsupdateIndexInvalueswhenwithStatewithinwithoutzip

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

Name: Krak\Fn\arrayFilter

Alias of array_filter:

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

Filters iterables as well as arrays:

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

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

Name: Krak\Fn\arrayMap

Alias of array_map:

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

Maps iterables as well as arrays:

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

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

Name: Krak\Fn\arrayReindex

Re-indexes a collection via a callable into an associative array:

$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]]);

assign($obj, iterable $iter)

Name: Krak\Fn\assign

Assigns iterable keys and values to an object:

$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\Fn\chain

Chains iterables together into one iterable:

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

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

Name: Krak\Fn\chunk

Chunks an iterable into equal sized chunks.:

$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:

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

compose(callable ...$fns)

Name: Krak\Fn\compose

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

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

construct($className, ...$args)

Name: Krak\Fn\construct

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

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

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

Name: Krak\Fn\curry

currys the given function $n times:

$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

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

Name: Krak\Fn\differenceWith

Takes the difference between two iterables with a given comparator:

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

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

Name: Krak\Fn\drop

Drops the first num items from an iterable:

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

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

Name: Krak\Fn\dropWhile

Drops elements from the iterable while the predicate returns true:

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

each(callable $handle, iterable $iter)

Name: Krak\Fn\each

Invokes a callable on each item in an iterable:

$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.

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

Name: Krak\Fn\filter

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:

$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\Fn\filterKeys

Filters an iterable off of the keys:

$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\Fn\flatMap

Maps and then flattens an iterable:

$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.

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

Name: Krak\Fn\flatten

Flattens nested iterables into a flattened set of elements:

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

Can flatten a specific number of levels:

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

flip(iterable $iter): iterable

Name: Krak\Fn\flip

Flips the keys => values of an iterable to values => keys:

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

fromPairs(iterable $iter): iterable

Name: Krak\Fn\fromPairs

Converts an iterable of tuples [$key, $value] into an associative iterable:

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

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

Name: Krak\Fn\hasIndexIn

Checks if a nested index exists in the given data:

$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:

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

head(iterable $iter)

Name: Krak\Fn\head

Returns the fist element in an iterable:

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

But returns null if the iterable is empty:

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

inArray(array $set, $item): bool

Name: Krak\Fn\inArray

Checks if an item is within an array of items:

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

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

Name: Krak\Fn\index

Accesses an index in an array:

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

If no value exists at the given index, $else will be returned:

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

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

Name: Krak\Fn\indexIn

Accesses a nested index in a deep array structure:

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

If any of the indexes do not exist, $else will be returned:

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

indexOf(callable $predicate, iterable $iter)

Name: Krak\Fn\indexOf

Searches for an element and returns the key if found:

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

iter($iter): \Iterator

Name: Krak\Fn\iter

Converts any iterable into a proper instance of Iterator.

Can convert arrays:

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

Can convert an Iterator:

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

Can convert objects:

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

Can convert any iterable:

$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:

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

Will throw an exception otherwise:

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

join(string $sep, iterable $iter)

Name: Krak\Fn\join

Joins an iterable with a given separator:

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

keys(iterable $iter): iterable

Name: Krak\Fn\keys

Yields only the keys of an in iterable:

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

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

Name: Krak\Fn\map

Lazily maps an iterable's values to a different set:

$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\Fn\mapAccum

Maps a function to each element of a list while passing in an accumulator to accumulate over every iteration:

$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

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

Name: Krak\Fn\mapKeys

Lazily maps an iterable's keys to a different set:

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

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

Name: Krak\Fn\mapKeyValue

Lazily maps an iterable's key/value tuples to a different set:

$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\Fn\mapOn

Maps values on specific keys:

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

onEach(callable $handle, iterable $iter)

Name: Krak\Fn\onEach

Duplicate of each.

Invokes a callable on each item in an iterable:

$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.

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

Name: Krak\Fn\op

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.:

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:

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

Supports equality operators:

$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:

$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:

$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);

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

Name: Krak\Fn\partial

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

$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:

$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:

$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\Fn\partition

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

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

pipe(callable ...$fns)

Name: Krak\Fn\pipe

Creates a function that pipes values from one func to the next.:

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

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))

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

Name: Krak\Fn\prop

Accesses a property from an object:

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

If no property exists, it will return the $else value:

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

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

Name: Krak\Fn\propIn

Accesses a property deep in an object tree:

$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:

$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\Fn\range

Creates an iterable of a range of values starting from $start going to $end inclusively incrementing by $step:

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

It also allows a decreasing range:

$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:

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\Fn\reduce

Reduces an iterable into a single value:

$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\Fn\reduceKeyValue

Reduces an iterables key value pairs into a value:

$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\Fn\reindex

Re-indexes a collection via a callable:

$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\Fn\retry

Executes a function and retries if an exception is thrown:

$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:

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

Retries until $shouldRetry returns false:

$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:

$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.

search(callable $predicate, iterable $iter)

Name: Krak\Fn\search

Searches for an element in a collection where the callable returns true:

$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:

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

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

Name: Krak\Fn\setIndex

Sets an index in an array:

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

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

Name: Krak\Fn\setIndexIn

Sets a nested index in an array:

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

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

Name: Krak\Fn\setProp

Sets a property in an object:

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

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

Name: Krak\Fn\slice

It takes an inclusive slice from start to a given length of an interable:

$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:

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

will not consume the iterator once the slice has been yielded:

$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\Fn\sortFromArray

Sort an iterable with a given array of ordered elements to sort by:

$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:

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.

spread(callable $fn, array $data)

Name: Krak\Fn\spread

Spreads an array of arguments to a callable:

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

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

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

Name: Krak\Fn\take

Takes the first num items from an iterable:

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

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

Name: Krak\Fn\takeWhile

Takes elements from an iterable while the $predicate returns true:

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

toArray(iterable $iter): array

Name: Krak\Fn\toArray

will tranform any iterable into an array:

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

can also be used as a constant:

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

toArrayWithKeys(iterable $iter): array

Name: Krak\Fn\toArrayWithKeys

can convert to an array and keep the keys:

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

toPairs(iterable $iter): iterable

Name: Krak\Fn\toPairs

Transforms an associative array into an iterable of tuples [$key, $value]:

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

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

Name: Krak\Fn\updateIndexIn

Updates a nested element within a deep array structure:

$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:

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\Fn\values

Exports only the values of an iterable:

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

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

Name: Krak\Fn\when

Evaluates the given value with the $then callable if the predicate returns true:

$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:

$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\Fn\withState

Decorate a function with accumulating state:

$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\Fn\within

Only allows keys within the given array to stay:

$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\Fn\without

Filters an iterable to be without the given keys:

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

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

Name: Krak\Fn\zip

Zips multiple iterables into an iterable n-tuples:

$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:

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