Last active
November 10, 2017 18:51
-
-
Save masterfermin02/72287cf7f3f2f1a5cf8e4658b021e0f5 to your computer and use it in GitHub Desktop.
"Getting Functional with PHP"
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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);*/ | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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