atomicptr / functional
A set of tools to enable a more functional style of programming in PHP, inspired by OCaml
v0.16.0
2024-12-15 19:09 UTC
Requires
- php: >=8.3
Requires (Dev)
- clean/phpdoc-md: ^0.19.3
- laravel/pint: ^1.17
- pestphp/pest: ^2.35
README
A set of tools to enable a more functional style of programming in PHP, inspired by OCaml.
<?php // lots of list utility functions list($even, $odd) = Lst::partition(fn (int $num) => $num % 2 === 0, [1, 2, 3, 4, 5, 6]); // $even is [2, 4, 6] // $odd is [1, 3, 5] // pipe a bunch of operations Collection::from(Lst::init(fn (int $index) => $index * $index, 100)) ->filter(fn (int $num) => $num % 2 === 0) ->filter(fn (int $num) => $num > 50) ->map(fn (int $num, int $index) => $num * $index) ->foldl(fn (int $acc, int $val) => $acc + $val, 0); // better error handling with Result function safeDivision(int $a, int $b): Result { if ($b === 0) { return Result::error("cant divide by 0"); } return Result::ok($a / $b); } $res = safeDivision(10, 2); if ($res->hasError()) { print($res->value()); exit(1); } // use bind for Result or Option to only continue as long as there is a value // lets assume we have a function fetch(string $url): Result that just returns // the response as a string wrapped into Result $title = fetch("https://atomicptr.dev/api/blog/post/1337") ->bind(fn (string $response) => Result::capture(fn () => json_decode($resp, true, flags: JSON_THROW_ON_ERROR))) ->bind(function (array $data) { assert(BlogPostSchema::isValid($data)); // A ficticious json schema validator return BlogPostResource::createFrom($data); // This converts the json data into a structure })->bind(fn (BlogPost $post) => $post->title()); if ($title->hasError()) { // something went wrong along the chain, so just panic $title->panic(); exit(1); } // express "nothing" without using null function findOneById(int $id): Option { $rows = DB::find("table_name", ["id" => $id]); if (empty($rows)) { return Option::none(); } return Option::some($rows[0]); } $row = findOneById(1337); if ($row->isNone()) { print("Can not find object 1337"); exit(1); } // ... // maps $incrementer = fn (Option $value) => $value->isSome() ? Option::some($value->value() + 1 : 1; $map = Map::empty() ->update("a", $incrementer) ->update("a", $incrementer) ->update("b", $incrementer) ->set("c", 5); $map->get("a"); // 3 $map->get("b"); // 1 // group products by type $productsByType = Lst::groupBy(fn (Product $product) => $product->getType()->toString(), Product::all()); $productsByType->get("electronics") // [Product, Product] // memoize functions $f = Memo::make(fn (int $a, int $b) => doSomethingHeavy($a, $b)); $res = $f(1337, 8080); // this will call "doSomethingHeavy" which might take a while // ... $res = $f(1337, 8080); // now we call it again but it will now instantly return the result because we already called it with 1 and 2
Install
The package is available on packagist
Install via:
$ composer req atomicptr/functional
Docs
Atomicptr\Functional
Atomicptr\Functional\Traits
Atomicptr\Functional\Exceptions
License
MIT