4
\$\begingroup\$

I've found myself on numerous occasions needing to with large text-files, where joining this on that, and filtering this out on arbitrary conditions.

The first couple of times, this turned into a loop in a loop in a loop mess which was indented more times than I care to either remember or admit.

More recently, I've attempted to make something that looks and behaves more "functional". Below is the basic class and a simple example.

I'm primarily concerned with extensibility. I'm not sure the approach I use will lend itself to extensibility in the future. I though, for example, about trying to stream results to disc, and I'm not sure that's doable at all without a fundamental change. Similarly it would be useful to add a simple ->not() for negating a filter, which I also don't see easily doable here.

A secondary concern is performance. PHP is perhaps not the best choice for a functional style. I'm guessing the performance of all these function calls isn't fantastic. Anything obvious I can do to improve it? If "real" performance is a requirement, the obvious suggestion might just to be to not use PHP, but if there are cheap ways to stretch this further, it would be nice.

Code:

class Functional {
  protected $result;
  protected $extra_data;

  public function __construct($array = NULL) {
    $this->result = $array;
  }

  public function reduce($callback, $i = NULL) {
    if ($i === NULL) {
      $i = $this->result;
    }

    $result = array_reduce($i, $callback);
    $this->result = $result;
    return $this;
  }

  public function walk($callback, $i = NULL) {
    if ($i === NULL) {
      $i = $this->result;
    }

    array_walk($i, $callback);
    $this->result = $i;
    return $this;
  }

  public function filter($callback, $i = NULL) {
    if ($i === NULL) {
      $i = $this->result;
    }

    $this->result = array_filter($i, $callback);
    return $this;
  }

  public function result() {
    return $this->result;
  }

  public function gather($callback, $key) {
    $this->extra[$key] = $callback($this->result)
    return $this;
  }

  public function extra($key) {
    return $this->extra[$key];
  }
}

Example usage:

$a = [1, 2, 3, 4];

$f = new Functional($a);
$result = $f->walk(function(&$i) { return $i *= 2; })
  ->reduce(function($i, $j) { return $i + $j; })
  ->result();

var_dump($result);

int(20)
\$\endgroup\$
6
  • \$\begingroup\$ whenever a function expects a function as an argument, it'd be best to add the callable type-hint. All in all, I'd also say that you're taking a tool (PHP), and try to use it as something it's not (ie a functional language). You can sort of pull this off, but it's not the best thing to do, IMHO. Using JS, or Scheme, or Haskell would be my advice \$\endgroup\$ – Elias Van Ootegem Dec 8 '14 at 15:43
  • \$\begingroup\$ Hmm, that's a good point... It wasn't available until PHP 5.4 though, right? Sadly I still need it to work in PHP 5.3 as well, despite it's EOL status. I do utilize 5.4 features in the tests (which aren't posted), but I'm avoiding that in the main code for now. \$\endgroup\$ – Letharion Dec 8 '14 at 15:44
  • \$\begingroup\$ Well, if it performance is an issue and if you need to convince anyone to upgrade: moving to PHP 5.5 easily gives you a 20~30% performance boost, possibly more (after upgrading, we saw an increase of well over 25%) \$\endgroup\$ – Elias Van Ootegem Dec 8 '14 at 15:50
  • \$\begingroup\$ As from what I have learned, in functional programming each function only depends on its own arguments and on nothing more. Can a function utilizing $this-> be a part of functional programming? \$\endgroup\$ – feeela Dec 8 '14 at 15:51
  • \$\begingroup\$ @feeela I assume you are thinking of pure functions. I titled the question "look functional", because I'm aware that it won't be pure. \$\endgroup\$ – Letharion Dec 8 '14 at 16:41
2
\$\begingroup\$

I propose you a more compact version with code duplication removal:

You can compact all of your call with an apply method, as shown in the following code:

<?php

class Functional
{
    protected $result;
    protected $extra_data;

    public function __construct($array = NULL)
    {
        $this->result = $array;
    }

    protected function apply($fn, $callback, $value)
    {
        $input = (is_null($value)) ? $this->result : $value;
        call_user_func($fn, $input, $callback);
        return $this;
    }

    public function reduce($callback, $i = NULL)
    {
        return $this->apply('array_reduce', $callback, $i);
    }

    public function walk($callback, $i = NULL)
    {
        return $this->apply('array_walk', $callback, $i);
    }

    public function filter($callback, $i = NULL)
    {
        return $this->apply('array_filter', $callback, $i);
    }

    public function result()
    {
        return $this->result;
    }

    public function gather($callback, $key)
    {
        $this->extra[$key] = $callback($this->result);
        return $this;
    }

    public function extra($key)
    {
        return $this->extra[$key];
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.