]> git.andy128k.dev Git - missing-tools.git/commitdiff
functions
authorAndrey Kutejko <andy128k@gmail.com>
Tue, 16 Sep 2014 18:52:06 +0000 (21:52 +0300)
committerAndrey Kutejko <andy128k@gmail.com>
Mon, 27 Oct 2014 21:13:42 +0000 (23:13 +0200)
src/function.php [new file with mode: 0644]
t/FunctionTest.php [new file with mode: 0644]

diff --git a/src/function.php b/src/function.php
new file mode 100644 (file)
index 0000000..fc53752
--- /dev/null
@@ -0,0 +1,199 @@
+<?php
+
+namespace PFF;
+
+class Functions
+{
+    static function identity()
+    {
+        return new IdentityFunc;
+    }
+
+    static function func($func)
+    {
+        if ($func instanceof Func)
+            return $func;
+        else
+            return new Func($func);
+    }
+
+    static function bind()
+    {
+        $args = func_get_args();
+        $func = array_shift($args);
+        return new BoundFunc($func, $args);
+    }
+
+    static function curry($func, $arity)
+    {
+        return new BoundFunc($func, Placeholder::range($arity));
+    }
+
+    static function compose($f, $g)
+    {
+        return new \PFF\CompositionFunc($f, $g);
+    }
+
+    static function S($f, $g)
+    {
+        return new \PFF\SFunc($f, $g);
+    }
+}
+
+abstract class AbstractFunc
+{
+    abstract function apply($args);
+
+    function call()
+    {
+        return $this->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 (file)
index 0000000..dd98477
--- /dev/null
@@ -0,0 +1,125 @@
+<?php
+
+use \PFF\Functions as F;
+use \PFF\Placeholder as P;
+
+function second()
+{
+    return func_get_arg(1);
+}
+
+class FunctionTest extends PHPUnit_Framework_TestCase
+{
+    public function testBind()
+    {
+        $f = \PFF\BoundFunc::create('str_replace', ' ', '_', P::p());
+        $this->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'));
+    }
+}
+