ibsciss / php-functional
a reducer implementation in php
Requires
- php: >=5.3.0
Requires (Dev)
- phpunit/phpunit: 4.*
- scrutinizer/ocular: ~1.1
- squizlabs/php_codesniffer: ~2.3
This package is not auto-updated.
Last update: 2024-11-07 00:43:06 UTC
README
A collection of functions and classes to provide some nice functional tools for your projects, with a simple, consistent and well tested api.
Especially useful to build data processing algorithms in a breeze.
Install
Via Composer
$ composer require ibsciss/php-functionnal
Usage
Simple example
Imagine you want to compute the total VAT amount for october:
Instead of doing things like this:
function compute_october_vat() { $total_vat_amount = 0; foreach ($invoices as $invoice) { if ($invoice->due_date->format('m') == '10') { $total_vat_amount += $invoice->amount * 0.2; } } return $total_vat_amount; }
Or, if you want to try with map / reduce functions:
function compute_october_vat() { return array_reduce( array_map( function($invoice) { return $invoice->amount * 0.2; }, array_filter( $invoices, function($invoice) { return $invoice->due_date->format('m') == '10'; } ) ), function($x, $y) { return $x + $y; }, 0); }
You can now use a more fluent api:
function compute_october_vat() { return Fp\collection($invoices) ->filter( function($invoice) { return $invoice->due_date->format('m') == '10'; }; ) ->map( function($invoice) { return $invoice->amount * 0.2; }; ) ->add(); }
Functional helper
Compose
The compose function give you the ability to create a new functions from existing functions:
compose(f,g,h)(x) == f(g(h(x)))
A practical example:
$plus_one = function($x) { return $x + 1; }; $square = function($x) { return pow($x, 2); }; $plus_one_and_square = Fp\compose($plus_one, $square); $plus_one_and_square(2) //return 9
Of course you can compose as much functions as you want.
Pipelines functions
Pipelines functions are useful to apply transformations to collections, Martin Fowler wrote a very good introduction (based on ruby) about it. On the same blog, you'll find another resource to learn how to refactor your too many loops using pipeline.
The map
, filter
and reduce
functions are wrapper around the native php function, to understand why we have made them please see the FAQ.
Map
Apply a function to each item of a collection to create a new array.
//square each item of the collection Fp\map( function($x) { return pow($x, 2); //square function }, [1,2,3] ); //return [1,4,9,16]
Filter
Build an array composed with items that returns true when passed in the given callback.
//return even values from the collection Fp\filter( function($x) { return ($x % 2 == 0); }, [1,2,3,4] ); //return [2,4]
Reduce
It makes an accumulation by passing each item to the given callback. The callback returning value is returned for the next call (an init value is provided for the first call).
//sum values of the collection Fp\reduce( function($carry, $item) { return $carray + $item }, [1,2,3,4], 0 ); //return 10
Chaining
You can chain operations by using the Fp\collection(collection)
function (don't forget to call values()
to get the results):
//squared even values from the given collection Fp\collection([1,2,3,4]) ->filter( function($x) { return ($x % 2 == 0); } ) ->map( function($x) { return pow($x, 2); } ) ->values();
Collection transducers
With classical pipeline functions, you have to iterate the whole collection for each step of the transformation and create an intermediate collection which is a massive waste in memory usage.
Moreover you can't really extract a step to use it in other contexts which is bad for code reuse.
To tackle these downsides of classic pipeline function, the functional world come with a nice solution: tranducers
.
Mapping
like map
Filtering
like filter
scalar transducer
Use with single_result
terminal reducer.
First
return the first element
Max
return the max
Aggregate reducer
Batching
Batch result
Enumerating
Create indexed tuples with results
Terminal reducer
appending
append to an array
conjoining
immutable appending by merge
single_result
to get a scalar result instead of a collection
FAQ
Why not using directly array_* (array_filter, array_map) functions ?
- To improve api consistency
- To be able to produce transducers if the iterable is omitted
- To be able to consume
Collection
objects.
Change log
Please see CHANGELOG for more information what has changed recently.
Testing
$ composer test
Contributing
Please see CONTRIBUTING for details.
Security
If you discover any security related issues, please email :author_email instead of using the issue tracker.
Credits
License
The MIT License (MIT). Please see License File for more information.