Documentation
Philosophy
Garp\Functional
is a practical functional library that strives to embrace functional paradigms.
The functions are written to be pure – meaning side-effect free with repeatable output given the same input. We generally don’t mutate data, but instead create copies of given data.
All functions are curried and data-last to encourage function composition.
As of version 3.0.0
, typeclasses are being added to the library, inspired by The Fantasyland Specification.
These typeclasses can be used to implement objects that are usable as input to the utilities in Garp\Functional
.
An index of implemented typeclasses is maintained at the end of this document.
On data-last
An important aspect of this library is that functions generally take the data upon which they act as last argument.
This is very different from how most libraries do it. It promotes a Point-free style of programming, which is an elegant way of doing function composition without naming the argument explicitly all the time.
This helps keep the function abstract, not needing a specifically named parameter that would otherwise bind the function to a certain problem domain, and of course it’s one less thing for typos to end up in.
Probably more important, a data-last, curried library enables you to write like this:
$toName = f\map(f\prop('name'));
$users = [
['name' => 'Bob', 'age' => 30, 'occupation' => 'Taxi driver'],
['name' => 'Alice', /* etc... */ ]
];
$names = $toName($users); // ['Bob', 'Alice', ...]
Or go totally crazy:
$getInitials = f\compose(f\join(' '), f\map(f\compose(f\concat_right('.'), f\prop(0))), f\split(' '));
$getInitials('Miles Davis'); // M. D.
Sure, that line is cuckoo, but the idea of composing functions without having to stop at every step to consider the control flow structures, what the parameters are going to be named, and how to return it is pretty powerful.
Function name constants
All functions also publish a constant by the same name. This allows you to pass around the function as if you had a reference to it, as is possible in other languages.
For example:
f\compose(
f\flatten,
f\entries
);
This works because the constant contains the fully namespaced function name as a string, and therefore qualifies as a callable
.
Function index
- add
- always
- autocurry
- binary
- both
- call
- compose
- concat
- concat_right
- concat_with
- contains
- count
- divide
- drop
- drop_while
- either
- entries
- equals
- every
- filter
- find
- find_index
- flatten
- flip
- fold
- group_by
- gt
- gte
- head
- id
- index_by
- instance
- is_assoc
- join
- keys
- keys_where
- last
- lt
- lte
- map
- match_regex
- merge_after
- merge_at
- modulo
- multiply
- none
- not
- omit
- once
- partial
- partial_right
- pick
- pipe
- prop_equals
- prop
- prop_in
- prop_of
- prop_set
- publish
- reduce
- reduce_assoc
- reindex
- reject
- rename_keys
- repeat
- replace
- some
- sort
- sort_by
- sort_by_map
- split
- subtract
- tail
- take
- take_while
- tap
- truthy
- unary
- unique
- usort
- when
- zip
Note: code examples assume the library is loaded with use Garp\Functional as f;
Also note: list functions are generally designed to work with both numerically indexed and associative arrays, iterable objects and strings.
add
Adds two numbers.
f\add(10, 20); // 30
f\add(10)(20); // 30
always
Returns a function that always returns the given argument.
$alwaysMiles = f\always('Miles Davis');
$alwaysMiles(1, 2, 3); // 'Miles Davis'
autocurry
autocurry
keeps partially applying a function until you give it enough arguments, after which it will resolve.
This is used heavily by Garp\Functional
internally, but it might be useful to you too.
Note: you have to provide the function’s arity manually, to avoid Reflection API lookups.
$foo = function ($a, $b, $c, $d) {
return $a . $b . $c . $d;
};
$curried = autocurry($foo, 4);
$first = $curried('a');
$second = $first('b');
$third = $second('c');
$fourth = $third('d'); // "abcd"
binary
Returns a function that accepts just two arguments.
$countArgs = function () {
return count(func_get_args());
};
$countArgs(1, 2, 3); // 3
f\binary($countArgs)(1, 2, 3); // 2
See also Unary.
both
Returns true if both arguments are truthy.
f\both(123, 456); // true
f\both(true, false); // false
f\both(array(), 123); // false
f\both('cheese', 'ham'); // true
When either of the arguments is a function, both
returns a new function waiting for arguments to
pass to the given function.
$isMediumNumber = f\both(f\gt(50), f\lt(200));
$isMediumNumber(67); // true
$isMediumNumber(199); // true
$isMediumNumber(10); // false
$isMediumNumber(600); // false
call
Call a method on an object.
// Imagine an array of active records, containing a `getName` method.
$users = $database->fetchUsers();
$names = f\map(f\call('getName'), $users); // array of names
Call
can also accept an array of arguments.
// Imagine an array of active records, containing a `getDateOfBirth` method that accept a format string
$users = $database->fetchUsers();
$datesOfBirth = f\map(f\call('getDateOfBirth', ['Y-m-d']), $users);
compose
Compose functions together. Execution order is right to left, as is traditional.
It mimics a manual approach. e.g. compose(foo, bar, baz)($args)
equals foo(bar(baz($args)))
.
Let’s look at the crazy example from the intro of this page:
$getInitials = f\compose(f\join(' '), f\map(f\compose(f\concat_right('.'), f\prop(0))), f\split(' '));
$getInitials('Miles Davis'); // M. D.
If you start at the right side you can follow along with the path your arguments will travel through the functional pipeline.
concat
Concatenate two lists. (strings are also lists)
f\concat('foo', 'bar'); // 'foobar'
$a = [1, 2, 3];
$b = [4, 5, 6];
f\concat($a, $b); // [1, 2, 3, 4, 5, 6]
Note, the right side overrides the left side:
$a = [
'first_name' => 'Miles',
'last_name' => 'Davis'
];
$b = [ 'first_name' => 'John' ];
f\concat($a, $b); // ['first_name' => 'John', 'last_name' => 'Davis']
concat_right
Same as concat
, but flips the order in which the arguments are concatenated.
Especially interesting when dealing with associative arrays: this function lets the left side
override the right side:
$a = [
'first_name' => 'Miles',
'last_name' => 'Davis'
];
$b = [ 'first_name' => 'John' ];
f\concat_right($a, $b); // ['first_name' => 'Miles', 'last_name' => 'Davis']
concat_with
This merges two or more arrays, using the given functions to determine how to merge values together.
Examples:
$a = ['total' => 100, 'amount' => 4];
$b = ['total' => 50, 'amount' => 5];
f\concat_with(f\add, $a, $b); // ['total' => 150, 'amount' => 9]
$c = ['name' => 'Foo'];
$d = ['name' => 'Bar'];
f\concat_with(f\concat, $c, $d); // ['name' => 'FooBar']
contains
Checks for existence of a needle in a haystack. When given an array, it performs in_array
.
On strings, it will use strpos
. Any object implementing Traversable
will be checked as if it were an array.
f\contains('hello', ['hello', 'world']); // true
f\contains(42, [12, 24, 42]); // true
count
Counts strings (chars) and arrays:
f\count(['a', 'b', 'c']); // 3
f\count('foobar'); // 6
f\count(new ClassWithToStringMethod('foo')); // 3
divide
Divides two numbers.
f\divide(2, 10); // 5
f\divide(5)(20); // 4
drop
Drop the first $n
items of a collection.
f\drop(2, ['foo', 'bar', 'baz']); // ['baz']
f\drop(6, 'Miles Davis'); // ['D', 'a', 'v', 'i', 's']
drop_while
Drop items from the front of a collection until the predicate function returns false.
Note that the function stops dropping when it finds a falsey result, so you may end up with items in
the array that do not match your predicate function:
$numbers = array(-1, -30, 5, 932, -2, 13);
$positiveNumbers = f\drop_while(f\lte(0), $numbers);
/**
* Result:
* array(5, 932, -2, 13);
*/
See also take and take_while.
either
Returns the left argument if truthy, otherwise the right argument.
Can be used for default values:
$name = f\either(f\prop('name', $user), 'Anonymous');
If either the left or the right argument is a function, a new function will be returned. Arguments passed to this function will first be pushed thru the given function(s) and its return value(s) will be compared.
$users = [
['name' => 'Hank', 'role' => 'admin'],
['name' => 'Julia', 'role' => 'basic'],
['name' => 'Lisa', 'role' => 'admin'],
['name' => 'Gerald']
);
$getBasicUsers = f\filter(
f\either(
f\not(f\prop('role')),
f\prop_equals('role', 'basic')
)
); // [['name' => 'Julia', 'role' => 'basic'], ['name' => 'Gerald']]
entries
Inspired by Javascript’s Array.entries
function, this will return a collection of tuples containing key/value pairs from your iterable:
f\entries(['foo', 'bar', 'baz']); // [[0, 'foo'], [1, 'bar'], [2, 'baz']]
f\entries(['foo' => 123, 'bar' => 456]); // [['foo', 123], ['bar', 456]]
Helpful when you need the keys when, for instance, mapping over a collection.
equals
Equality check in function form.
f\equals(1, 2); // false
f\equals('Hello', 'Hello'); // true
f\equals('1', 1); // false
When given two Setoid types, will map to the equals()
method:
class User implements Setoid {
public function equals(Setoid $that): bool {
return $this->id === $that->id;
}
}
$user1 = new User(1);
$user2 = new User(1);
f\equals($user1, $user2); // true
every
Returns true if all items in the list match the predicate function.
$numbers = [12, 40, 23, 90];
f\every('is_int', $numbers); // true
f\every(f\gt(50), $numbers); // false
filter
Curried version of array_filter
.
Note that this version of filter reindexes numeric arrays. It’s closer to array filtering found in
other languages, where this often is the case.
And frankly, it drives me up the walls to filter an array in PHP and getting back a mutant array
with keys starting at 5 with large gaps in between.
So this an opinionated version of array_filter
, I guess.
$names = ['Miles Davis', 'John Coltrane'];
f\filter(f\equals('Miles Davis'), $names); // ['Miles Davis']
// I will preserve string indexes:
$numbers = ['hundred' => 100, 'three' => 3, 'fiftytwo' => 52];
f\filter(f\gt(50), $numbers); // ['hundred' => 100, 'fiftytwo' => 52]
// I will remove falsy values:
$values = [0, null, false, '', '0'];
f\filter(f\truthy, $values); // []
find
Basically combines filter
with a prop(0)
call: it filters a collection and returns the first
match.
$numbers = [40, 15, 23, 90, 29];
f\find(f\gt(20), $numbers); // 23
f\find(f\gt(200), $numbers); // null
find_index
Finds the index of the first item that matches the predicate function.
$numbers = [20, 10, 50, 21, 90];
f\find_index(f\lt(20), $numbers); // 1
f\find_index(f\gt(50), $numbers); // 5
flatten
Flatten an array of arrays into a single array.
$data = [1, 2, [3, 4, 5, [6, 7], 8], 9, [], 10];
f\flatten($data); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Note: associative arrays are considered their own little unit and not unpacked further.
You can use array_values
for that.
flip
Flip the order of the first two arguments of a function.
$concat = function ($a, $b) {
return $a . $b;
};
$concat('Hello', 'world'); // 'Helloworld'
f\flip($concat)('Hello', 'world'); // 'worldHello'
fold
Folds a collection of values into a Monoid.
f\fold(Sum::class, [42, 100, 89, 5, 11, 100])->value; // 347
Garp\Functional
provides a starter-kit of Monoids to use in your programs, see the list of types below.
Try to think of Monoids as a way of combining two values, but not necessarily into a collection. They define the rules of concatenation.
Any
and All
are good examples of Monoids that specify combining two values behaviorally. They both describe boolean logic, OR and AND respectively:
f\fold(Any::class, [false, false, false, true, false])->value; // true
f\fold(All::class, [false, false, false, true, false])->value; // false
group_by
Returns an array from the given collection, keyed by the given index. If index is callable, the collection is keyed by the result of calling the function with the given item in the collection.
$musicians = array(
array('name' => 'Miles Davis', 'instrument' => 'trumpet'),
array('name' => 'John Coltrane', 'instrument' => 'saxophone'),
array('name' => 'Freddy Hubbard', 'instrument' => 'trumpet'),
array('name' => 'Herbie Hancock', 'instrument' => 'piano'),
array('name' => 'Thelonious Monk', 'instrument' => 'piano')
);
f\group_by('instrument', $musicians);
/**
* Result:
*
* array(
* 'trumpet' => array(
* array('name' => 'Miles Davis', 'instrument' => 'trumpet'),
* array('name' => 'Freddy Hubbard', 'instrument' => 'trumpet'),
* ),
* 'saxophone' => array(
* array('name' => 'John Coltrane', 'instrument' => 'saxophone'),
* ),
* 'piano' => array(
* array('name' => 'Herbie Hancock', 'instrument' => 'piano'),
* array('name' => 'Thelonious Monk', 'instrument' => 'piano')
* )
* )
*/
Using a function:
$strings = array('a', 'as', 'asd', 'aa', 'asdf', 'qwer');
f\group_by('strlen', $strings);
/**
* Result:
* array(
* 1 => array('a'),
* 2 => array('as', 'aa'),
* 3 => array('asd'),
* 4 => array('asdf', 'qwer')
* )
*/
gt
Returns true if the given value is greater than the predicate.
f\gt(10, 100); // true
f\gt(10, 5); // false
Works with instances of Ord.
Imagine a Size
class that takes arbitrary values that are mapped internally to numbers:
$large = new Size('large');
$small = new Size('small');
f\gt($small, $large); // true
f\gt($small, $small); // false
gte
Returns true if the given value is greater than or equal to the predicate.
f\gte(10, 100); // true
f\gte(10, 10); // true
f\gte(10, 9); // false
Works with instances of Ord.
Imagine a Size
class that takes arbitrary values that are mapped internally to numbers:
$large = new Size('large');
$small = new Size('small');
f\gte($small, $large); // true
f\gte($small, $small); // true
head
Get the head of a list.
$spices = ['nutmeg', 'clove', 'cinnamon'];
f\head($spices); // 'nutmeg'
f\head('Miles'); // 'M'
id
Identity function, returns what it’s given.
Useful in places that expect a callback function but you don’t want to mutate anything. For instance
in a when
application.
123 === f\id(123); // true
$spices = ['clove', 'nutmeg', 'allspice', 'cumin'];
$spices === f\id($spices); // true
index_by
Creates an array where items of the given collection are indexed by passing them through the given index function. Note that the last item is preserved, duplicates are purged. If you want to keep all items resulting in the same index, use group_by.
$strings = array('a', 'as', 'asd', 'aa', 'asdf', 'qwer');
f\index_by('strlen', $strings);
/**
* Result:
* array(
* 1 => 'a',
* 2 => 'aa',
* 3 => 'asd',
* 4 => 'qwer'
* )
*/
See also: group_by.
instance
Makes the PHP language a little more expressive. PHP 5.4 allows chaining of new instances like so;
(new Instance())->doSomething();
This function sort of brings this to earlier versions of PHP:
instance(new Instance())->doSomething();
Also accepts strings:
instance('Foo_Bar_Baz'); // new Foo_Bar_Baz()
is_assoc
Checks wether an iterable is associative.
is_assoc([]); // false
is_assoc([1, 2, 3]); // false
is_assoc(['foo' => 'bar']); // true
is_assoc(['foo' => 'bar', 1, 2, 3]); // true
Note that mixed numeric indexes with associative indexes will be considered an associative array.
join
Join a collection, and add a separator between the items.
$spices = ['nutmeg', 'clove', 'cinnamon'];
f\join('_', $spices); // 'nutmeg_clove_cinnamon'
keys
Returns the keys of a collection. Works with iterable object as well, contrary to the native
array_keys
.
f\keys(['a', 'b', 'c']); // [0, 1, 2]
f\keys(['foo' => 123, 'bar' => 456]); // ['foo', 'bar]
keys_where
Returns the keys of a collection where its values match a given predicate function.
$fruits = [
['name' => 'apple', 'color' => 'red'],
['name' => 'banana', 'color' => 'yellow'],
['name' => 'kiwi', 'color' => 'green'],
['name' => 'lime', 'color' => 'green'],
['name' => 'lemon', 'color' => 'yellow']
];
f\keys_where(f\prop_equals('color', 'green'), $fruits) // [2, 3]
last
Returns the last item in a collection.
f\last(['foo', 'bar', 'baz']); // 'baz'
f\last('Miles Davis'); // 's'
lt
Returns true if the given value is less than the predicate.
f\lt(10, 5); // true
f\lt(10, 100); // false
Works with instances of Ord.
Imagine a Size
class that takes arbitrary values that are mapped internally to numbers:
$large = new Size('large');
$small = new Size('small');
f\lt($large, $small); // true
f\lt($small, $small); // false
lte
Returns true if the given value is less than or equal to the predicate.
f\lte(10, 5); // true
f\lte(10, 10); // true
f\lte(10, 100); // false
Works with instances of Ord.
Imagine a Size
class that takes arbitrary values that are mapped internally to numbers:
$large = new Size('large');
$small = new Size('small');
f\lte($large, $small); // true
f\lte($small, $small); // true
map
Curried version of array_map
.
$names = ['Miles Davis', 'John Coltrane'];
f\map(f\split(' '), $names); // [['Miles', 'Davis'], ['John', 'Coltrane']]
match_regex
Returns the matches if the given value matches the regular expression:
f\match_regex('/^\d+$/', '12345'); // ['12345']
f\match_regex('/([a-zA-Z]+) world/', 'Hello world'); // ['Hello world', 'Hello']
f\filter(f\match_regex('/^\d+$/'), ['123', 'abc', '456']); // ['123', '456']
merge_after
Like merge_at, but will add the given item after the specified index.
merge_at
This is array_splice
on steroids. For starters it won’t mutate the given array, but returns a new results.
Furthermore, it’s designed to accept a plethora of input and deal especially with PHP’s awkward sorted associative arrays.
Examples will make this a thousand times more clear:
Splice a value into a numeric array
$numericArray = ['foo', 'bar', 'baz'];
f\merge_at('cux', 2, $numericArray); // ['foo', 'bar', 'cux', 'baz']
Splice a value into an associative array
$assoc = [
'foo' => 123,
'baz' => 789
];
f\merge_at(['bar' => 456], 'baz', $assoc);
// Will return:
// [
// 'foo' => 123,
// 'bar' => 456,
// 'baz' => 789
// ]
Note the way we splice an associative construct into the array – by passing an array with a single [key => value]
construction.
This will work only when the target is an associative array already!
Combine numeric keys with associative targets
You can use numeric indexes with associative arrays, for instance to move an item to the top.
$assoc = [
'foo' => 123,
'baz' => 789
];
f\merge_at(['bar' => 456], 0, $assoc);
// Will return:
// [
// 'bar' => 456,
// 'foo' => 123,
// 'baz' => 789
// ]
Use predicate functions to determine the target
$jazz = [
'Miles' => [
'name' => 'Miles Davis',
'instrument' => 'trumpet'
],
'John' => [
'name' => 'John Coltrane',
'instrument' => 'saxophone'
],
'Herbie' => [
'name' => 'Herbie Hancock',
'instrument' => 'piano'
]
];
$withThelonious = f\merge_at(
['Thelonious' => ['name' => 'Thelonious Monk', 'instrument' => 'piano']],
f\prop_equals('instrument', 'piano'),
$jazz
);
// Will return:
// [
// 'Miles' => [
// 'name' => 'Miles Davis',
// 'instrument' => 'trumpet'
// ],
// 'John' => [
// 'name' => 'John Coltrane',
// 'instrument' => 'saxophone'
// ],
// 'Thelonious' => [
// 'name' => 'Thelonious Monk',
// 'instrument' => 'piano'
// ],
// 'Herbie' => [
// 'name' => 'Herbie Hancock',
// 'instrument' => 'piano'
// ]
// ];
modulo
Modulo of two numbers.
f\modulo(3, 10); // 1
f\modulo(4)(6); // 2
multiply
Multiplies two numbers.
f\multiply(2, 10); // 20
f\multiply(5)(20); // 100
none
Returns true when none of the items in the list match the predicate function.
$numbers = [12, 40, 23, 90];
f\none('is_string', $numbers); // true
f\none(f\gt(50), $numbers); // false
not
Create a new function that negates the outcome of the given function.
$noString = f\not('is_string');
$noString('Hello world'); // false
$noString(123); // true
omit
Create a copy of the given array omitting the specified keys.
This is the opposite of pick.
$miles = ['first' => 'Miles', 'last' => 'Davis', 'instrument' => 'Trumpet'];
f\omit(['instrument'], $miles); // ['first' => 'Miles', 'last' => 'Davis']
once
Impure but very powerful: returns a function that guards the given function from being executed more than once.
$addOne = function ($x) { return $x + 1; };
$addOneOnce = f\once($addOne);
$addOneOnce(10); // 11
$addOneOnce(50); // 11
Subsequent calls will return the result of the first invocation.
partial
Partially apply a function from the left side.
$splitOnSpace = f\partial('explode', ' ');
$splitOnSpace('Hello World'); // ['Hello', 'World']
partial_right
Partially apply a function from the right side.
$isTraversableObject = f\partial_right('is_a', 'Traversable');
$isTraversableObject([1, 2, 3]); // false
pick
Create an array from a subset of properties from the given object.
$spices = ['nutmeg', 'clove', 'cinnamon'];
f\pick([1, 2], $spices); // [1 => 'clove', 2 => 'cinnamon']
$musician = ['first_name' => 'Miles', 'last_name' => 'Davis', 'instrument' => 'trumpet'];
f\pick(['first_name', 'instrument'], $musician); // ['first_name' => 'Miles', 'instrument' => 'trumpet']
pipe
Compose functions together. Execution order is left to right.
It’s the inverse of compose. e.g. pipe(foo, bar, baz)($args)
equals baz(bar(foo($args)))
.
$getInitials = f\pipe(
f\split(' '),
f\map(f\pipe(f\prop(0), f\concat_right('.'))),
f\join(' ')
);
$getInitials('Miles Davis'); // M. D.
If you start at the right side you can follow along with the path your arguments will travel through the functional pipeline.
prop_equals
Check equality with an object property.
$musician = ['first_name' => 'Miles', 'last_name' => 'Davis'];
f\prop_equals('first_name', 'Miles', $musician); // true
It’s curried as usual, making this an excellent predicate function for filter
.
$trumpetPlayers = f\filter(f\prop_equals('instrument', 'trumpet'), $musicians);
prop
Take a property from a collection.
Accepts objects, arrays, even stings.
$miles = ['first_name' => 'Miles', 'last_name' => 'Davis'];
f\prop('first_name', $miles); // 'Miles'
f\prop('last_name', $miles); // 'Davis'
f\prop('date_of_birth', $miles); // null
$foo = new Foo();
$foo->bar = 'baz';
f\prop('bar', $foo); // 'baz'
f\prop('bin', $foo); // null
$name = 'Miles';
f\prop(0, $name); // 'M'
f\prop(3, $name); // 'e'
prop_in
Inspired by Clojure’s get-in, this function takes a bunch of props and returns a value from a nested associative structure matching the given keys.
Doing prop_in(["author", "comments", 0, "body"], $post)
would be the same as writing the
following, but safe since it won’t blow up on missing properties:
$post->author->comments[0]->body;
prop_of
Take a property from a collection. This is the same function as prop but with its arguments flipped. The use-case is common enough to warrant its own function.
$miles = ['first_name' => 'Miles', 'last_name' => 'Davis'];
f\prop_of($miles, 'first_name'); // 'Miles'
f\prop_of($miles, 'last_name'); // 'Davis'
f\prop_of($miles, 'date_of_birth'); // null
$foo = new Foo();
$foo->bar = 'baz';
f\prop_of($foo, 'bar'); // 'baz'
f\prop_of($foo, 'bin'); // null
$name = 'Miles';
f\prop_of($name, 0); // 'M'
f\prop_of($name, 3); // 'e'
An interesting symmetry exists between PHP’s native array access (using square brackets) and this function. For example:
$data = [
'name' => 'spaghetti',
'type' => 'pasta',
];
$food = f\prop_of($data);
$food('type'); // 'pasta'
$data['type']; // 'pasta'
So using a curried version of prop_of
allows to stay close to the familiar way of accessing array
indices. The moment you access an undefined index however, our version turns out to be superior:
$data['origin']; // Notice: Undefined index: origin
$food('origin'); // null
prop_set
Returns a new object with the given $key
set to the given $value
. As shown below prop_set
accepts callables. This means though, that if the input is dynamic, the value should be wrapped in f\always($value)
, as string values could contain valid php function names and lead to unpredictable behavior.
$data = [
'name' => 'spaghetti',
'type' => 'pasta',
];
$data2 = f\prop_set(
'cook',
'Mario',
$data
);
// $data2: ['name' => 'spaghetti', 'type' => 'pasta', 'cook' => 'Mario']
// $data remains untouched: ['name' => 'spaghetti', 'type' => 'pasta']
prop_set
also accepts callables. The function is passed a single argument: the target object.
$data = [
'name' => 'spaghetti',
'type' => 'pasta',
];
$data2 = f\prop_set(
'name_length',
f\compose('strlen', f\prop('name')),
$data
);
// $data2: ['name' => 'spaghetti', 'type' => 'pasta', 'name_length' => 9]
publish
This sounds bananas but this function basically publishes any private method.
This wraps Closure::bindTo, and exists mostly to be able to pass private methods to map
and filter
.
class Foo {
public function filterNumbers(array $collection) {
// This is going to throw an exception:
return f\map([$this, 'isInt'], $collection);
}
private function isInt($n) {
return is_int($n);
}
}
The above will generate an error because isInt
is a private method.
publish
can be used here to publish the create a closure scoped to the object and therefore able to reach the private method:
public function filterNumbers(array $collection) {
return f\map(f\publish('isInt', $this), $collection);
}
Note: an alternative in PHP 7.1 would be Closure::fromCallable()
, which is scoped to the current object automatically.
reduce
Curried version of array_reduce
:
$numbers = [20, 43, 15, 12];
$sum = f\reduce(f\add, 0, $numbers); // 90
reduce_assoc
Version of reduce
tailored to associative datastructures. The key will be the third argument to the callback function.
$assoc = [
'foo' => [1, 2, 3],
'bar' => [4],
'baz' => []
];
$counts = f\reduce_assoc(
function ($acc, $cur, $key) {
return f\prop_set(
$key, count($cur), $acc
);
},
[],
$assoc
); // Returns ["foo" => 3, "bar" => 1, "baz" => 0]
reindex
Alias for the native array_values
.
I tend to use array_values
a lot after array_filter
, but the name array_values
does not really
convey my intent to my fellow developers. Hopefully reindex
does.
Note that filter has this built-in.
$data = [123, 'abc', true, [], 'def'];
$strings = array_filter('is_string', $data); // [1 => 'abc', 4 => 'def']
f\reindex($strings); // [0 => 'abc', 1 => 'def']
reject
Opposite of filter
:
$stuff = ['abc', 123, false, 'def', 456, true];
f\reject('is_int', $stuff); // ['abc', false, 'def', true]
f\reject('is_bool', $stuff); // ['abc', 123, 'def', 456]
rename_keys
Rename keys in an array:
$a = [
'foo' => 123,
'bar' => 456
];
f\rename_keys(
['foo' => 'baz', 'bar' => 'qud'],
$a
); // ['baz' => 123, 'qud' => 456]
Of course it also accepts a transformer function:
$a = [
'foo' => 123,
'bar' => 456
];
f\rename_keys('strrev', $a); // ['oof' => 123, 'rab' => 456]
repeat
Repeats the given function a fixed number of times, returning an array of the accumulated results.
$fiveRandomIds = f\repeat(5, 'uniqid')();
replace
Curried preg_replace
, basically:
f\replace('/(hello)/', 'goodbye', 'hello world'); // 'goodbye world'
f\map(f\replace('/(\d)/', 'x'), ['123', 'abc', '456', ['90']]); // ['xxx', 'abc', 'xxx', ['90']]
some
Returns true if some of the items in the list match the predicate function.
$numbers = [12, 40, 23, 90];
f\some('is_int', $numbers); // true
f\some(f\gt(50), $numbers); // true
f\some('is_string', $numbers); // false
sort
Sort an array. A pure version of the native sort
.
Does not mutate the original array.
$spices = ['Nutmeg', 'Clove', 'Cinnamon'];
$sortedSpices = f\sort($spices); // ['Cinnamon', 'Clove', 'Nutmeg']
$spices; // ['Nutmeg', 'Clove', 'Cinnamon']
sort_by
Use the given function to sort a collection of objects.
To sort by a certain key, use f\prop()
:
$musicians = [
['first_name' => 'Louis', 'last_name' => 'Armstrong', 'instrument' => 'trumpet'],
['first_name' => 'Miles', 'last_name' => 'Davis', 'instrument' => 'trumpet'],
['first_name' => 'John', 'last_name' => 'Coltrane', 'instrument' => 'saxophone'],
);
f\sort_by(f\prop('first_name'), $musicians);
/**
* Result:
*
* [
* ['first_name' => 'John', 'last_name' => 'Coltrane', 'instrument' => 'saxophone'],
* ['first_name' => 'Louis', 'last_name' => 'Armstrong', 'instrument' => 'trumpet'],
* ['first_name' => 'Miles', 'last_name' => 'Davis', 'instrument' => 'trumpet'],
* ]
*/
But another application could be to sort by length:
$groups = [
[1, 2, 3, 4, 5],
[1, 2],
[1, 2, 3]
];
f\sort_by('count', $groups);
/**
* Result:
*
* [
* [1, 2],
* [1, 2, 3]
* [1, 2, 3, 4, 5],
* ]
*/
sort_by_map
Sort a given array by using a reference array:
$roygbiv = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'];
$colors = ['blue', 'orange', 'red', 'indigo'];
f\sort_by_map($roygbiv, $colors); // ['red', 'orange', 'blue', 'indigo']
Works with associative as well as numerically indexed arrays:
$person = [
'dob' => new DateTime('1985-02-11'),
'nationality' => 'Nl',
'name' => 'Harmen'
];
f\sort_by_map(
['name', 'nationality', 'dob'],
$person
); // ['name' => 'Harmen', 'nationality' => 'Nl', 'dob' => new DateTime(...)]
split
Split a string into an array.
$musician = 'Miles Davis';
f\split(' ', $musician); // ['Miles', 'Davis']
subtract
Subtracts the left argument from the right argument.
f\subtract(10, 30); // 20
f\subtract(10)(30); // 20
tail
Get the tail of a list.
$spices = ['nutmeg', 'clove', 'cinnamon'];
f\tail($spices); // ['clove', 'cinnamon']
f\tail('Miles'); // 'iles'
take
Take $n
items of a collection:
f\take(2, ['foo', 'bar', 'baz']); // ['foo', 'bar']
f\take(5, 'Miles Davis'); // ['M', 'i', 'l', 'e', 's']
take_while
Perhaps more interesting than take
is take_while
. It keeps taking items from the front of a
collection until the predicate function returns false
for the given item.
It’s like filter
but could be faster if your collection is ordered, since take_while
will stop
after the first falsey result.
$diary = array(
array('month' => 1, 'day' => 1, 'entry' => '...'),
array('month' => 1, 'day' => 14, 'entry' => '...'),
array('month' => 2, 'day' => 27, 'entry' => '...'),
array('month' => 3, 'day' => 1, 'entry' => '...'),
array('month' => 3, 'day' => 5, 'entry' => '...'),
array('month' => 4, 'day' => 2, 'entry' => '...')
);
$janFebEntries = f\take_while(f\compose(f\lte(2), f\prop('month')), $diary);
/**
* Result:
* array(
* array('month' => 1, 'day' => 1, 'entry' => '...'),
* array('month' => 1, 'day' => 14, 'entry' => '...'),
* array('month' => 2, 'day' => 27, 'entry' => '...'),
* )
*/
See also drop and drop_while.
tap
tap
is a higher-order function that’s helpful to include side-effects in a chain.
It returns a function which calls the given callback but discards its return value and returns the original value.
You can use it to debug a function composition. Consider the following:
$composition = f\compose($functionA, $functionB, $functionC);
If something goes wrong in $functionB
, you might want to log the intermediate value, which can be difficult in a composition like this.
One solution would be:
$composition = f\compose($functionA, function($x) { myLoggingFunction($x); return X; }, $functionB, $functionC);
But that’s a bit cumbersome. This is exactly where tap()
comes in:
$composition = f\compose($functionA, f\tap('myLoggingFunction'), $functionB, $functionC);
An explicit example:
function log($var) {
var_dump($var);
}
$thing = f\tap('log')('my thing'); // dumps "my thing", and stores its value in $thing
truthy
Returns wether the given argument is truthy.
f\truthy(12345); // true
f\truthy(false); // false
f\truthy(''); // false
f\truthy([1, 2, 3]); // true
If given a function, it’ll return a new function waiting for input. The funciton’s return value will be checked for truthiness.
$isArray = f\truthy('is_array');
$isArray(array()); // true
$isArray(123); // false
unary
Creates a new function that passes only the first argument thru to the given function.
$countArgs = function () {
return count(func_get_args());
};
$countArgs(1, 2, 3); // 3
f\unary($countArgs)(1, 2, 3); // 1
This is helpful in situations where giving a function too many arguments explicitly triggers an
error, but you have no control over the amount of arguments passed to the function.
An example is the native is_array
, which accepts one
argument and one argument only. Our some
function however, gives the predicate function both the
item in the collection and its index.
This would cause an error:
$hasArray = f\some('is_array');
$stuff = array('abc', array(), 123, true);
$hasArray($stuff); // Warning: is_array() expects exactly 1 parameter, 2 given
unary
can fix this for you:
$hasArray = f\some(f\unary('is_array'));
$stuff = array('abc', array(), 123, true);
$hasArray($stuff); // true
unique
A version of the native array_unique
which works with multi-dimensional arrays.
$musicians = [
['first_name' => 'Miles', 'last_name' => 'Davis', 'instrument' => 'trumpet'],
['first_name' => 'Miles', 'last_name' => 'Davis', 'instrument' => 'trumpet'],
['first_name' => 'Louis', 'last_name' => 'Armstrong', 'instrument' => 'trumpet'],
['first_name' => 'John', 'last_name' => 'Coltrane', 'instrument' => 'saxophone'],
['first_name' => 'Miles', 'last_name' => 'Davis', 'instrument' => 'trumpet'],
['first_name' => 'John', 'last_name' => 'Coltrane', 'instrument' => 'saxophone'],
['first_name' => 'Louis', 'last_name' => 'Armstrong', 'instrument' => 'trumpet'],
];
f\unique($musicians);
/**
* Result:
*
* [
* ['first_name' => 'Miles', 'last_name' => 'Davis', 'instrument' => 'trumpet'],
* ['first_name' => 'Louis', 'last_name' => 'Armstrong', 'instrument' => 'trumpet'],
* ['first_name' => 'John', 'last_name' => 'Coltrane', 'instrument' => 'saxophone']
* ]
*/
Works with an array of Setoids:
class User implements Setoid {
public function equals(Setoid $that): bool {
return $this->id === $that->id;
}
}
$user1 = new User(1);
$user2 = new User(2);
$user3 = new User(1);
$user4 = new User(2);
$user5 = new User(3);
f\unique([$user1, $user2, $user3, $user4, $user5]); // [$user1, $user2, $user5]
usort
Same as sort
, but is a version of the native usort
.
$spices = ['clove', 'nutmeg', 'allspice', 'cumin'];
f\usort(
function ($a, $b) {
return strlen($a) - strlen($b);
},
$spices
); // ['clove', 'cumin', 'nutmeg', 'allspice']
when
It’s a ternary operator in function form.
Given three scalar values, it’s truly a ternary operation:
f\when(true, 'yep', 'nope'); // 'yep'
f\when(false, 'yep', 'nope'); // 'nope'
But given functions, it gets a little more interesting:
$givePoints = f\when(
f\prop_equals('type', 'superuser'),
f\concat(['points' => 100]),
f\concat(['points' => 50])
);
$customer = ['name' => 'Joe', 'type' => 'regular'];
$givePoints($customer); // ['name' => 'Joe', 'type' => 'regular', 'points' => 50];
$superCustomer = ['name' => 'Hank', 'type' => 'superuser'];
$givePoints($superCustomer); // ['name' => 'Hank', 'type' => 'superuser', 'points' => 100];
The fourth argument is passed into all the given functions.
zip
Creates a new array by pairing up indexes from the supplied arrays.
Takes two or more arguments.
f\zip([1, 2, 3], ['a', 'b', 'c']); // [[1, 'a'], [2, 'b'], [3, 'c']]
null
will be provided for missing indexes:
$miles = array('first' => 'Miles', 'last' => 'Davis', 'instrument' => 'Trumpet');
$john = array('first' => 'John', 'last' => 'Coltrane');
f\zip($miles, $john); // [
// 'first' => ['Miles', 'John'],
// 'last' => ['Davis', 'Coltrane'],
// 'instrument' => ['Trumpet', null]
// ]
Types and Typeclasses
Garp\Functional
implements various typeclasses as interfaces, following The Fantasyland Specification.
Implementing these takes time, which means this list won’t be complete for a while. Pull Requests are very welcome.
A typeclass is an algebraic datatype that adheres to some laws.
The specific implementation is up to you, as long as the laws are obeyed, the object can be used with functions in this library, and objects can be composed to form new functionality.
Read Tom Harding’s Fantas, Eel, And Specification for concrete implementations (in Javascript) of these types and why they’re so incredibly useful.
Test traits
This library offers only interfaces, very little concrete implementations of the types.
You have to implement the classes yourself. How will you know you’ve implemented them correctly?
Garp\Functional
offers traits that can be used to test your objects. The list below includes examples of how to use the traits to test whether your objects obey the algebraic laws defined for the given typeclass.
Full list of implemented TypeClasses
All typeclasses are in the Garp\Functional\Types\TypeClasses
namespace, so the fully qualified name of Setoid
for instance would be Garp\Functional\Types\TypeClasses\Setoid
.
Setoid
Encapsulates equality.
Methods
public function equals(Setoid $that): bool;
Testing
How to test?
use Garp\Functional\Types\Traits\TestsSetoidLaws;
$this->assertObeysSetoidLaws($setoid1, $setoid2, $setoid3);
Pass it 3 of your Setoid-implementing instances and the Setoid laws will be checked against your class.
Works with functions
Ord
Encapsulates order. Given two objects of type Ord
, you can determine which one is the greater of the two.
Extends Setoid.
Methods
public function lte(Ord $that): bool;
Testing
use Garp\Functional\Types\Traits\TestsOrdLaws;
$this->assertObeysOrdLaws($ord1, $ord2, $ord3);
Pass it 3 of your Ord-implementing instances and the Ord laws will be checked against your class.
Works with functions
Semigroup
Encapsulates concatenation. Arrays and string are obvious examples of Semigroups, but Semigroups can also express behavior, like multiplication, addition, or maximisation.
Garp\Functional
actually offers a couple of concrete Semigroup
implementations, see below.
Methods
public function concat(Semigroup $that): Semigroup;
Testing
use Garp\Functional\Types\Traits\TestsSemigroupLaws;
$this->assertObeysSemigroupLaws($semigroup1, $semigroup2, $semigroup3);
Pass it 3 of your Semigroup-implementing instances and the Semigroup laws will be checked against your class.
Works with functions
Monoid
Extends Semigroup
, and provides an identity value. This allows you fold multiple values into a single value, starting from the identity value.
Methods
public static function empty(): Monoid;
Testing
use Garp\Functional\Types\Traits\TestsMonoidLaws;
$this->assertObeysMonoidLaws($myMonoid);
Pass it your Monoid-implementing instance, and the trait will check if it obeys the identity laws.
Works with functions
Full list of implemented Types
The following is a list of types: utility classes implementing the above typeclasses.
Note that generally, they encapsulate behavior, which might be different from what you expect from classic OOP paradigms.
For instance, Any
expresses the f\some
function as an object.
All types are in the Garp\Functional\Types
namespace, so the fully qualified name of Max
is Garp\Functional\Types\Max
.
Max
A Monoid, keeping the greater value:
(new Max(100))->concat(new Max(200))->value; // 200
(new Max(200))->concat(new Max(100))->value; // 200
f\fold(Max::class, [10, 490, 50, 92])->value; // 490
Min
A Monoid, keeping the smaller value:
(new Min(100))->concat(new Min(200))->value; // 100
(new Min(200))->concat(new Min(100))->value; // 100
f\fold(Min::class, [10, 490, 50, 92])->value; // 10
Sum
A Monoid, producing the sum of the two values:
(new Sum(100))->concat(new Sum(200))->value; // 300
f\fold(Sum::class, [10, 90, 200])->value; // 300
Product
A Monoid, producing the product of the two values:
(new Product(50))->concat(new Product(5))->value; // 250
f\fold(Product::class, [10, 5, 5])->value; // 250
Any
A Monoid, encapsulating boolean logic for OR:
(new Any(true))->concat(new Any(false))->value; // true
f\fold(Any::class, [true, false, false, true])->value; // true
All
A Monoid, encapsulating boolean logic for AND:
(new All(false))->concat(new All(true))->value; // false
f\fold(All::class, [true, false, false, true])->value; // false
StringM
A Monoid implementation of string (unfortunately, String
is reserved).
(new StringM('foo'))->concat(new StringM('bar'))->value; // "foobar"
f\fold(StringM::class, ['foo', 'bar', 'baz'])->value; // "foobarbaz"