Hello, internals,
I have been thinking about an RFC idea. The subject is pure functions.
First, let us define, for formality, what pure functions are. Straight from
Wikipedia, a pure function is a function that follows the following
properties:
- the function return values are identical for identical arguments.
- the function has no side effects (no mutation of non-local variables,
mutable reference arguments or input/output streams).
In PHP, there is no formal distinction in the language between pure and
impure functions. The proposal would be a new Pure attribute that, for
example, when applied to the function foo, would require it to be pure.
Therefore, the function foo:
- could not call functions and methods that do not have the Pure attribute,
such as those that generate I/O side effects, such as file_get_contents,
file_put_contents, print. - could not access any external values, such as:
- could not access superglobal variables, $_SESSION, $_REQUEST, $_GET,
$_POST etc. - could not access variables from its class - if it is a method - such
as $this->val, self::$val, static::$val. - could not access static variables from other classes, such as
OtherClass::$val. - etc.
- could not access superglobal variables, $_SESSION, $_REQUEST, $_GET,
With the new attribute, an example of a valid pure function would be:
#[Pure]
function my_array_first(array $array, mixed $fallback = null): mixed {
return $array ? $array[array_key_first($array)] : $fallback;
}
echo my_array_first([], 11); // 11
Some examples of invalid pure functions (which would cause Fatal errors)
would be:
#[Pure]
function random_number(int $a): int {
return random_int(10, 20) + $a;
}
echo random_number(10); // Fatal error: pure function random_number() calls
impure function `random_int()`.
class Ha {
private int $a = 1;
#[Pure]
public function getA(): int {
return $this->a;
}
}
new Ha()->getA(); // Fatal error: pure method Ha::getA() accesses $this.
#[Pure]
function request_method(): string {
return $_SERVER['REQUEST_METHOD'];
}
echo request_method(); // Fatal error: pure function request_method()
accesses superglobal $_SERVER.
I believe pure functions could bring benefits in certain areas.
Today, tools such as PHPStan, Psalm and PhpStorm accept annotations such as
@pure, but nobody guarantees that the function respects them. With a native
attribute, that guarantee would become the responsibility of the language
itself. Moreover, we would have the common benefits of pure functions:
- it would be easier to read and test a function, since the result would
depend only on the arguments, without needing mocks for global state,
session or file system - the result of a pure function could be cached (memoization) without
changing the behavior of the program - the compiler and OPcache could, IN THE FUTURE, optimize calls with
constant arguments - it would be safer to execute the code in Fibers or in extensions such as
parallel, since there would be no shared state
However, if you find the idea interesting, the points about the
implementation arise. I am not a great connoisseur of php-src, but I
believe it would be necessary to:
- compile-time validations, to reject the use of $this, superglobals,
static properties etc. inside a pure function - checking the calls. When the target is known at compile time, such as
random_int, the fatal error could be emitted there. But, for dynamic calls,
such as $fn() or $obj->foo() and for functions declared later, the check
would have to happen at runtime. - marking internal functions that are pure as pure.
I would like to hear the list's opinions on this idea. What do you think?
Regards,
Pedro Veloso
Hey Pedro,
On Fri, 25 Sept 2026 at 21:59, Pedro Veloso contatopedrohalves@gmail.com
wrote:
class Ha { private int $a = 1; #[Pure] public function getA(): int { return $this->a; } } new Ha()->getA(); // Fatal error: pure method Ha::getA() accesses $this.
Overall, I would also like to see functional purity in PHP, but a n
instance method is just a function with an implicit additional $this
argument, and it is pure if it causes no state mutation.
Marco Pivetta
Hey Pedro,
On Fri, 25 Sept 2026 at 21:59, Pedro Veloso
contatopedrohalves@gmail.com wrote:class Ha { private int $a = 1; #[Pure] public function getA(): int { return $this->a; } } new Ha()->getA(); // Fatal error: pure method Ha::getA() accesses $this.Overall, I would also like to see functional purity in PHP, but a n
instance method is just a function with an implicit additional$this
argument, and it is pure if it causes no state mutation.Marco Pivetta
I am also a big fan of functional purity in most forms. However, as noted there's already many SA tools that support this. (Via @pure or #[Pure]. There's even an unreleased PER from FIG that has one.). So for this to make sense in the language itself, it needs to offer more benefit than just "the same thing but in the language."
The OP mentioned possible optimizations or inlining or things like that. At least some of those would need to be included initially to justify the RFC. Eg, #[Pure, Memoize] to mark it pure and auto-memoize the function, #[Pure, Inlineable] to allow the compiler/optimizer to compile away the function call, maybe the function call itself could be made cheaper if we know the function is pure... there's likely lots of options, but someone with more engine knowledge would need to say what's feasible. But we would need something.
The other catch is that, as Marco notes, purity is not always trivial to discover. Especially when passing around objects, if you call a method on the object, it MIGHT mutate something? Who knows? So defining precisely what we're able to enforce would be important, too.
--Larry Garfield