Skip to content

Instantly share code, notes, and snippets.

@masterfermin02
Last active November 10, 2017 18:51
Show Gist options
  • Save masterfermin02/72287cf7f3f2f1a5cf8e4658b021e0f5 to your computer and use it in GitHub Desktop.
Save masterfermin02/72287cf7f3f2f1a5cf8e4658b021e0f5 to your computer and use it in GitHub Desktop.
"Getting Functional with PHP"
<?php
/**
* This is a implementantion in PHP of primary application logic for our Functional Programming blog example
* See related blog series at: http://www.datchley.name/tag/functional-programming/
* Version: 2.0
*/
// Access the `obj` using the property `prop`
function get_prop($obj, $prop)
{
if ( is_callable( $prop ) ) {
$key = call_user_func( $prop, $obj);
} elseif ( is_object( $obj ) && isset( $obj->{ $prop } ) ) {
$key = $obj->{ $prop };
} elseif ( isset( $obj[ $prop ] ) ) {
$key = $obj[ $prop ];
} else {
$key = $prop;
}
return $key;
}
function flip($fn) {
$outargs = func_get_args();
return function() use($fn,$outargs){
$args = array_merge($outargs, func_get_args());
return call_user_func_array($fn, $args);
};
}
// Given a list of objects, return a list of the values
// for property 'prop' in each object
function pluck($list, $prop) {
return mapWith(getWith($prop))($list);
}
// Filter `list` using the predicate function `fn`
function filter($list, $fn) {
return array_filter($list,$fn);
}
// Returns an object which groups objects in `list` by property `prop`. If
// `prop` is a function, will group the objects in list using the string returned
// by passing each obj through `prop` function.
function group($list, $prop) {
return array_reduce($list,function($grouped, $item) use($prop) {
$key = get_prop($item,$prop);
$grouped[$key][] = $item;
return $grouped;
}, []);
}
// Returns a new list by applying the function `fn` to each item
// in `list`
function map($list, $fn) {
return array_map($fn,$list);
}
// Returns a new object which is the result of mapping
// each *own* `property` of `obj` through function `fn`
function mapObject($obj, $fn) {
$keys = is_array($obj) ? array_keys($obj) : get_object_vars($obj);
return array_reduce($keys,function($res,$key) use($obj,$fn){
$res[$key] = call_user_func_array( $fn, [$key, get_prop($obj,$key)]);
return $res;
},[]);
}
// Return new list as combination of the two lists passed
// The second list can be a function which will be passed each item
// from the first list and should return an array to combine against for that
// item. If either argument is not a list, it will be treated as a list.
//
// Ex., pair([a,b], [c,d]) => [[a,c],[a,d],[b,c],[b,d]]
function pair($list, $listFn) {
is_array($list) || ($list = [$list]);
(is_callable($listFn) || is_array($listFn)) || ($listFn = [$listFn]);
return flatMapWith(function($itemLeft) use($listFn) {
return mapWith(function($itemRight) use($itemLeft) {
return [$itemLeft, $itemRight];
})(is_callable($listFn) ? call_user_func( $listFn, $itemLeft ) : $listFn);
})($list);
}
// Sort a list using comparator function `fn`,
// returns new array (shallow copy) in sorted order.
function _sort($list, $fn) {
//print_r($list);
return custom_sort($list,$fn);
}
function isAssociativeArray($arr)
{
// https://stackoverflow.com/questions/173400/how-to-check-if-php-array-is-associative-or-sequential
// Credit: Answer by Squirrel
for (reset($arr); is_int(key($arr)); next($arr)) ;
return !is_null(key($arr)) ? TRUE : FALSE;
}
function custom_sort()
{
$args = func_get_args();
/**
* Returns a sorted copy of the list, applying the provided function to each element before comparison
*
* @param callable $callable
* @param Generator|array $arr
*
* @return array
* @throws Exception
*/
$_sort = function ($arr,$callable) {
$arr = $arr;
$comparator = function ($a, $b) use ($callable) {
return $callable($a,$b);
};
if (isAssociativeArray($arr)) {
uasort($arr, $comparator);
} else {
usort($arr, $comparator);
}
return $arr;
};
return call_user_func_array(curry_right($_sort), $args);
}
function array_orderby()
{
$args = func_get_args();
$data = array_shift($args);
foreach ($args as $n => $field) {
if (is_string($field)) {
$tmp = array();
foreach ($data as $key => $row)
$tmp[$key] = $row[$field];
$args[$n] = $tmp;
}elseif ( is_callable( $field ) ) {
$key = call_user_func( $field);
}
}
$args[] = &$data;
call_user_func_array('array_multisort', $args);
return array_pop($args);
}
function objSort(&$objArray,$indexFunction,$sort_flags=0) {
$indices = [];
foreach($objArray as $obj) {
$indeces[] = $indexFunction($obj);
}
array_multisort($indeces,$objArray,$sort_flags);
return $objArray;
}
// Return a copy of the array 'list' flattened by one level, ie [[1,2],[3,4]] = [1,2,3,4]
function flatten($list) {
return array_reduce($list,function($items,$item){
return is_array($item) ? array_merge($items,$item) : $item;
},[]);
}
// Return a flattened list which is the result of passing each
// item in `list` thorugh the function `fn`
function flatMap($list, $fn) {
return flatten(map($list, $fn));
}
// Takes a binary comparison function
// and returns a version that adhere's to the Array#sort
// API of return -1, 0 or 1 for sorting.
function comparator($fn) {
return function($a,$b){
if($a < $b) return -1;
if($b < $a ) return 1;
return 0;
};
}
function curry_left($callable)
{
$outerArguments = func_get_args();
array_shift($outerArguments);
return function() use ($callable, $outerArguments) {
return call_user_func_array($callable, array_merge($outerArguments, func_get_args()));
};
}
function curry_right($callable)
{
$outerArguments = func_get_args();
array_shift($outerArguments);
return function() use ($callable, $outerArguments) {
return call_user_func_array($callable, array_merge(func_get_args(), $outerArguments));
};
}
function getWith($fn)
{
return curry_right('get_prop',$fn);
}
function filterWith($fn)
{
return curry_right('filter',$fn);
}
function mapWith($fn)
{
return curry_right('map',$fn);
}
function groupBy($fn)
{
return curry_right('group',$fn);
}
function mapObjectWith($fn)
{
return curry_right('mapObject',$fn);
}
function flatMapWith($fn)
{
return curry_right('flatMap',$fn);
}
function pluckWith($fn)
{
return curry_right('pluck',$fn);
}
function pairWith($fn)
{
return curry_right('pair',$fn);
}
function sortBy($fn)
{
return curry_right('_sort',$fn);
}
function useWith($fn /*, txfn, ... */) {
$transforms = func_get_args();
array_shift($transforms);
$_transform = function($args) use($transforms) {
return array_map(function($arg,$i) use($transforms) {
return $transforms[$i]($arg);
},$args,array_keys($args));
};
return function() use($_transform,$transforms,$fn) {
$args = func_get_args();
$transformsLen = count($transforms);
$targs = array_slice($args,0,$transformsLen);
$remaining = array_slice($args,$transformsLen);
return call_user_func_array($fn, array_merge(call_user_func($_transform,$targs), $remaining));
};
}
// Return the first / last element matching a predicate
function first(array $array, $test) {
foreach($array as $v)
if($test($v))
return $v;
return null;
}
// Return true if at least one element matches the predicate
function any($array, $test) {
foreach($array as $v)
if($test($v))
return true;
return false;
}
// Return true if all elements match the predicate
function all($array, $test) {
foreach($array as $v)
if(! $test($v))
return false;
return true;
}
function last(array $array, $test) {
return first(array_reverse($array), $test);
}
// Compose two functions together
function compose($f, $g) {
return function() use($f,$g) {
return $f(call_user_func_array($g, func_get_args()));
};
}
/*function compose() {
$args = func_get_args();
$fn = array_shift($args);
$gn = array_shift($args);
$fog = $gn ? function() use($fn,$gn,$args) { return $fn($gn(func_get_args())); } : $fn;
return count($args) ? call_user_func_array('compose', array_merge($fog,$args)) : $fog;
}*/
function pipeline(){
return flip('compose');
}
/*function compose(callable ...$functions)
{
$initial = array_shift($functions);
return array_reduce($functions, function ($f, $g) {
return function (...$args) use ($f, $g) {
return $f($g(...$args));
};
}, $initial);
}*/
/*function pipeline(callable ...$functions)
{
return compose(...array_reverse($functions));
}*/
// Right curried versions of the above functions, which
// allow us to create partially applied versions of each
// and use within a `compose()` or `sequence()` call
/*var getWith = rightCurry(get),
filterWith = rightCurry(filter),
mapWith = rightCurry(map),
groupBy = rightCurry(group),
mapObjectWith = rightCurry(mapObject),
flatMapWith = rightCurry(flatMap),
pluckWith = rightCurry(pluck),
pairWith = rightCurry(pair),
sortBy = rightCurry(sort);*/
<?php
include 'functional.php';
//print_r(pair([1,2], [3,4]));
$list = [5,3,6,2,8,1,9,4,7];
// Simple comparison for '>='
function greaterThanOrEqual($a, $b) {
return $a >= $b;
}
function ascSort($a,$b)
{
return $a > $b;
}
function greaterThanOrEqualTo($to){
return curry_right('greaterThanOrEqual',$to);
}
// a unary comparison function to see if a value is >= 5
$fiveOrMore = greaterThanOrEqualTo(5);
sort($list);
//print_r(filterWith($fiveOrMore)($list));
$evens = function($n) { return $n%2 == 0; };
$justEvens = filterWith($evens);
// print_r($justEvens($list));
// print_r(pair([1,2],[3,4]));
// print_r(flatten([[1,2],[3,4]]));
function sum($a,$b) { return $a + $b; }
function add1($v) { return $v+1; }
$additiveSum = useWith('sum', 'add1', 'add1');
// Before sum receives 4 & 5, they are each passed through
// the 'add1()' function and transformed
//echo $additiveSum(4,5); // 11
// Array of sample object data
$arrdates = json_decode('[
{ "id": 1, "published": '.strtotime('2015-07-29').' },
{ "id": 2, "published": '.strtotime('2017-11-01').' }
]');
//print_r($arrdates);
$thirtyDaysAgo = strtotime("-30 days");
$within30Days = useWith(greaterThanOrEqualTo($thirtyDaysAgo), getWith('published'));
// Get any object with a published date in the last 30 days
//print_r(filterWith($within30Days)($arrdates)) ;
$list = json_decode('[
{ "value": "A", "tag": "letter" },
{ "value": 1, "tag": "number" },
{ "value": "B", "tag": "letter" },
{ "value": 2, "tag": "number" }
]');
//print_r(groupBy('tag')($list));
//print_r(getWith('tag')($list));
//print_r(pluckWith('tag')($list));
//print_r(pairWith(getWith('tag'))($list));
//print_r(pair($list, getWith('tag')));
//echo 'ByTags';
//print_r($bytags);
//echo 'groupedtags';
// Step 3: strip extra key in nested pairs:
function getPostRecords($prop, $value) {
return pluckWith(0)($value);
}
$records = json_decode('[
{
"id": 1,
"title": "Currying Things",
"author": "Dave",
"selfurl": "/posts/1",
"published": 1437847125528,
"tags": [
"functional programming"
],
"displayDate": "2015-07-25"
},
{
"id": 2,
"title": "ES6 Promises",
"author": "Kurt",
"selfurl": "/posts/2",
"published": 1507673196,
"tags": [
"es6",
"promises"
],
"displayDate": "2015-07-26"
},
{
"id": 3,
"title": "Monads, Futures, Promises",
"author": "Beth",
"selfurl": "/posts/3",
"published": 1507673196,
"tags": [
"promises",
"futures"
],
"displayDate": "2015-04-25"
},
{
"id": 4,
"title": "Basic Destructuring in ES6",
"author": "Angie",
"selfurl": "/posts/4",
"published": 1507673196,
"tags": [
"es6",
"destructuring"
],
"displayDate": "2015-06-06"
},
{
"id": 5,
"title": "Composing Functions",
"author": "Tom",
"selfurl": "/posts/5",
"published": 1507673196,
"tags": [
"functional programming"
],
"displayDate": "2015-04-14"
},
{
"id": 6,
"title": "Lazy Sequences in FP",
"author": "Dave",
"selfurl": "/posts/6",
"published": 1507673196,
"tags": [
"functional programming"
],
"displayDate": "2015-06-21"
},
{
"id": 7,
"title": "Common Promise Idioms",
"author": "Kurt",
"selfurl": "/posts/7",
"published": 1438876909394,
"tags": [
"es6",
"promises"
],
"displayDate": "2015-08-06"
},
{
"id": 8,
"title": "Stop using Deferred",
"author": "Dave",
"selfurl": "/posts/8",
"published": 1435773701255,
"tags": [
"promises",
"futures"
],
"displayDate": "2017-11-01"
},
{
"id": 9,
"title": "Default Function Parameters in ES6",
"author": "Angie",
"selfurl": "/posts/9",
"published": 1436205701255,
"tags": [
"es6",
"destructuring"
],
"displayDate": "2017-11-06"
},
{
"id": 10,
"title": "Use more Parenthesis!",
"author": "Tom",
"selfurl": "/posts/10",
"published": 1440604909394,
"tags": [
"functional programming"
],
"displayDate": "2017-10-26"
}
]');
//print_r($records);
//print_r(pluckWith('tags')($records));
//$filtered = filterWith($within30Days)($records);
//print_r(pair($filtered, getWith('tags')));
//$bytags = pairWith(getWith('tags'))($filtered ); // #1
//$groupedtags = groupBy(getWith(1))($bytags); // #2
//print_r($bytags);
//print_r(json_encode($groupedtags));
//print_r($groupedtags);
//print_r(mapObjectWith('getPostRecords')($groupedtags));
$numbers = [5,1,3,2,4];
$names = ['River','Zoë','Wash','Mal','Jayne','Book','Kaylee','Inara','Simon'];
// generic, binary comparison returning true|false
function lessThan($a,$b) { return $a < $b; }
function greaterThan($a,$b) { return $a > $b; }
// create a comparator function
$asc = comparator('lessThan');
//print_r(sortBy($asc)($numbers));
//$news = sortBy($asc)($numbers);
//print_r($news);
//$natrsort = compose('array_reverse', 'natsort');
//$natrsort($numbers);
// [1, 2, 3, 4, 5]
//print_r($numbers);
//print_r(sortBy($asc)($names));
$kessel_times = json_decode('[
{ "name": "Slave 1", "parsecs": 13.17 },
{ "name": "Falcon", "parsecs": 11.5 },
{ "name": "Executor", "parsecs": 18 }
]');
$ascendingByParsecs = useWith(comparator('lessThan'), getWith('parsecs'), getWith('parsecs'));
//print_r(sortBy($ascendingByParsecs)($kessel_times));
$descending = comparator('greaterThan');
$descendingByPublishDate = useWith($descending, getWith('published'), getWith('published'));
function descendingByPublishDate()
{
return useWith('greaterThan', getWith('published'), getWith('published'));
}
// Gets the group key and list of records for that group
function sortByPublishDate($group,$recs) {
return sortBy('descendingByPublishDate')($recs);
}
$mostRecentByTagOrderedByPublishDate = pipeline(
filterWith($within30Days)
/*pairWith(getWith('tags')),
groupBy(getWith(1)),
mapObjectWith('getPostRecords'),
mapObjectWith('sortByPublishDate')*/
);
$composed_finished = $mostRecentByTagOrderedByPublishDate($records);
print_r($composed_finished);
/*$filtered = filterWith($within30Days)($records);
$bytags = pairWith(getWith('tags'))($filtered);
$groupedtags = groupBy(getWith(1))($bytags);
$finalgroups = mapObjectWith('getPostRecords')($groupedtags);
$finished = mapObjectWith('sortByPublishDate')($finalgroups);
print_r($finished);*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment