fp4php/functional

repository·master·Indexed 42 days ago

https://github.com/fp4php/functional

A PHP library providing functional programming primitives for type safety and conciseness. It features the Option and Either monads for null safety and error handling, specialized immutable collections (ArrayList, LinkedList, HashSet, HashMap), and a suite of functional utilities including N and KV combinators, partial application, and type-proving functions. Designed for integration with Psalm for enhanced static analysis.

Tokens
28.4K
Snippets
96
Records
107
Agent score
52%

What's inside fp4php/functional

  1. Documentation structure

    master

    The library is organized into several core modules. For detailed API references, consult the following documentation sections:

    • Collections: Data structures and their methods.
    • Streams: Stream processing capabilities.
    • Monads: Implementation and usage of monadic types (e.g., Option).
    • Functions: Common functional utility functions.
    • Combinators: Higher-order functions for combining other functions.
  2. Use Stream terminal operations to execute logic

    master

    To trigger the execution of a stream pipeline, you must call a terminal operation. Common terminal operations include:

    • toList(): Converts the stream to a list.
    • toFile(string $path): Writes the stream elements to a file.
    • drain(): Executes the stream without returning a value (useful for side effects).
    • fold(mixed $initial)(callable $accumulator): Reduces the stream to a single value.
    • lines(): Prints elements to stdout (often used with time-based streams).
  3. Use the Either monad for error handling

    master

    The Either monad represents a computation that can result in either a success or an error. It consists of two subclasses:

    • Right: Represents a successful outcome.
    • Left: Represents an error outcome.

    Use Either to handle errors explicitly. If any step in a flatMap chain returns a Left, the subsequent steps are skipped (short-circuited), and the error is propagated. You can create successful results with Either::right($value) and error results with Either::left($errorValue). Use .get() to retrieve the result (which will be either the success value or the error value).

    <?php
    
    declare(strict_types=1);
    
    use Fp\Functional\Either\Either;
    
    // Example of chaining operations that might return errors
    $statusOrErrorMessage = getUserById(654)
        ->flatMap(fn(User $user) => getUserFirstOrder($user))
        ->flatMap(fn(Order $order) => getOrderTrackNumber($order))
        ->flatMap(fn(TrackingNumber $number) => getTrackingStatus($number))
        ->get();
  4. Implement HashContract for custom object keys in HashMap

    master

    When using objects as keys in a NonEmptyHashMap, the default behavior relies on spl_object_hash. If you need equality and hashing to be based on object properties rather than instance identity, implement the HashContract interface.

    Required methods:

    • equals(mixed $that): bool: Defines how two objects are considered equal.
    • hashCode(): string: Returns a string representation of the object's hash used for bucket lookups.
    <?php
    
    use Fp\Collections\HashContract;
    
    final class Foo implements HashContract
    {
        public function __construct(
            public readonly int $a,
            public readonly bool $b = true,
        ) {}
    
        public function equals(mixed $that): bool
        {
            return $that instanceof self
                && $this->a === $that->a
                && $this->b === $that->b;
        }
    
        public function hashCode(): string
        {
            return md5(implode(',', [$this->a, $this->b]));
        }
    }
  5. Use KV combinators for Key-Value collections

    master

    Since version 5, Fp\Collections\Entry has been removed to improve IDE autocompletion. Instead, all collection methods now have a dedicated Key-Value (KV) version that allows you to access both the key and the value in your callback.

    HashMap methods

    If you are using Fp\Collections\HashMap, use mapKV instead of map when you need access to the keys.

    Collection functions

    For standard arrays, use Fp\Collection\mapKV instead of Fp\Collection\map to access keys. In v5+, the key is passed as the first argument to the callback, which resolves previous Psalm compatibility issues.

    use Fp\Collections\HashMap;
    use function Fp\Collection\map;
    use function Fp\Collection\mapKV;
    
    // Using HashMap methods
    $hashMap->map(fn(int $value) => $value + 2);
    $hashMap->mapKV(fn(int $key, int $value) => $key + $value);
    
    // Using standalone functions for arrays
    map($array, fn(int $value) => $value + 2);
    mapKV($array, fn(int $key, int $value) => $key + $value);
  6. Use Option monad for null safety and short-circuiting

    master

    The Option monad allows you to chain operations that might fail or return null without explicit null checks. If any step in the chain results in an empty Option, the subsequent steps are skipped (short-circuited), preventing Null Pointer Exceptions.

    Common methods include:

    • Option::fromNullable($value): Creates an Option from a potentially null value.
    • Option::try(fn() => ...): Wraps a computation that might throw an exception.
    • Option::when(condition, fn()): Returns an Option containing the result of the function only if the condition is met.
    • flatMap(fn): Chains operations that return another Option.
    • getOrElse($default): Extracts the value or returns a default if the Option is empty.
    use Fpunctional\Option\Option;
    use Fp\
    Collections\ArrayList;
    
    // Example of safe chaining with short-circuiting
    $result = $collection
        ->first(fn(int $elem) => $elem > 0)
        ->map(fn(int $elem) => $elem + 1)
        ->flatMap(fn(int $elem) => div($elem, $elem - 1))
        ->getOrElse(0);
  7. Use HashMap for key-value storage

    master

    The HashMap class is an implementation of the Map<TK, TV> interface, providing efficient key-value storage. It supports storing objects as keys.

    By default, when using objects as keys, HashMap compares them using the spl_object_hash function. This means comparison is based on object identity rather than object state.

    To use objects as keys based on their internal state (value equality) rather than identity, your class must implement the HashContract interface and define equals(mixed $that): bool and hashCode(): string methods.

    <?php
    
    declare(strict_types=1);
    
    use Fp\​Collections\HashMap;
    
    // To use objects as keys based on value equality, implement HashContract
    final class Foo implements HashContract
    {
        public function __construct(
            public readonly int $a,
            public readonly bool $b = true,
        ) {}
    
        public function equals(mixed $that): bool
        {
            return $that instanceof self
                && $this->a === $that->a
                && $this->b === $that->b;
        }
    
        public function hashCode(): string
        {
            return md5(implode(',', [$this->a, $this->b]));
        }
    }
    
    // Creating a HashMap from pairs
    $collection = HashMap::collectPairs([
        [new Foo(1), 1],
        [new Foo(2), 2],
        [new Foo(3), 3],
        [new Foo(4), 4]
    ]);
    
    // Retrieving a value by key with a fallback
    $value = $collection(new Foo(2))->getOrElse(0); // 2
    
    // Performing functional operations
    $result = $collection
        ->map(fn(int $value) => $value + 1)
        ->filter(fn(int $value) => $value > 2)
        ->fold(0)(fn(int $acc, int $value) => $acc + $value); // 12
  8. Understand the Collection hierarchy

    master

    The functional library organizes its collection types into a strict hierarchy based on two dimensions: whether the collection is empty or non-empty, and the specific data structure used (Sequence, Set, or Map).

    All collections follow a pattern where a general interface (e.g., Collection<TV>) is specialized into a category (e.g., Seq<TV>) and finally into a concrete implementation (e.g., ArrayList<TV>).

    Empty Collections

    Use these when you need standard collection interfaces that allow for zero elements:

    • Sequences: Collection<TV> $\rightarrow$ Seq<TV> $\rightarrow$ LinkedList<TV> or ArrayList<TV>
    • Sets: Collection<TV> $\rightarrow$ Set<TV> $\rightarrow$ HashSet<TV>
    • Maps: Collection<TV> $\rightarrow$ Map<TK, TV> $\rightarrow$ HashMap<TK, TV>

    Non-Empty Collections

    Use these when you want to leverage type safety to guarantee that a collection contains at least one element. These types are specialized versions of the empty collections:

    • Non-Empty Sequences: NonEmptyCollection<TV> $\rightarrow$ NonEmptySeq<TV> $\rightarrow$ NonEmptyLinkedList<TV> or NonEmptyArrayList<TV>
    • Non-Empty Sets: NonEmptyCollection<TV> $\rightarrow$ NonEmptySet<TV> $\rightarrow$ NonEmptyHashSet<TV>
    • Non-Empty Maps: NonEmptyCollection<TV> $\rightarrow$ NonEmptyMap<TK, TV> $\rightarrow$ NonEmptyHashMap<TK, TV>
    # Empty Collections
    Collection<TV> -> Seq<TV> -> LinkedList<TV>
    Collection<TV> -> Seq<TV> -> ArrayList<TV>
    Collection<TV> -> Set<TV> -> HashSet<TV>
    Collection<TV> -> Map<TK, TV> -> HashMap<TK, TV>
    
    # Non-Empty Collections
    NonEmptyCollection<TV> -> NonEmptySeq<TV> -> NonEmptyLinkedList<TV>
    NonEmptyCollection<TV> -> NonEmptySeq<TV> -> NonEmptyArrayList<TV>
    NonEmptyCollection<TV> -> NonEmptySet<TV> -> NonEmptyHashSet<TV>
    NonEmptyCollection<TV> -> NonEmptyMap<TK, TV> -> NonEmptyHashMap<TK, TV>
  9. How the Either monad works for error handling

    master

    The Either monad represents a computation that can result in either a success or a failure. It is composed of two subclasses:

    • Left: Represents an error outcome (the failure case).
    • Right: Represents a successful outcome (the success case).

    Either allows you to chain multiple operations that might fail using flatMap. If any step in the chain returns a Left, the subsequent steps are skipped (short-circuited), and the final result will contain that Left error. This avoids deeply nested conditional checks for null or error states.

    <?php
    
    declare(strict_types=1);
    
    use Fp\
    Functional\\Either\\Either;
    
    // Example of chaining operations that might fail
    $statusOrErrorMessage = getUserById(654)
        ->flatMap(fn(User $user) => getUserFirstOrder($user))
        ->flatMap(fn(Order $order) => getOrderTrackNumber($order))
        ->flatMap(fn(TrackingNumber $number) => getTrackingStatus($number))
        ->get();
  10. Core Concepts of Functional PHP

    master

    The library provides a typesafe and concise way to perform functional programming in PHP. Its power comes from the combination of:

    • Collections: Specialized data structures like ArrayList, LinkedList, and NonEmptyLinkedList that support functional transformations.
    • Monads: Specifically the Option monad for handling nullability and potential failures safely.
    • Immutability: Operations on collections return new instances rather than modifying the original.
    • Covariance: Collection template parameters are covariant, allowing a collection of a subtype (e.g., Admin) to be passed where a collection of a supertype (e.g., User) is expected.
  11. Core concepts of Streams

    master

    Streams in Fp\Streams\Stream are immutable generator object wrappers based on PHP generators. They follow these core principles:

    • Lazy Evaluation: Operations are not executed immediately. They are only applied when a terminal operation (e.g., toList(), toFile(), drain(), fold()) is called.
    • Immutability & Forking: Every non-terminal operation produces a new stream fork. Note that a single stream object can be forked no more than once.
    • Creation: Streams can be created from any iterable using Stream::emits() or via static factory methods like Stream::emit(), Stream::infinite(), or Stream::awakeEvery().
    • Memory Efficiency: Because they are based on generators, they allow for constant memory usage even when processing large or infinite datasets.
    use Fp\Streams\Stream;
    
    Stream::emit(1)
        ->repeat() // [1, 1, ...] infinite stream
        ->map(fn(int $i) => $i + 1) // [2, 2, ...] infinite stream
        ->take(5) // [2, 2, 2, 2, 2]
        ->toList(); // [2, 2, 2, 2, 2]
  12. Use N combinators for working with tuples and shapes

    master

    N combinators like mapN, flatMapN, and reindexN are used to transform tuples or shapes. Unlike standard map or flatMap which operate on the container as a whole, N combinators deconstruct the input tuple/shape and pass each individual value as a separate argument to the provided callback function.

    This is particularly useful for converting a collection of validated values (like an Option containing a shape) into a specific object or data structure.

    // Example: Using mapN to transform a shape into an object
    $maybeData->mapN(fn(int $a, bool $b, bool $c) => new Foo($a, $b, $c));