Skip to content

Instantly share code, notes, and snippets.

@mathiasverraes
Created December 9, 2014 19:40
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mathiasverraes/0ec97dcd2d237eb25187 to your computer and use it in GitHub Desktop.
Save mathiasverraes/0ec97dcd2d237eb25187 to your computer and use it in GitHub Desktop.
max($list, $function) in php
<?php
// test data
class Foo {
private $a;
private $b;
function __construct($a, $b)
{
$this->a = $a;
$this->b = $b;
}
public function getA() { return $this->a; }
public function getB() { return $this->b; }
}
$list = [
$foo1 = new Foo(2, "bar"),
$foo2 = new Foo(5, "baz"),
$foo3 = new Foo(1, "bam"),
];
// simple
$isGreater = function($carry, $item) {
return is_null($carry) || $item->getA() > $carry->getA() ? $item : $carry;
};
$maxOfList = array_reduce($list, $isGreater);
assert($maxOfList === $foo2);
// reusable max() function
$max = function($list, callable $extract) {
$compare = function($carry, $item) use($extract) {
return is_null($carry) || $extract($item) > $extract($carry) ? $item : $carry;
};
return array_reduce($list, $compare);
};
$extractA = function($x) { return $x->getA();};
assert($max($list, $extractA) === $foo2);
@mathiasverraes
Copy link
Author

$max should be called max_by(), avoids name clashing with max().

@texdc
Copy link

texdc commented Dec 9, 2014

Why do we need to know which Foo has the largest $a? Don't forget the lessons learned about domains, ubiquitous language, and encapsulation.

class Foo
{
    public static function maxA(array $fooList)
    {
        return array_reduce($fooList, function(self $carry = null, self $item) {
            return ($carry == null || $item->a > $carry->a) ? $item : $carry;
        });
    }
}

@turanct
Copy link

turanct commented Dec 10, 2014

@texdc yes, when you're doing OOP that certainly is a reasonable way to do it (encapsulating actions together with the data). In functional programming, you would use higher order functions to do it, like in mathias' example.

In fact, when max_by() is implemented, you can then compose domain specific functions (with names using the ubiquitous language) out of the more generic basic functions like max_by(). e.g.: find_oldest_pupil() is created by using max_by(), Pupil, and the Pupil\get_age() function.

That is, of course, if i'm not mistaken 😑

@texdc
Copy link

texdc commented Dec 10, 2014

@turanct true, it just seems like a lot of extra work/code when only basic OOP is necessary. It's also reinventing array_reduce(), in a way: both max_by() and array_reduce() would have the same signature.

@mathiasverraes
Copy link
Author

As @turanct suggests, it's perfectly possible to combine FP techniques with DDD principles. DDD is not tied to OOP at all. For example, using Lambdalicious http://verraes.net/2014/11/higher-order-programming/ you would create functions like this:

<?php
$findOldestIn = max_by( __, method(@getAge, [], __));

$oldestPupil = $findOldestIn($listOfPupils);

I think you'll agree this covers the domain + ubiquitous language part.
Ihe example that you gave is not encapsulated. Foo::maxA is a global function, that happens to reside in a class. That's not the same as encapsulation. In my example, $findOldestIn has local scope, and can only be accessed by other scopes when you explicitly hand over the function to them. That's a powerful way of information hiding.

it just seems like a lot of extra work/code when only basic OOP is necessary.

max_by only needs to defined once, somewhere globally, as it is a very generic and low level function. The definition of $findOldestIn is extremely compact. You can of course combine FP and OOP and define it inside a class:

<?php
class Pupil
{
    public static function findOldestIn($listOfPupils) {
        return max_by( $listOfPupils, method(@getAge, [], __));

(note: At the moment, max_by is not implemented in Lambdalicious, but it will sooner or later)

@texdc
Copy link

texdc commented Dec 11, 2014

Foo::maxA may not be encapsulated, but the data and logic related to Foo is. Public getters is a leaky interface, but are necessary when using global functions. Otherwise how can the logic be performed without access to the internals of an object? Therefore, a simple class method effectively encapsulates both data and logic for that particular class.

class PupilService
{
    public function findOldestIn(array $aListOfPupils)
    {
        $compareAge = function(Pupil $carry = null, Pupil $item) {
            return ($carry == null || $item->isOlderThan($carry)) ? $item : $carry;
        };
        return array_reduce($aListOfPupils, $compareAge);
    }
}

max_by(__, method(@getAge, [], __)) is not elegant - it's incoherent. Attempts to remove syntactical noise, verbosity, and ceremony should not sacrifice clarity.

edit: convert static method to service method

@texdc
Copy link

texdc commented Dec 12, 2014

Ok, in order to have a truly simple and reusable function, you want find_by, not max_by. See my fork.

class PupilService
{
    public function findOldestIn(array $aListOfPupils)
    {
        $pupilIsOlder = function(Pupil $pupilA, Pupil $pupilB) {
            return $pupilA->isOlderThan($pupilB);
        };
        return find_by($aListOfPupils, $pupilIsOlder);
    }
}

http://3v4l.org/6O94O

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment