ibsciss/php-functional

a reducer implementation in php

dev-master / 1.0.x-dev 2016-03-17 21:43 UTC

README

Join the chat at https://gitter.im/Ibsciss/php-functional

Software License Travis CI Code Coverage Scrutinizer Code Quality Build Status

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.