Darkwood · PHP 8.6 · Flow

PHP Speed Tooling

Partial Function Application,
Tokens and Flow

How much tooling can PHP provide
before another abstraction earns its place?

PHP 8.6 experiment · Darkwood

@matyo91

The rule

Need argument binding?→ PFA
Need lightweight source structure?→ PHP tokens
Need to iterate local files?→ foreach
Need actual orchestration?→ consider Flow

Use the PHP primitive first.

01 | rule @matyo91

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.

02 | pfa @matyo91

FCC vs PFA

First-class callable

fn ($ctx) => $step->apply($ctx)

$step->apply(...)

Partial Function Application

fn ($v) => applyBudget($v, $budget)

applyBudget(?, $budget)

PFA starts where FCC stops: some arguments are already known.

03 | fcc-pfa @matyo91

? 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.

04 | placeholders @matyo91

PFA goes straight into Flow

Flow changed: 0 lines

$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.

05 | flow @matyo91

Pipe ≠ Partial Application

PFA

bind arguments

|>

thread a value

Flow

execute jobs

$input
    |> removeNoise(...)
    |> normalizeWhitespace(...)
    |> applyBudget(?, 14);

They are complementary, not competing syntax.

06 | pipe @matyo91

First conclusion

We almost added nothing.

No partial()
No curry API
No pipe emulation
No PHP 8.6 requirement for Flow

PHP already solved the syntax problem.

07 | nothing @matyo91

PHP Speed Tooling

What if the same rule applies
to tools written about PHP?

Need a parser stack?
↓
Maybe just tokens

Choose the cheapest representation
that still contains the information you need.

08 | tooling @matyo91

Our tool · tools/pfa-opportunities

PHP files
        ↓
PhpToken::tokenize()
        ↓
ignore whitespace / comments
        ↓
find simple arrow functions
        ↓
PFA / FCC / ignore
No php-parser
~540 lines
report only
09 | scanner @matyo91

Conservative by design

Accepted

fn ($x) => foo($x, $bound)
→ foo(?, $bound)

fn ($x) => $step->apply($x)
→ $step->apply(...)

Rejected

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.

10 | conservative @matyo91

What did it find?

Zero is a result.

144

PHP files

498,107

bytes

51,663

significant tokens

0

PFA candidates

3

FCC candidates

0

Flow src

The selected production code did not contain obvious PFA migration sites.

11 | findings @matyo91

Tokens are enough… until they aren’t

Tokens are enough for

simple syntactic shapes
arrow functions
argument positions
method call patterns

AST / semantics may be needed for

scope
types
name resolution
reliable automated rewriting

Speed Tooling means avoiding unnecessary machinery —
not refusing necessary machinery.

12 | limits @matyo91

Should the scanner use Flow?

Plain PHP

foreach ($files as $file) {
    scanFile($file, $config);
}

Flow

$scan = scanFile(?, $config);
$flow = new Flow($scan);
$flow(new Ip($file));
$flow->await();
Plain ~79.5 ms
Flow ~120.2 ms
Peak ~6 MiB both

foreach wins here. Flow added orchestration without an orchestration problem.

13 | foreach @matyo91

When Flow earns its place

PHP primitives

argument binding
callables
pipeline syntax
tokenization
simple iteration

Flow

multiple IPs
driver-controlled execution
error jobs
events
IP strategies
await()

Flow becomes useful when execution itself becomes the problem.

14 | boundary @matyo91

PHP primitive first.

PFA instead of an adapter abstraction.

PhpToken instead of a parser stack.

foreach instead of an orchestration engine.

Add Flow when execution becomes orchestration.

@matyo91