PHP Functional Programming library. Monads and common use functions.

Overview

Functional PHP

PHP Functional Programming library. Monads and common use functions.

psalm level psalm type coverage phpunit coverage

Documentation

Installation

Composer

$ composer require whsv26/functional

Enable psalm plugin (optional)

To improve type inference for particular functions

$ vendor/bin/psalm-plugin enable Fp\\Psalm\\FunctionalPlugin

Contribution

Build documentation

  1. Install dependencies
whsv26@whsv26:~$ sudo apt install pandoc
  1. Generate doc from src
whsv26@whsv26:~$ make
Comments
  • Improve type inference for Option and Either assertion

    Improve type inference for Option and Either assertion

    This PR aim to fix wrong type inference for Option and Either assertion methods. Example of correct inference:

    /**
     * @return Option<int>
     */
    function getOption(): Option
    {
        return Option::none();
    }
    
    /** @psalm-param (Some<int>) $_some */
    function assertIsSome($_some): void {}
    
    /** @psalm-param (None) $_none */
    function assertIsNone($_none): void {}
    
    function test(): void
    {
        $opt = getOption();
    
        if ($opt->isSome()) {
            assertIsSome($opt); // $opt is Some<int> at this point
        } else {
            assertIsNone($opt); // $opt is None, before it was Option<int>
        }
    }
    
    opened by klimick 4
  • Do we need function 'but_last' ?

    Do we need function 'but_last' ?

    Been thinking about function but_last, like this

    I am not sure if this function is necessary. Also we just may code something like this:

    $withoutLast = \Fp\Collection\reverse(
        \Fp\Collection\tail(
            \Fp\Collection\reverse($collection)
        )
    );
    
    // or simplified version
    
    $withoutLast = reverse(tail(reverse($collection)));
    

    Maybe native function but_last would be more understandable and less resource consuming? I would be pleasured to hear your opinion.

    opened by nzour 3
  • Q: why does Option::get() may return null?

    Q: why does Option::get() may return null?

    Just noticed that Option::get() may return null value. Are there any specific reasons for this behavior?

    I mean, there are other implementations of Option and non of them do return null value.

    Java

    public T get() {
        if (value == null) {
            throw new NoSuchElementException("No value present");
        }
    
        return value;
    }
    

    Scala

    def get: Nothing = throw new NoSuchElementException("None.get")
    

    Rust

    pub fn unwrap(self) -> T {
        match self {
            Some(val) => val,
            None => panic!("called `Option::unwrap()` on a `None` value"),
        }
    }
    

    Maybe there is a contract for Option to have that kind of behavior?

    opened by nzour 3
  • Add method match on Option and Either?

    Add method match on Option and Either?

    Greetings, sir. Just got a few thoughts about implementing method match on Option and Either.

    Like C# functional lib has

    This would look like

    Option:

    $hello = Option::of('Hello');
    
    $result = $hello->match(
        some: fn(string $v) => "{$v} world",
        none: fn() => 'Hello from none'
    );
    

    Either:

    /** @var Either<string, FooInterface> $found */
    $found = Some::of(new FooImpl());
    
    $found->match(
        right: \Fp\id,
        left: function(string $errorMessage) {
            // sorry for side effect here
            $this->logger->logWarning($errorMessage);
    
            return new BarImpl();
        },
    );
    

    So the method signature would look like:

    Option:

    /**
     * @template TR
     * @param callable(A): TR $some
     * @param callable(): TR $none
     * @return TR
     */
    public function match(callable $some, callable $none): mixed
    {
    

    Either:

    /**
     * @template TR
     * @param callable(R): TR $right
     * @param callable(L): TR $left
     * @return TR
     */
    public function match(callable $right, callable $left): mixed
    {
    

    I think this may be useful. What do you think?

    opened by nzour 3
  • Any cases to add @psalm-pure for really pure functions?

    Any cases to add @psalm-pure for really pure functions?

    In some cases it may be useful to use few functions inside pure context.

    Example:

    /**
     * @psalm-immutable
     */
    class Foo
    {
        /** 
         * @var list<Bar>
         */
        public array $bars;
    
        public function __construct(Bar $first, Bar ...$rest)
        {
            // Impure method call issue occurred here (because Foo is immutable, so requires pure context)
            $this->bars = asList([$first], $rest);
        }
    }
    

    Of course, there are more ways to compile list, but if we have asList why wouldn't we use it whenever we want?

    I was just thinking about function asList, maybe there are few others function that are actually pure but not annotated yet

    opened by nzour 2
  • Partition function type inference issues

    Partition function type inference issues

    /**
     * @return list<array{mixed, Either<string, int>}>
     */
    function getValues() { ... }
    
    
    $values = getValues();
    
    $res = partition($values, static function (array $v) {
        [, $either] = $v; // type of $v is lost somewhere, it actually is array<TKey: array-key, TValue: mixed>
      
        return $either instanceof Right;
    });
    
    // but for $res variable - type inference would work correctly
    
    opened by nzour 1
  • Allow `null` values for `Option`

    Allow `null` values for `Option`

    /**
     * Returns value from $array by $path if present
     *
     * @param non-empty-list<string> $path
     * @psalm-pure
     */
    function dotAccess(array $path, array $array): Option
    {
        $key = $path[0];
        $rest = array_slice($path, offset: 1);
    
        if (empty($rest) && array_key_exists($key, $array)) {
            return Option::of($array[$key]);
        }
    
        if (array_key_exists($key, $array) && is_array($array[$key])) {
            return dotAccess($rest, $array[$key]);
        }
    
        return new None();
    }
    
    $arr = [
        'person' => [
            'name' => 'Jhon',
            'address' => null,
        ],
    ];
    
    // Prints Some('Jhon') - good
    print_r(dotAccess(['person', 'name'], $arr));
    
    // Prints None - not good
    print_r(dotAccess(['person', 'address'], $arr));
    
    
    opened by klimick 1
  • Option::do implementation incorrect

    Option::do implementation incorrect

    This snippet type checks by psalm, but at runtime it fails:

    /**
     * @return Option<stdClass>
     */
    function toSomething(mixed $_): Option
    {
        return Option::none();
    }
    
    $items = [];
    
    Option::do(function() use ($items) {
        $mapped = [];
    
        /** @psalm-suppress MixedAssignment */
        foreach ($items as $item) {
            $mapped[] = yield toSomething($item);
        }
    
        return $mapped;
    });
    
    PHP Fatal error:  Uncaught Error: Call to a member function isEmpty() on null in /home/app/vendor/whsv26/functional/src/Fp/Functional/Option/Option.php:82
    Stack trace:
    #0 /home/app/run.php(26): Fp\Functional\Option\Option::do()
    #1 {main}
      thrown in /home/app/vendor/whsv26/functional/src/Fp/Functional/Option/Option.php on line 82
    
    Process finished with exit code 255
    

    This only happens if no yield has been called in Option::do.

    opened by klimick 1
  • Add Either::mapLeft

    Add Either::mapLeft

    Currently there is no way to change left side of the Either.

    Syntactic example where it can be useful:

    /**
     * @return Either<ApplicationError, OrderOutput>
     */
    function createOrder(OrderInput $input, OrderService $service): Either
    {
        return $service->create($input)
            ->map(fn(Order $o) => OrderOutput::from($o))
            ->mapLeft(fn(OrderError $e) => ApplicationError::from($e));
            // map concrete domain error to more generic error
    }
    
    
    opened by klimick 1
  • Fp\Json\jsonDecode is unsafe

    Fp\Json\jsonDecode is unsafe

    If you run the following piece of code, it will lead to a runtime error:

    <?php
    
    use Fp\Functional\Either\Right;
    use Fp\Functional\Either\Left;
    use function Fp\Json\jsonDecode;
    
    require __DIR__ . '/vendor/autoload.php';
    
    $decoded = jsonDecode('"kek"')
        ->map(fn(array $val) => array_merge($val, ['lol' => '_']));
    
    
    
    PHP Fatal error:  Uncaught TypeError: {closure}(): Argument #1 ($val) must be of type array, string given, called in /home/php/whsv26/functional/src/Fp/Functional/Either/Either.php on line 83 and defined in /home/php/whsv26/functional/run.php:12
    Stack trace:
    #0 /home/php/whsv26/functional/src/Fp/Functional/Either/Either.php(83): {closure}()
    #1 /home/php/whsv26/functional/run.php(15): Fp\Functional\Either\Either->flatMap()
    #2 {main}
      thrown in /home/php/whsv26/functional/run.php on line 12
    
    

    Because json_decode can deserialize literals like numbers, strings, booleans.

    bug 
    opened by klimick 1
  • ArrayList::prependedAll infer ArrayList<T|mixed> instead ArrayList<T>

    ArrayList::prependedAll infer ArrayList instead ArrayList

    /**
     * @param ArrayList<int> $numbers
     */
    function test(ArrayList $numbers): void {}
    
    /**
     * @return ArrayList<int>
     */
    function getNumbers(): ArrayList
    {
        return ArrayList::empty();
    }
    
    /**
    ERROR: MixedArgumentTypeCoercion
        at /app/run.php:34:5
        Argument 1 of test expects Fp\Collections\ArrayList<int>, parent type Fp\Collections\ArrayList<int|mixed> provided (see https://psalm.dev/194)
        getNumbers()->prepended(getNumbers())
    */
    test(
        getNumbers()->prependedAll(getNumbers())
    );
    
    bug psalm 
    opened by klimick 0
Releases(v5.2.0)
  • v5.2.0(Dec 5, 2022)

    New functions

    • contains: Checks if a value exists in an collection.
    • sequenceEitherMerged: Same as sequenceEither, but merge all errors to non-empty-list.

    Either updates

    • Either::filterOrElse: Passing R to the $left callback
    Source code(tar.gz)
    Source code(zip)
  • v5.1.0(Nov 28, 2022)

    New functions

    • proveUnion: allows to combine multiple evidences.
    • proveNull: proveUnion($mixed, [proveInt(...), proveNull(...)]) (inferred as Option<int|null>).
    • asEnumOf: allows to cast string|int to enum instance.

    Static analysis improvement

    • toMergedArray: More specific type.
    Source code(tar.gz)
    Source code(zip)
  • v5.0.0(Nov 21, 2022)

    In addition to new functionality, this release contains backwards-incompatible changes. Read about BC here: https://github.com/fp4php/functional/blob/master/UPGRADING-v5.md

    New collection ops

    • groupMap: groupBy + map combinator
    • groupMapReduce: groupBy + map + fold combinator
    • reindex: Index each elements by callback and returns HashMap
    • firstMap: Like first but use Option data type instead bool
    • lastMap: Like last but use Option data type instead bool
    • traverseOption, traverseEither: See example
    • sorted: $callback now is optional. Will be use default PHP comparison mechanism without $callback.
    • sortedBy: allow specify value for sorting.
    • sortedDesc: allow specify value for ascending sorting.
    • sortedDescBy: allow specify value for desceding sorting.
    • flatten: Turns iterable<iterable<T>> to iterable<T>
    • zipWithKeys: Turns Seq<T> to Seq<array{int, T}>
    • init: Returns all elements except last
    • partition: Separates collection by predicate
    • partitionMap: Separates collection by predicate (but using Either)
    • toString: Returns string representation of datatype.
    • toStream: Turns collection to Stream
    • toMergedArray: Like array_merge but as method
    • toNonEmptyMergedArray: Non-empty version of toMergedArray
    • *N combinators: (mapN, flatMapN and others...)

    Psalm plugin improvement

    Docs

    Source code(tar.gz)
    Source code(zip)
  • v4.20.0(Aug 23, 2022)

  • v4.19.0(Aug 19, 2022)

  • v4.18.0(Jun 15, 2022)

  • v4.17.0(Jun 2, 2022)

  • v4.16.1(Feb 8, 2022)

  • v4.16.0(Dec 28, 2021)

  • v4.15.0(Dec 27, 2021)

  • v4.14.0(Dec 26, 2021)

  • v4.13.0(Dec 24, 2021)

    • Set intersect operation
    • NonEmptySet intersect operation
    • Set diff operation
    • NonEmptySet diff operation
    >>> HashSet::collect([1, 2, 3])
        ->intersect(HashSet::collect([2, 3]))->toArray();
    => [2, 3]
    
    
    >>> HashSet::collect([1, 2, 3])
        ->diff(HashSet::collect([2, 3]))->toArray();
    => [1]
    
    Source code(tar.gz)
    Source code(zip)
  • v4.12.0(Dec 24, 2021)

    • asPairs function
    • asPairsGenerator function
    • fix asGenerator namespace (Callable -> Cast)
    >>> asPairs(['a' => 1, 'b' => 2]);
    => [['a', 1], ['b', 2]]
    
    >>> Stream::emits(asPairsGenerator(['a' => 1, 'b' => 2]));
    => [['a', 1], ['b', 2]]
    
    Source code(tar.gz)
    Source code(zip)
  • v4.11.0(Dec 21, 2021)

  • v4.10.0(Dec 18, 2021)

    • fix Option::fold and Either::fold signatures.
    • add Option::toArrayList cast method.
    >>> Option::some(1)
        ->toArrayList(ArrayList::singleton(...))
        ->toArray();
    => [1]
    
    >>> Option::some([1])
        ->toArrayList(ArrayList::collect(...))
        ->toArray();
    => [1]
    
    >>> Option::none()
        ->toArrayList(ArrayList::collect(...))
        ->toArray();
    => []
    
    Source code(tar.gz)
    Source code(zip)
  • v4.9.0(Dec 18, 2021)

    • Option getOrThrow method
    >>> Option::some(1)->getOrThrow(fn() => new RuntimeException('???'));
    => 1
    
    >>> Option::none()->getOrThrow(fn() => new RuntimeException('???'));
    RuntimeException with message '???'
    
    Source code(tar.gz)
    Source code(zip)
  • v4.8.1(Dec 14, 2021)

  • v4.8.0(Dec 13, 2021)

    • range collector for Seq
    • replace 0|positive-int with int for range start and stop parameters.
    >>> ArrayList::range(0, 10, 2)->toArray();
    => [0, 2, 4, 6, 8]
    
    >>> ArrayList::range(0, 3)->toArray();
    => [0, 1, 2]
    
    >>> ArrayList::range(0, 0)->toArray();
    => []
    
    Source code(tar.gz)
    Source code(zip)
  • v4.7.0(Dec 8, 2021)

  • v4.6.0(Dec 1, 2021)

    • mkString operation for Seq and Stream
    >>> LinkedList::collect([1, 2, 3])->mkString()
    => '1,2,3'
    
    >>> LinkedList::collect([1, 2, 3])->mkString("(", ",", ")")
    => '(1,2,3)'
    
    >>> LinkedList::collect([])->mkString("(", ",", ")")
    => '()'
    
    Source code(tar.gz)
    Source code(zip)
  • v4.5.0(Dec 1, 2021)

    • toAssocArray cast for Map
    >>> HashMap::collectPairs([['a',  1], ['b', 2]])->toAssocArray();
    => Some(['a' => 1, 'b' => 2])
    
    >>> ArrayList::collect([['a',  1], ['b', 2]])->toHashMap(fn($entry) => entry)->toAssocArray();
    => Some(['a' => 1, 'b' => 2])
    
    >>> HashMap::collectPairs([[new Foo(), 1], [new Foo(), 2]])->toAssocArray();
    => None
    
    Source code(tar.gz)
    Source code(zip)
  • v4.4.0(Nov 30, 2021)

  • v4.3.0(Nov 23, 2021)

  • v4.2.0(Oct 2, 2021)

    • intersperse and zip operations have been added for Seq implementations
    • Drop traits
    • Move collection and stream operation implementations into independent classes
    Source code(tar.gz)
    Source code(zip)
  • v4.1.1(Sep 21, 2021)

  • v4.1.0(Sep 17, 2021)

  • v4.0.1(Sep 9, 2021)

  • v4.0.0(Sep 8, 2021)

    • Option::cond method has been added
    • tap collection operation has been added
    • Set::subsetOf, NonEmptySet::subsetOf operations have been added
    • Seq::sorted, NonEmptySeq::sorted methods have been added
    • objectOf and classOf functions have been added
    • Fix Either::try method unsoundness. Change Left type up to Throwable
    • Drop deprecated isSequence function
    • Replace any, anyOf with exists, existsOf
    • Fix exists function unsoundness. Change needle with just predicate callable.
    • Drop deprecated reduceNel, reduceNer functions
    • Fix fold and reduce function signatures
    • pluck function plugin support for object-like arrays
    • NonEmptySeqOps::groupBy operation has been added
    • Additional cast operations for collections
    • partitionOf return type generalization via psalm plugin
    • Map::get method return type refining from Option to Some for literal key types
    • Fix Map::every signature. Replace Value+key arguments with Entry argument
    • Add explicit getIterator methods for interfaces for better foreach support
    • NonEmptyHashMap has been added
    • Drop Semigroup and Monoid instance fabric methods from abstract classes
    • Drop getNamedTypes function.
    • Semigroup and Monoid instances for collection classes
    • filterMap operation has been added [by @klimick]
    • Rename group function to groupBy
    • Change tail function return type from array to list
    • Rename Map::reindex to Map::mapKeys. Add Map::mapValues alias for Map::map method
    • Psalm plugin for conditionally pure functions [by @klimick]
    • Drop butLast function
    • Replace IndexedSeq and LinearSeq interfaces with just Seq interface
    • Replace NonEmptyIndexedSeq and NonEmptyLinearSeq interfaces with just NonEmptySeq interface
    • Rename $rhs to $that for HashContract
    • Replace EmptyCollectionException with Option for non-empty collect methods
    • Rename HashMap::collecIterable to HashMap::collect and HashMap::collect to HashMap::collectPairs
    Source code(tar.gz)
    Source code(zip)
  • v3.0.1(Aug 29, 2021)

  • v3.0.0(Aug 28, 2021)

    • drop jsonSearch function because of additional composer dependency
    • drop mtdowling/jmespath.php package dependency
    • replace reversed value+key arguments with short living Entry objects for HashMap
    • move Option::getOrElse callable argument type to Option::getOrCall
    • additional Set ops
    • drop Map::reduce @experimental method
    Source code(tar.gz)
    Source code(zip)
Owner
Alexander Sv.
Alexander Sv.
PHP code scanner to use with gettext/gettext

PHP code scanner to use with gettext/gettext

Gettext 12 Nov 11, 2022
All In 1 Spam Tool For Termux Users Subscribe Us (Noob Hackers) some shit heads are trying to abuse this script so don't worry about them ...let them hallucinate ...but you are free to use this script

ABOUT TOOL : SPAMX is a all in one Bombing+Spam tool from this tool you can send anonymous messages to your target without showing your real number an

N17R0 449 Jan 7, 2023
Micro PHP benchmark library

Ubench Ubench is a PHP micro library for benchmark Installation Note: If you are looking for this package for laravel application then install it from

Jeremy Perret 554 Nov 25, 2022
Library for counting the lines of code in PHP source code

sebastian/lines-of-code Library for counting the lines of code in PHP source code. Installation You can add this library as a local, per-project depen

Sebastian Bergmann 715 Jan 5, 2023
PHPMD is a spin-off project of PHP Depend and aims to be a PHP equivalent of the well known Java tool PMD. PHPMD can be seen as an user friendly frontend application for the raw metrics stream measured by PHP Depend.

PHPMD PHPMD is a spin-off project of PHP Depend and aims to be a PHP equivalent of the well known Java tool PMD. PHPMD can be seen as an user friendly

PHP Mess Detector 2.1k Jan 8, 2023
Search PHP source code for function & method calls, variables, and more from PHP.

Searching PHP source code made easy Search PHP source code for function & method calls, variable assignments, classes and more directly from PHP. Inst

Permafrost Software 22 Nov 24, 2022
PHP_CodeSniffer tokenizes PHP files and detects violations of a defined set of coding standards.

About PHP_CodeSniffer is a set of two PHP scripts; the main phpcs script that tokenizes PHP, JavaScript and CSS files to detect violations of a define

Squiz Labs 9.9k Jan 4, 2023
Phan is a static analyzer for PHP. Phan prefers to avoid false-positives and attempts to prove incorrectness rather than correctness.

Phan is a static analyzer for PHP that prefers to minimize false-positives. Phan attempts to prove incorrectness rather than correctness. Phan looks f

null 5.4k Jan 7, 2023
Beautiful and understandable static analysis tool for PHP

PhpMetrics PhpMetrics provides metrics about PHP project and classes, with beautiful and readable HTML report. Documentation | Twitter | Contributing

PhpMetrics 2.3k Dec 22, 2022
:crystal_ball: Better Reflection is a reflection API that aims to improve and provide more features than PHP's built-in reflection API.

Better Reflection Better Reflection is a reflection API that aims to improve and provide more features than PHP's built-in reflection API. Why is it b

Roave, LLC 1.1k Dec 15, 2022
A set of tools for lexical and syntactical analysis written in pure PHP.

Welcome to Dissect! master - this branch always contains the last stable version. develop - the unstable development branch. Dissect is a set of tools

Jakub Lédl 221 Nov 29, 2022
PHP completion, refactoring, introspection tool and language server.

Phpactor This project aims to provide heavy-lifting refactoring and introspection tools which can be used standalone or as the backend for a text edit

Phpactor 882 Jan 1, 2023
Instant Upgrades and Instant Refactoring of any PHP 5.3+ code

Rector - Speedup Your PHP Development Rector helps you with 2 areas - major code changes and in daily work. Do you have a legacy code base? Do you wan

RectorPHP 6.5k Jan 8, 2023
Deptrac is a static code analysis tool for PHP that helps you communicate, visualize and enforce architectural decisions in your projects

Deptrac is a static code analysis tool for PHP that helps you communicate, visualize and enforce architectural decisions in your projects. You can freely define your architectural layers over classes and which rules should apply to them.

QOSSMIC GmbH 2.2k Dec 30, 2022
phpcs-security-audit is a set of PHP_CodeSniffer rules that finds vulnerabilities and weaknesses related to security in PHP code

phpcs-security-audit v3 About phpcs-security-audit is a set of PHP_CodeSniffer rules that finds vulnerabilities and weaknesses related to security in

Floe design + technologies 655 Jan 3, 2023
A PHP parser written in PHP

PHP Parser This is a PHP 5.2 to PHP 8.0 parser written in PHP. Its purpose is to simplify static code analysis and manipulation. Documentation for ver

Nikita Popov 15.9k Jan 3, 2023
A PHP VM implementation in PHP

PHPPHP A PHP VM implementation written in PHP. This is a basic VM implemented in PHP using the AST generating parser developed by @nikic To see what's

Anthony Ferrara 801 Dec 25, 2022
Provides functionality that helps writing PHP code that has runtime-specific (PHP / HHVM) execution paths

sebastian/environment This component provides functionality that helps writing PHP code that has runtime-specific (PHP / HHVM) execution paths. Instal

Sebastian Bergmann 6.5k Jan 3, 2023
A full-scale PHP sandbox class that utilizes PHP-Parser to prevent sandboxed code from running unsafe code

A full-scale PHP 7.4+ sandbox class that utilizes PHP-Parser to prevent sandboxed code from running unsafe code. It also utilizes FunctionParser to di

Corveda 192 Dec 10, 2022