---
title: "Partial Function Application : Building the Call Along the Way"
url: https://www.exakat.io/partial-function-application-building-the-call-along-the-way/
date: 2026-10-02
modified: 2026-10-02
lang: en
author: "dams"
description: "Building the Call Along the Way A function call is always atomic with PHP. The target and every argument must converge at the same place, at the same moment, or..."
categories:
  - "Technology"
tags:
  - "Partial Function Application"
  - "PFA"
  - "php"
image: https://www.exakat.io/wp-content/uploads/2026/10/partial.640.jpg
word_count: 1932
---

# Partial Function Application : Building the Call Along the Way

# Building the Call Along the Way

A function call is always atomic with PHP. The target and every argument must converge at the same place, at the same moment, or nothing happens. This doesn't sound like a limitation, rather a cooking recipe: you're not going to start your strawberry pie in Winter, with the pie crust, and wait for the fruits to be in season a few months later. Yet, the some ingredient may be mixed, and completed later with the rest of it. Sometimes, the kitchen counter is too small.

In comparison with source code, it means that every call site carries the full weight of assembly. When three arguments are known at boot time and the fourth arrives with the HTTP request, the developer bridges that gap with structure: a closure, a class, a wrapper function, or a re-fetch of values that were perfectly available ten layers up. The bridge costs code estate, and the writing stays hardcoded long after everyone forgets why it exists.

