PHP Functional Programming library. Monads and common use functions.
Supported installation method is via composer:
$ composer require fp4php/functional
Please refer to the fp4php/functional-psalm-plugin repository.
Typesafe and concise.
Powerful combination: Collections + Option monad.
<?php
use Fp\Collections\ArrayList;
use Fp\Functional\Option\Option;
use function Fp\Evidence\of;
use function Fp\Evidence\proveString;
class PgSqlCurrencyArrayType extends Type
{
public function convertToDatabaseValue($value, AbstractPlatform $platform): string
{
$currencies = Option::fromNullable($value)
->filter(is_iterable(...))
->getOrElse([]);
return ArrayList::collect($currencies)
->flatMap(of(Currency::class))
->map(fn(Currency $currency) => $currency->getCurrencyCode())
->mkString('{', ',', '}');
}
/**
* @return ArrayList<Currency>
*/
public function convertToPHPValue($value, AbstractPlatform $platform): ArrayList
{
$csv = Option::fromNullable($value)
->flatMap(proveString(...))
->map(fn(string $pgSqlArray) => trim($pgSqlArray, '{}'))
->getOrElse('');
return ArrayList::collect(explode(',', $csv))
->filterMap($this->parseCurrency(...));
}
/**
* @return Option<Currency>
*/
public function parseCurrency(string $currencyCode): Option
{
return Option::try(fn() => Currency::of($currencyCode));
}
}
- Type safety
<?php
use Fp\Collections\NonEmptyLinkedList;
/**
* Inferred type is NonEmptyLinkedList<1|2|3>
*/
$collection = NonEmptyLinkedList::collectNonEmpty([1, 2, 3]);
/**
* Inferred type is NonEmptyLinkedList<int>
*
* Literal types are dropped after map transformation,
* but NonEmpty collection prefix has been kept
*/
$mappedCollection = $collection->map(fn($elem) => $elem - 1);
/**
* Inferred type is LinkedList<positive-int>
* NonEmpty prefix has been dropped
*/
$filteredCollection = $mappedCollection->filter(fn(int $elem) => $elem > 0);
<?php
use Tests\Mock\Foo;
use Tests\Mock\Bar;
use Fp\Collections\NonEmptyArrayList;
$source = [new Foo(1), null, new Bar(2)];
/**
* Inferred type is ArrayList<Foo|Bar>
* Null type was removed
* NonEmpty prefix was removed
*/
$withoutNulls = NonEmptyArrayList::collectNonEmpty($source)
->filter(fn(Foo|Bar|null $elem) => null !== $elem);
/**
* Inferred type is ArrayList<Foo>
* Bar type was removed
*/
$onlyFoos = $withoutNulls->filter(fn($elem) => $elem instanceof Foo);
- Covariance
<?php
use Fp\Collections\NonEmptyLinkedList;
class User {}
class Admin extends User {}
/**
* @param NonEmptyLinkedList<User> $collection
*/
function acceptUsers(NonEmptyLinkedList $collection): void {}
/**
* @var NonEmptyLinkedList<Admin> $collection
*/
$collection = NonEmptyLinkedList::collectNonEmpty([new Admin()]);
/**
* You can pass collection of admins instead of users
* Because of covariant template parameter
*/
acceptUsers($collection);
- Immutability
<?php
use Fp\Collections\LinkedList;
$originalCollection = LinkedList::collect([1, 2, 3]);
/**
* $originalCollection won't be changed
*/
$prependedCollection = $originalCollection->prepended(0);
/**
* $prependedCollection won't be changed
*/
$mappedCollection = $prependedCollection->map(fn(int $elem) => $elem + 1);
- Null safety
<?php
use Fp\Functional\Option\Option;
use Fp\Collections\ArrayList;
/**
* @var ArrayList<int> $collection
*/
$collection = getCollection();
/**
* @return Option<float>
*/
function div(int $a, int $b): Option
{
return Option::when(0 !== $b, fn() => $a / $b);
}
/**
* It's possible there is no first collection element above zero
* or divisor is zero.
*
* In this case the execution will short circuit (stop)
* and no Null Pointer Exception will be thrown.
*/
$collection
->first(fn(int $elem) => $elem > 0)
->map(fn(int $elem) => $elem + 1)
->flatMap(fn(int $elem) => div($elem, $elem - 1))
->getOrElse(0)
<?php
use Tests\Mock\Foo;
use Fp\Functional\Option\Option;
use function Fp\Evidence\proveTrue;
use function Fp\Evidence\proveNonEmptyList;
/**
* Inferred type is Option<Foo>
*/
$maybeFooMaybeNot = Option::do(function() use ($untrusted) {
// If $untrusted is not null then bind this value to $notNull
$notNull = yield Option::fromNullable($untrusted);
// If $notNull is non-empty-list<Tests\Mock\Foo> then bind this value to $nonEmptyListOfFoo
$nonEmptyList = yield proveNonEmptyList($notNull, of(Foo::class));
// Continue computation if $nonEmptyList contains only one element
yield proveTrue(1 === count($nonEmptyList));
// I'm sure it's Foo object
return $nonEmptyList[0];
});
// Inferred type is Tests\Mock\Foo
$foo = $maybeFooMaybeNot->getOrCall(fn() => new Foo(0));
- Install dependencies
$ sudo apt install pandoc
- Generate doc from src
$ make