├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml.dist ├── src └── Combinators.php └── test ├── ComposeTest.php ├── FlipTest.php ├── IdTest.php ├── IfElseTest.php ├── KTest.php └── OnTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | composer.phar 2 | composer.lock 3 | /vendor/ 4 | /build/ 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | install: composer install 2 | language: php 3 | php: 4 | - 7.0 5 | - nightly 6 | script: composer test 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 php-fp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Functional combinators for PHP. [![Build Status](https://travis-ci.org/php-fp/php-fp-combinators.svg?branch=master)](https://travis-ci.org/php-fp/php-fp-combinators) 2 | 3 | ## Intro 4 | 5 | Combinators are simple functions that allow you to modify the behaviour of values and functions after they have been created. These allow greater reuse of functions and reduction of boilerplate. 6 | 7 | ## API 8 | 9 | These combinators aren't fully curried by default - mainly for optimisation reasons - but are designed so that most common use cases can be satisfied as is. Consequently, the type signatures use the comma (`,`) to represent multiple arguments. 10 | 11 | ### `compose :: (b -> c), (a -> b) -> a -> c` 12 | 13 | Instead of writing `function ($x) { return f(g(x)); }`, `compose` allows us to express this as `compose('f', 'g')` (where the parameters could be closures, invokables, or anything that can be used as a "function"). Given two functions, `f` and `g`, a function will be returned that takes a value, `x`, and returns `f(g(x))`. This operation is _associative_, so `compose` calls can nest to create longer chains of functions. A simple, two-function example is shown here: 14 | 15 | ```php 16 | c) -> (b, a) -> c` 37 | 38 | This function takes a two-argument function, and returns the same function with the arguments swapped round: 39 | 40 | ```php 41 | a` 54 | 55 | Returns whatever it was given! Again, this is useful in composition chains for the times when you don't want to do anything to the value (see `converge`): 56 | 57 | ```php 58 | Bool), (a -> b), (a -> b) -> a -> b` 67 | 68 | This function allows for conditionals in composition chains. Unlike `converge`, which branches and merges, `ifElse` chooses which function to run based on the predicate, and the other function is ignored: 69 | 70 | ```php 71 | b -> a` 85 | 86 | This function takes a value, and then returns a function that always returns that value, regardless of what it receives. This creates a "constant" function to wrap a value in places where invocation is expected: 87 | 88 | ```php 89 | c), (a -> b) -> a, a -> c` 101 | 102 | This function, also called the `Psi` combinator, allows you to call a function on transformations of values. This is really useful for things like sorting on particular properties of objects: we can call a compare function on two objects given a transformation. It's probably best illustrated with an example: 103 | 104 | ```php 105 | $b; }; 127 | 128 | $test = [ 129 | ['title' => 'hello', 'test' => 3], 130 | ['title' => 'goodbye', 'test' => 2], 131 | ['title' => 'something', 'test' => 4] 132 | ]; 133 | 134 | array_column($sort($test, $f), 'title'); // ['goodbye', 'hello', 'something'] 135 | ``` 136 | 137 | ## Contributing 138 | 139 | If I've missed any of your favourite combinators, feel free to submit a PR. If you have a good idea about how to make any of this README clearer, that would be so great: while these combinators are not new to functional programmers, they _are_ complex and need to be explained well. All help is appreciated :) 140 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | { 4 | "name": "Tom", 5 | "email": "tomjharding@live.co.uk" 6 | } 7 | ], 8 | "autoload": { 9 | "files": ["src/Combinators.php"] 10 | }, 11 | "autoload-dev": { 12 | "psr-4": { 13 | "PhpFp\\Combinators\\Test\\": "test/" 14 | } 15 | }, 16 | "description": "A handful of functional combinators in PHP.", 17 | "homepage": "http://www.github.com/php-fp/php-fp-combinators", 18 | "keywords": ["functional", "combinators"], 19 | "license": "MIT", 20 | "name": "php-fp/php-fp-combinators", 21 | "scripts": { 22 | "test": "phpunit" 23 | }, 24 | "time": "2016-06-13", 25 | "versions": "2.0.0", 26 | "require-dev": { 27 | "phpunit/phpunit": "^5.7" 28 | }, 29 | "require": { 30 | "cypresslab/php-curry": "^0.4.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | test/ 6 | 7 | 8 | 9 | 10 | 11 | src/ 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Combinators.php: -------------------------------------------------------------------------------- 1 | assertEquals( 24 | $f(3), 25 | 2, 26 | 'Composes correctly.' 27 | ); 28 | 29 | $this->assertEquals( 30 | $f(2), 31 | 1.5, 32 | 'Types correctly handled.' 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/FlipTest.php: -------------------------------------------------------------------------------- 1 | assertEquals( 19 | $subtractFrom(2, 9), 20 | 7, 21 | 'Flips.' 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/IdTest.php: -------------------------------------------------------------------------------- 1 | assertEquals( 12 | 2, 13 | Combinators::id(2), 14 | 'Identity.' 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/IfElseTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($f(1), 'Oops!', 'Success'); 22 | $this->assertEquals($f(2), '2 is even!', 'Failure'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/KTest.php: -------------------------------------------------------------------------------- 1 | assertEquals( 14 | $f(2), 15 | 3, 16 | 'Creates a constant.' 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/OnTest.php: -------------------------------------------------------------------------------- 1 | $b; 30 | }; 31 | 32 | $test = [ 33 | [ 34 | 'title' => 'hello', 35 | 'test' => 3 36 | ], 37 | 38 | [ 39 | 'title' => 'goodbye', 40 | 'test' => 2 41 | ], 42 | 43 | [ 44 | 'title' => 'something', 45 | 'test' => 4 46 | ] 47 | ]; 48 | 49 | $this->assertEquals( 50 | array_column($sort($test, $f), 'title'), 51 | ['goodbye', 'hello', 'something'], 52 | 'Transforms parameters.' 53 | ); 54 | } 55 | } 56 | --------------------------------------------------------------------------------