rfc:single-expression-functions

PHP RFC: Single-Expression functions

Introduction

This RFC proposes an alternative syntax for single-expression functions using the => operator instead of curly braces and explicit return statements.

This syntax provides a concise way to declare simple functions without the overhead of traditional function body syntax.

In PHP there are several types of functions:

  • traditional/classic: function ...(...) {...}
  • closures: function ...(...) use (...) {...}
  • anonymous: $var = function (...) {...}
  • class methods: class ... { function ...(...) {...} }
  • arrow(+auto captures): fn(...) => ...

I'd like to discuss about class methods and classic functions.

Basically, they don't capture variable from outer scope. Class methods live inside the “this” scope.

There are a lot of code variants to be written in one line, but it's not acceptable with the functions.

One-liner is accepted with arrow function (fn () => ...), but methods cannot be written with “fn” declaration instead of “function”, as long as classic functions.

Proposal

This RFC introduces a shorthand syntax for functions that consist of a single return statement.

The proposed syntax uses the => operator to denote that the function directly returns the result of the expression.

*Shorthand syntax does not affect how capturing works.*

Reasoning

Modern codebases typically contain numerous entities, models, forms, data transfer objects (DTOs), and value objects (VOs) with many getter and setter methods. These methods are usually simple one-liners that create significant cognitive overhead with minimal functional value.

Concise syntax makes it easier to associate function names with their return values, reducing mental parsing overhead.

Pipe (https://wiki.php.net/rfc/pipe-operator-v3) and Partial Function Application https://wiki.php.net/rfc/partial_function_application_v2 develop ideology of functional programming and boost one-liners.

PSR

PSR-compliant syntax requires multi-line formatting:

function getName() 
{
   return "Name";
}

Even compact syntax contains redundant elements:

function getName() { return "Name"; }

Cognitive Processing Analysis

When reading traditional function syntax, developers mentally process:

function getName() { return "Name"; }

The brain performs this parsing:

  • function - skip (boilerplate)
  • getName - identify function purpose
  • () - note no parameters
  • { - skip (boilerplate)
  • return - skip (expected keyword)
  • "Name" - identify return value
  • ; - skip (boilerplate)
  • } - skip (boilerplate)

Effectively, the brain extracts:

getName() "Name"

This syntax directly represents the mental model: function maps to value, making the code more readable and reducing the cognitive load required to understand simple getter/setter methods.

Unfortunately, it's not acceptable by PHP, so there should be a delimiter, e.g. =>, which is used in arrow functions. Plus functionality scope keyword, meaning “function starts”.

function getName() => "Name"

Syntax

function functionName(parameters): returnType => expression;

This is functionally equivalent to:

function functionName(parameters): returnType 
{
    return expression;
}

Examples

Basic function:

// Short syntax
function getVersion(): int => 1;
 
// Equivalent traditional syntax
function getVersion(): int 
{
    return 1;
}

Function with parameters:

// Short syntax
function add(int $a, int $b): int => $a + $b;
 
// Equivalent traditional syntax
function add(int $a, int $b): int 
{
    return $a + $b;
}

Class methods:

class Calculator 
{
    // Short syntax
    public function multiply(int $a, int $b): int => $a * $b;
 
    // Equivalent traditional syntax
    public function divide(int $a, int $b): float 
    {
        return $a / $b;
    }
}

Proxy classes:

class Proxy 
{
    private $decorated;
 
    public function doA() => $this->decorated->doA();
 
    public function doB() => $this->decorated->doB();
}

Data Models classes:

class User 
{
    private $name;
    private $age;
    private $username;
 
    public function getAge() => $this->age;
 
    public function setAge($value) => $this->age = $value;
 
    public function getName() => $this->username;
 
    public function setName($value) => $this->name = $value;
 
    public function getUsername() => $this->username;
 
    public function setUsername($value) => $this->username = $value;
}

It should be possibly done with native property getter/setters, but such methods have different purpose compared to getters/setters. E.g. mind of UserInterface.

Backward Incompatible Changes

This change introduces no breaking changes. The proposed syntax would currently result in a parse error, making it safe to implement.

Proposed PHP Version(s)

PHP 8.5

RFC Impact

To SAPIs

The implementation is purely lexical—during parsing, the short syntax is transformed into the equivalent traditional syntax before further processing. This ensures no impact to SAPIs.

To Existing Extensions

Nope.

To Opcache

Implementation does compile-time changes that do not affect Opcache.

New Constants

Nope.

php.ini Defaults

Nope.

Open Issues

Make sure there are no open issues when the vote starts!

Unaffected PHP Functionality

Parent scope won't be captured as arrow functions do.

Current RFC do lexer/parser modification allows to shorten typical code.

Future Scope

This section details areas where the feature might be improved in future, but that are not currently proposed in this RFC.

Proposed Voting Choices

Single voting widget that requires 2/3rd majority.

Implement Single Expression Functions as outlined in the RFC?
Real name Yes No
alcaeus (alcaeus)  
alec (alec)  
ashnazg (ashnazg)  
bukka (bukka)  
bwoebi (bwoebi)  
crell (crell)  
derick (derick)  
devnexen (devnexen)  
edorian (edorian)  
ericmann (ericmann)  
galvao (galvao)  
girgias (girgias)  
heiglandreas (heiglandreas)  
josh (josh)  
kalle (kalle)  
malferov (malferov)  
mbeccati (mbeccati)  
ocramius (ocramius)  
rasmus (rasmus)  
sergey (sergey)  
thekid (thekid)  
theodorejb (theodorejb)  
timwolla (timwolla)  
Count: 5 18

Patches and Tests

Links to any external patches and tests go here.

If there is no patch, make it clear who will create a patch, or whether a volunteer to help with implementation is needed.

Make it clear if the patch is intended to be the final patch, or is just a prototype.

For changes affecting the core language, you should also provide a patch for the language specification.

Implementation

References

Rejected Features

Keep this updated with features that were discussed on the mail lists.

rfc/single-expression-functions.txt · Last modified: by nielsdos