Darkwood · PHP 8.6 · Flow
How much tooling can PHP provide
before another abstraction earns its place?
PHP 8.6 experiment · Darkwood
The rule
Use the PHP primitive first.
Experiment 1 · closure tax
fn ($value) => transform($value, $configuration)
↓
transform(?, $configuration)
PHP 8.6 Partial Function Application
The extra closure existed only to bind one argument.
FCC vs PFA
fn ($ctx) => $step->apply($ctx)
$step->apply(...)
fn ($v) => applyBudget($v, $budget)
applyBudget(?, $budget)
PFA starts where FCC stops: some arguments are already known.
? vs ...
foo(?, $config)
exact placeholder
required in the resulting Closure
foo('value', ...)
remaining list stays open
optionals keep their defaults
Bound arguments are evaluated when the partial is created.
PFA goes straight into Flow
$flow = new Flow(
static fn (BenchmarkState $state): BenchmarkState =>
loadPassages($state, $corpus),
);
↓
$flow = new Flow(loadPassages(?, $corpus));
$flow
->fn(applyBudgetToState(?, 12))
->fn(collect(?, $box));
PFA already produces the Closure Flow expects.
Pipe ≠ Partial Application
bind arguments
|>thread a value
execute jobs
$input
|> removeNoise(...)
|> normalizeWhitespace(...)
|> applyBudget(?, 14);
They are complementary, not competing syntax.
First conclusion
partial()PHP already solved the syntax problem.
PHP Speed Tooling
Choose the cheapest representation
that still contains the information you need.
Our tool · tools/pfa-opportunities
PHP files
↓
PhpToken::tokenize()
↓
ignore whitespace / comments
↓
find simple arrow functions
↓
PFA / FCC / ignore
Conservative by design
fn ($x) => foo($x, $bound)
→ foo(?, $bound)
fn ($x) => $step->apply($x)
→ $step->apply(...)
fn ($p) => $p->toArray()
fn ($x) => foo(bar($x), $c)
fn ($x) => complicated($x)
&& other($x)
False negatives are acceptable.
False refactoring advice is not.
What did it find?
PHP files
bytes
significant tokens
PFA candidates
FCC candidates
Flow src
The selected production code did not contain obvious PFA migration sites.
Tokens are enough… until they aren’t
simple syntactic shapes
arrow functions
argument positions
method call patterns
scope
types
name resolution
reliable automated rewriting
Speed Tooling means avoiding unnecessary machinery —
not refusing necessary machinery.
Should the scanner use Flow?
foreach ($files as $file) {
scanFile($file, $config);
}
$scan = scanFile(?, $config);
$flow = new Flow($scan);
$flow(new Ip($file));
$flow->await();
foreach wins here. Flow added orchestration without an orchestration problem.
When Flow earns its place
argument binding
callables
pipeline syntax
tokenization
simple iteration
multiple IPs
driver-controlled execution
error jobs
events
IP strategies
await()
Flow becomes useful when execution itself becomes the problem.
PFA instead of an adapter abstraction.
PhpToken instead of a parser stack.
foreach instead of an orchestration engine.
Add Flow when execution becomes orchestration.