return is not a function, it's a keyword
array_walk
is the functional form of foreach (...) { ... }
. You need a way to stop it early as soon as the first match is founds, but return
simply won't work here. Enter callcc
. callcc
calls its user-supplied function with the current contintuation, providing it an $exit
function which is essentially the functional form of the return
keyword.
function indexByDateRange(array $ranges, int $days) {
return callcc(fn($exit) =>
array_walk($ranges, fn($v, $k) =>
$days <= $v && $exit($k)
)
? count($ranges)
: null
);
}
As soon as $exit
is called, array_walk
is halted and no additional iteration happens -
$ranges = [30,60,90,120,360];
echo "index for (73): ", indexByDateRange($ranges, 73), PHP_EOL;
// 2
echo "index for (4): ", indexByDateRange($ranges, 4), PHP_EOL;
// 0
echo "index for (500): ", indexByDateRange($ranges, 500), PHP_EOL;
// 5
callcc
can be implemented as an ordinary function -
class Box extends Error {
public function __construct($value) {
$this->unbox = $value;
throw $this;
}
}
function callcc(callable $f) {
try { return $f(fn($value) => new Box($value)); }
catch (Box $b) { return $b->unbox; }
catch (Error $e) { throw $e; }
}
Now you are afforded beautiful functional style thanks to arrow expressions, good programmer ergonomics, and early-exit semantics. For more details, see callcc
introduced in this Q&A.
array_walk is not functional
PHP has gained better support for functional style in recent versions, but historically PHP wasn't intended to be a programming language. array_walk
was added way back in PHP 4 and has an odd behaviour of always returning true
.
If you're a PHP programmer trying to write functional programs, you already understand you will need a library of generic functions at your side -
// array_each : ('a array, a' -> void) -> void
function array_each(array $a, callable $f) {
array_walk($a, $f);
}
When the iterator is exhausted, array_each
gives a null return value, allowing the caller to properly use the null coalescing operator, ??
. The ternary operator is no longer required -
// indexByDateRange : (int array, int) -> int
function indexByDateRange(array $ranges, int $days) {
return callcc(fn($exit) =>
array_each($ranges, fn($v, $k) =>
$days <= $v && $exit($k)
)
?? count($ranges)
);
}
// callcc : (('a -> 'b) -> 'a) -> 'a
const callcc = ...
don't get me wrong
Recursion is de facto mechanism for writing "loops" in functional style, but sometimes constructs like foreach
are more intuitive. Here we saw how functional style can steal a page from imperative style's book, and still uphold functional principles. Barmar and mickmackusa provide a solution you should use. But maybe you're wondering if indexByDateRange
could be a pure functional expression. The above form shows you it's possible and what it looks like.