-
-
Save mathiasverraes/0ec97dcd2d237eb25187 to your computer and use it in GitHub Desktop.
<?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); |
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;
});
}
}
@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 😑
@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.
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)
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
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);
}
}
$max should be called max_by(), avoids name clashing with max().