From 7d1e71a4ead3f753e10b2ad142ca5097cd8d6eb2 Mon Sep 17 00:00:00 2001 From: Andrey Kutejko Date: Tue, 16 Sep 2014 21:52:06 +0300 Subject: [PATCH] functions --- src/function.php | 199 +++++++++++++++++++++++++++++++++++++++++++++ t/FunctionTest.php | 125 ++++++++++++++++++++++++++++ 2 files changed, 324 insertions(+) create mode 100644 src/function.php create mode 100644 t/FunctionTest.php diff --git a/src/function.php b/src/function.php new file mode 100644 index 0000000..fc53752 --- /dev/null +++ b/src/function.php @@ -0,0 +1,199 @@ +apply(func_get_args()); + } + + function __invoke() + { + return $this->apply(func_get_args()); + } + + /* compose */ + + function then($then) + { + return new CompositionFunc($then, $this); + } + + function thenBind() + { + $args = func_get_args(); + $func = array_shift($args); + return $this->then(BoundFunc::createv($func, $args)); + } +} + +class Func extends AbstractFunc +{ + private $f; + + function __construct($f) + { + $this->f = $f; + } + + function apply($args) + { + return call_user_func_array($this->f, $args); + } +} + +class IdentityFunc extends AbstractFunc +{ + function apply($args) + { + if (count($args) !== 1) + throw new \Exception('Wrong number of arguments. Only one is expected.'); + return $args[0]; + } +} + + +class Placeholder +{ + public $place; + + function __construct($place) + { + $this->place = $place; + } + + static function p($place=1) + { + return new self($place); + } + + static function range($count) + { + $range = array(); + for ($i = 1; $i <= $count; ++$i) + $range[] = self::p($i); + return $range; + } +} + +class BoundFunc extends Func +{ + private $args, $arity; + + function __construct($f, $args=null) + { + parent::__construct($f); + $this->args = $args; + $this->arity = 0; + foreach ($this->args as $p) + if ($p instanceof Placeholder) + $this->arity = max($this->arity, $p->place); + } + + static function createv($func, $args) + { + return new BoundFunc($func, $args); + } + + static function create() + { + $args = func_get_args(); + $func = array_shift($args); + return self::createv($func, $args); + } + + function apply($args) + { + if (count($args) < $this->arity) { + $px = Placeholder::range($this->arity - count($args)); + return new self($this, array_merge($args, $px)); + } + + $a = array(); + foreach ($this->args as $p) { + if ($p instanceof Placeholder) { + $a[] = $args[$p->place - 1]; + } else { + $a[] = $p; + } + } + return parent::apply($a); + } +} + +class CompositionFunc extends Func +{ + private $left; + + function __construct($left, $right) + { + parent::__construct($right); + $this->left = Functions::func($left); + } + + function apply($args) + { + return $this->left->call(parent::apply($args)); + } +} + +class SFunc extends Func +{ + private $left; + + function __construct($left, $right) + { + parent::__construct($right); + $this->left = Functions::func($left); + } + + function apply($args) + { + $r = parent::apply($args); + $args[] = $r; + return $this->left->apply($args); + } +} + diff --git a/t/FunctionTest.php b/t/FunctionTest.php new file mode 100644 index 0000000..dd98477 --- /dev/null +++ b/t/FunctionTest.php @@ -0,0 +1,125 @@ +assertEquals('no_space', $f('no space')); + + $f = \PFF\BoundFunc::create('sprintf', '[%s:%s:%s]', '[', P::p(), ']'); + $this->assertEquals('[[:Abc:]]', $f('Abc')); + } + + public function testCompose() + { + $f = F::func('abs') + ->then('sqrt'); + + $this->assertEquals(2, $f(4)); + $this->assertEquals(2, $f(-4)); + $this->assertEquals(9, $f(81)); + } + + public function testChain() + { + $before = ' text with_spaces underscores and-hypens '; + $after = '_text_with_spaces_underscores_and_hypens_'; + + $f = F::bind('str_replace', ' ', '_', P::p()) + ->then(F::bind('str_replace', '-', '_', P::p())) + ->then(F::bind('str_replace', '__', '_', P::p())); + + $this->assertEquals($after, $f($before)); + + + $f = F::identity() + ->thenBind('str_replace', ' ', '_', P::p()) + ->thenBind('str_replace', '-', '_', P::p()) + ->thenBind('str_replace', '__', '_', P::p()); + + $this->assertEquals($after, $f($before)); + } + + public function testArrayBindDefault() + { + $f = F::bind(array('PFF\Arr', 'get'), P::p(1), P::p(2), 'DEFAULT'); + + $arr = array( + 'apple' => 100, + 'grapefruit' => 400, + 'carrot' => 50, + ); + + $this->assertEquals(400, $f($arr, 'grapefruit')); + $this->assertEquals('DEFAULT', $f($arr, 'orange')); + $this->assertEquals('DEFAULT', $f($arr, 'orange', 'no-oranges')); + } + + public function testGetter() + { + $arr = array('name' => 'apple', 'weight' => 100); + + $getter = F::bind(array('PFF\Arr', 'get'), P::p(2), P::p(1)); + $getName = $getter('name'); + $getWeight = $getter->call('weight'); + + $this->assertEquals('apple', $getName($arr)); + $this->assertEquals(100, $getWeight($arr)); + } + + public function testPluck0() + { + $getName = F::bind(array('PFF\Arr', 'get'), P::p(), 'name'); + $f = F::bind('array_map', $getName, P::p()); + + $arr = array( + array('name' => 'apple', 'weight' => 100), + array('name' => 'grapefruit', 'weight' => 400), + array('name' => 'carrot', 'weight' => 50), + ); + + $this->assertEquals(array('apple', 'grapefruit', 'carrot'), $f($arr)); + } + + public function testPluck1() + { + $f = F::compose( + F::curry('array_map', 2), + F::bind(array('PFF\Arr', 'get'), P::p(2), P::p(1))); + + $arr = array( + array('name' => 'apple', 'weight' => 100), + array('name' => 'grapefruit', 'weight' => 400), + array('name' => 'carrot', 'weight' => 50), + ); + + $this->assertEquals(array('apple', 'grapefruit', 'carrot'), $f->call('name')->call($arr)); + } + + public function testPluck2() + { + $second = F::func('second'); + $getter = F::bind(array('PFF\Arr', 'get'), P::p(2), P::p(1)); + $pluck = F::S( + F::bind('array_map', P::p(3), P::p(1)), + F::compose($getter, $second)); + + $arr = array( + array('name' => 'apple', 'weight' => 100), + array('name' => 'grapefruit', 'weight' => 400), + array('name' => 'carrot', 'weight' => 50), + ); + + $this->assertEquals(array('apple', 'grapefruit', 'carrot'), $pluck($arr, 'name')); + } +} + -- 2.49.0