-
-
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); |
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);
}
}
@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: bothmax_by()
andarray_reduce()
would have the same signature.