[Partial Function Application](https://wiki.php.net/rfc/partial_function_application_v2), landing in PHP 8.6, removes the constraint, at least partially. A call can start where the first arguments live:

and finish wherever the last argument finally shows up:

The `?` placeholder marks an open slot. What travels between the two lines is a [Closure](https://www.php.net/manual/en/class.closure.php) and not a hand-written one. PHP derives it from the target function, preserving names, types, defaults, and attributes. Reflection can read it. Static analysis can follow it. The half-made call is a first-class value.

Now, a value that nobody calls is a value that does nothing. PFA closures can be assembled patiently, one argument at configuration time, another at wiring time, a third when the request arrives, and then never be executed. The call was built; the actual call may well never be made. Between now and execution, there might be other condition that apply, and lead to the cancellation for the call. In other times, the arguments would have been grouped and moved, but not used at any level. Similar waste, if you ask me.

Whether that trade-off matters depends on how the partial function applications travel through the codebase, and on the patterns they replace. Let's see what we can read from the current source code, before the new features gets activated.

## The closure, by hand

Before architecture, there is instinct. When some arguments are known and others are not, the PHP developer reaches for a closure:

This closure is partial application, performed manually. The known value enters through `use`, or through automatic capture in arrow functions, the open slot becomes a parameter, and execution waits for the caller. The [RFC](https://wiki.php.net/rfc/partial_function_application_v2) calls the hand-written form "cumbersome" because it forces the author to redeclare every parameter name and type: this is information the target function already carries.

PFA replaces the ceremony:

One expression. No parameter list, no type annotations, no `use` clause. The resulting closure inherits its signature from [`in_array`](https://www.php.net/manual/en/function.in-array.php), and in particular, the subset of parameters that `?` left open.

## A Class's job: the closure, objectified, again

When the open slots need names, discoverability, validation, or collection across many steps and many files, the closure grows into a class. A class whose entire purpose is storing arguments until someone calls `run()`:

Query builders work this way. [Doctrine's QueryBuilder](https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/query-builder.html) chains `select()`, `where()`, `orderBy()`, then `getResult()`. Fluent option resolvers, mock builders in test frameworks, HTTP request builders: the pattern occurs everywhere a call's arguments arrive in stages.

Each of these classes is a partially applied function written as a type: one field per open slot, one setter per argument, one terminal method for the deferred invocation. The cost scales with the number of arguments: fields, setters, validation, a terminal method, a class per call shape.

PFA collapses the pure-plumbing cases into an expression:

The `...` after the last provided argument signals that everything else remains open, producing a zero-argument closure — a delayed call. No class needed.

Builder classes that carry domain logic, cross-slot validation, conditionals, negotiated defaults, remain classes. PFA replaces argument warehouses, not argument processors.

## Code inverted around the last argument

The subtlest workaround, and the most pervasive. Since all arguments must exist at the call site, code migrates to the location of the scarcest argument. The late value becomes the organizing principle:

The function lives here because `$title` lives here. Everything else, `$legal`, `$locale`, gets fetched on the spot, re-derived at every call, because there was nowhere to put it earlier. The shape of the code follows data availability, not logic.

PFA inverts the organization. Stable arguments bind where they originate; only the volatile one stays open:

Larry Garfield, who authored the RFC, [notes on the PHP Foundation blog](https://thephp.foundation/blog/2025/12/08/partial-application/) that most uses in practice will follow this shape: reducing a function down to a single remaining argument, applied when the callback's single parameter arrives. Currying, in everything but name. No, not the basketball player.

## The grandmother of all partial applications

The oldest workaround predates closures, predates [first-class callables](https://wiki.php.net/rfc/first_class_callable_syntax), predates everything. When the same function keeps appearing with the same leading arguments, the developer promotes it to a named wrapper:

This is a hand-compiled partial application, fossilized into a function definition. Codebases are full of them. Entire CMS ecosystems run on stacks of such wrappers, passed as string callables, or called as a global function, always available. In a way, PFA are hunting down global functions, the way we ostracized the global variables:

The wrapper is static and greppable, so any IDE finds it, any code search locates every call site. PFA replaces the wrapper with a runtime value:

The gain is dynamism: `str_replace($from, $to, ?)` adapts to its inputs, and no signature gets duplicated. The trade-off is indirection: the call shape lives one step away from the function name. Both forms coexist in PHP 8.6. The named wrapper becomes a choice, not the only option.

But the repeated call without a wrapper remains the pattern most vulnerable to drift:

The day the rule changes, every site needs editing. PFA consolidates the rule into one configuration point:

One place to change. Every completion site just calls `$underscore()`.

## The trio

These three features form a long story arc, across PHP versions.

[First-Class Callable syntax](https://wiki.php.net/rfc/first_class_callable_syntax), since PHP 8.1, proved that a call can be held before being made. `trim(...)` wraps the function into a Closure with all arguments pending.

The [pipe operator](https://thephp.foundation/blog/2025/07/11/php-85-adds-pipe-operator/), since PHP 8.5, gave the held call a canonical place to complete. `$input |> trim(...)` feeds the value and executes in one step: no intermediate variable, no nested parentheses.

PFA, with PHP 8.6, fills the gap between the two: the call can be assembled gradually, one argument at a time, between first-class capture and pipe completion. Garfield [calls pipes and PFA](https://stitcher.io/blog/php-86-partial-function-application) "the twins, reunited."

## Spotting PFA candidates in today's code

Let's switch our hat, and take the static code analysis one. The workaround family doubles as an audit map. Every workaround still in a codebase marks a spot where PFA can slot in.

**Relay closures**: the highest-frequency target. Any arrow function whose body is a single call, whose parameters appear verbatim among the call's arguments, and whose body contains no logic beyond the call itself:

| Hand-written form | PFA form |
| ----------------- | -------- |
| `fn($x) => f($x, $a, $b)` | `f(?, $a, $b)` |
| `fn($x) => f($a, $x, $b)` | `f($a, ?, $b)` |
| `fn($x) => $this->repo->save($x, $this->tenant)` | `$this->repo->save(?, $this->tenant)` |
| `fn() => $svc->flush($em)` | `$svc->flush($em, ...)` |
| wrappers inside pipe chains | `$x \|> f($const, ?)` |

Closures that contain conditions, transformations, null-coalescing, or multiple statements stay closures. PFA replaces the relay, not the logic.

**Repeated literal calls**: `str_replace(' ', '_', ...)` scattered across files, `$date->format('d/m/Y')` copy-pasted through a template layer. Grep finds them. Each cluster is one PFA closure built at configuration time, completed at each call site.

**Argument-warehouse classes**: the vanishing test settles these. If PHP had always had PFA, would this class exist? If the class stores arguments and forwards them to a single real call, it converts. If it validates, negotiates, or branches on its stored values, it stays.

## Waste by absence

PFA closures are cheap to create and carry no side effects until called. That is the feature, and here is a related risk.

A closure built at boot time and stored in a container costs memory for the lifetime of the request, whether or not any code path reaches it. A builder class had the same cost, but the class was visible: it had a name, a file, an import statement. The PFA closure is a value in a variable. Nothing in the code advertises that it was prepared but never used.

The preparation cost is small in isolation. Multiplied across a container that eagerly builds PFA closures the way it eagerly builds services, it accumulates. And it does it silently, because the closures leave no trace of non-execution, and no naming conflict in case of repetition.

Static analysis could flag this: a PFA closure assigned but never invoked on any reachable path. That pattern is detectable. Now, imagine that the execution is conditional: cannot get the 4th parameter? well, we'll throw an exception, and forget it.

PFA will impact the code organisation by linking several methods in a series of steps to build the final call. Either the closure is completed with a new argument, or it is passed to the next step intact, and it is eventually executed. It is now a lot easier to follow how a specific function is executed, and it is always good to see how conditions are applied to data.

I also wonder if PFA will generate a family of function with long list of arguments. After all, a PFA make more sense the more arguments there are.