Functional programing is a fascinating concept.
The purpose of this library is to explore Functors
, Applicative Functors
and Monads
in OOP PHP, and provide examples of real world use case.
Monad types available in the project:
- State Monad
- IO Monad
- Collection Monad (a.k.a List Monad, since
list
is a protected keyword in PHP I name itcollection
) - Either Monad
- Maybe Monad
composer require widmogrod/php-functional
This repository follows semantic versioning concept. If you want to contribute, just follow GitHub workflow and open a pull request.
More information about changes you can find in change log
Quality assurance is brought to you by PHPSpec
composer test
You can find more use cases and examples in the example directory.
NOTE: Don't be confused when browsing thought examples you will see phrase like "list functor" and in code you will see
Monad\Collection
. Monad is Functor and Applicative. You could say that Monad implements Functor and Applicative.
use Functional as f;
$collection = Monad\Collection::of([
['id' => 1, 'name' => 'One'],
['id' => 2, 'name' => 'Two'],
['id' => 3, 'name' => 'Three'],
]);
$result = $collection->map(function($a) {
return $a['id'] + 1;
});
assert(f\extract($result) === [2, 3, 4]);
Apply function on list of values and as a result, receive list of all possible combinations of applying function from the left list to a value in the right one.
[(+3),(+4)] <*> [1, 2] == [4, 5, 5, 6]
use Functional as f;
$collectionA = Monad\Collection::of([
function($a) {
return 3 + $a;
},
function($a) {
return 4 + $a;
},
]);
$collectionB = Monad\Collection::of([
1, 2
]);
$result = $collectionA->ap($collectionB);
assert($result instanceof Applicative\Collection);
assert(f\extract($result) === [4, 5, 5, 6]);
Extracting from a list of uneven values can be tricky and produce nasty code full of if (isset)
statements.
By combining List and Maybe Monad, this process becomes simpler and more readable.
use Monad\Maybe;
use Monad\Collection;
$data = [
['id' => 1, 'meta' => ['images' => ['//first.jpg', '//second.jpg']]],
['id' => 2, 'meta' => ['images' => ['//third.jpg']]],
['id' => 3],
];
// $get :: String a -> Maybe [b] -> Maybe b
$get = function ($key) {
return f\bind(function ($array) use ($key) {
return isset($array[$key])
? Maybe\just($array[$key])
: Maybe\nothing();
});
};
$result = Collection::of($data)
->map(Maybe\maybeNull)
->bind($get('meta'))
->bind($get('images'))
->bind($get(0));
assert(f\valueOf($result) === ['//first.jpg', '//third.jpg', null]);
In php
world, the most popular way of saying that something went wrong is to throw an exception.
This results in nasty try catch
blocks and many of if statements.
Either Monad shows how we can fail gracefully without breaking the execution chain and making the code more readable.
The following example demonstrates combining the contents of two files into one. If one of those files does not exist the operation fails gracefully.
use Functional as f;
use Monad\Either;
function read($file)
{
return is_file($file)
? Either\Right::of(file_get_contents($file))
: Either\Left::of(sprintf('File "%s" does not exists', $file));
}
$concat = f\liftM2(
read(__DIR__ . '/e1.php'),
read('aaa'),
function ($first, $second) {
return $first . $second;
}
);
assert($concat instanceof Either\Left);
assert($concat->extract() === 'File "aaa" does not exists');
Example usage of IO Monad
. Read input from stdin
, and print it to stdout
.
use Monad\IO as IO;
use Functional as f;
// $readFromInput :: Monad a -> IO ()
$readFromInput = f\mcompose(IO\putStrLn, IO\getLine, IO\putStrLn);
$readFromInput(Monad\Identity::of('Enter something and press <enter>'))->run();
use Monad\IO as IO;
use Monad\Control as C;
use Functional as f;
$do = control\doo([
IO\putStrLn('Your name:'), // put on screen line: Your name:
'$name' =>
IO\getLine(), // prompt for the name, and store it in '$name' key
control\runWith(IO\putStrLn, ['$name']), // put on screen entered name
IO\putStrLn('Your surname:'), // put on screen line: Your surname:
'$surname' =>
IO\getLine(), // prompt for surname, and store it in '$surname' key
control\runWith(function($surname, $name) { // put on screen: "Hello $surname, $name"
return IO\putStrLn(sprintf("Hello %s, %s", $surname, $name));
}, ['$surname', '$name']),
]);
// performs operation, before that nothings happens from above code.
$do->run();
Example output:
Your name:
Gabriel
Your surname:
Habryn
Hello Habryn, Gabriel
This variant of sequence_
ignores the result.
use Monad\IO as IO;
use Functional as f;
f\sequence_([
IO\putStrLn('Your name:'),
IO\getLine(),
IO\putStrLn('Your surname:'),
IO\getLine(),
IO\putStrLn('Thank you'),
])->run();
Here links to their articles/
libraries that help me understood the domain:
- http://drboolean.gitbooks.io/mostly-adequate-guide
- https://github.com/fantasyland/fantasy-land
- http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html
- http://learnyouahaskell.com/functors-applicative-functors-and-monoids
- http://learnyouahaskell.com/starting-out#im-a-list-comprehension
- http://robotlolita.me/2013/12/08/a-monad-in-practicality-first-class-failures.html
- http://robotlolita.me/2014/03/20/a-monad-in-practicality-controlling-time.html
- https://github.com/folktale/data.either