rfc:single-expression-functions

PHP RFC: Single-Expression functions

  • Version: 1.0
  • Date: 2025-05-22
  • Author: Dmitrii Derepko xepozzd@gmail.com
  • Status: Draft
  • Target Version: PHP 8.5

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.

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.

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.

Current Syntax Problems

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.

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;
    }
}

Rationale

Distinction from Arrow Functions

This proposal deliberately uses = instead of => to avoid confusion with existing arrow function syntax (fn() => expression). The key differences are:

  • Scope: Short functions with = are regular named functions with global scope
  • Capture: No variable capture from parent scope (unlike fn)
  • Syntax: Uses function keyword, not fn
  • Purpose: Syntactic sugar for simple return statements

Benefits

  1. Reduced verbosity for simple functions
  2. Improved readability for single-expression functions
  3. Consistency with modern language trends toward concise syntax
  4. No performance impact - purely lexical transformation
  5. Visual clarity - removes 8 symbols, 1 line instead of 4
  6. Faster comprehension of function purpose and return value
  7. Consistent with mathematical notation where f(x) = expression
  8. Maintains clarity while eliminating redundancy

Use Cases

Getter methods:

class User 
{
    public function __construct(
        private string $name,
        private string $email
    ) {}
 
    public function getName(): string = $this->name;
    public function getEmail(): string = $this->email;
    public function getDisplayName(): string = ucfirst($this->name);
}

Simple calculations:

function calculateTax(float $amount): float = $amount * 0.18;
function isEven(int $number): bool = $number % 2 === 0;
function formatCurrency(float $amount): string = '$' . number_format($amount, 2);

Delegation methods:

class Service {
    public function __construct(private Repository $repo) {}
 
    public function findById(int $id): ?Entity = $this->repo->find($id);
    public function count(): int = $this->repo->count();
}

Technical Implementation

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

  • No runtime performance impact
  • Full compatibility with existing reflection APIs
  • Identical behavior to traditional function declarations

Backward Compatibility

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

Alternative Syntax Considered

Using => operator

The original Short Functions RFC proposed using =>, but this creates confusion with arrow functions:

// Confusing - looks like an arrow function
function add(int $a, int $b): int => $a + $b;
 
// Clear distinction
function add(int $a, int $b): int = $a + $b;

Pipe Operator usage

// multiline
function handle(string $input): string = func1($input)
	|> func2(...)
	|> func3(...)
	|> func4(...)
	;
 
// oneline
function handle(string $input): string = func1($input) |> func2(...) |> func3(...) |> func4(...);

Clone

class Point
{
    public function __construct(private int $x, private int $y) {}
 
    public function withX($x): static => clone($this, ["x" => $x]);
    public function withY($y): static => clone($this, ["y" => $y]);
}
class Point
{
    public function __construct(private int $x, private int $y) {}
 
    public function withX($x): static 
    {
    	return clone($this, ["x" => $x]);
    }
 
    public function withY($y): static 
    {
    	return clone($this, ["y" => $y]);
    }
}

Single-Expression functions in other languages

Kotlin

fun add(a: Int, b: Int): Int = a + b
fun getVersion() = 1
fun isEven(n: Int) = n % 2 == 0

Scala

def add(a: Int, b: Int): Int = a + b
def getVersion: Int = 1
def square(x: Int) = x * x

F#

let add a b = a + b
let getVersion () = 1
let isPositive x = x > 0

Rust

fn add(a: i32, b: i32) -> i32 = a + b;
fn get_version() -> i32 = 1;
fn add(a: i32, b: i32) -> i32 { a + b }

Haskel

add :: Int -> Int -> Int
add a b = a + b
 
getVersion :: Int
getVersion = 1

Swift

func add(_ a: Int, _ b: Int) -> Int = a + b 
func getVersion() -> Int = 1

C#

public int Add(int a, int b) => a + b;
public string FullName => $"{FirstName} {LastName}";
public int GetVersion() => 1;

Dart

int add(int a, int b) => a + b;
String get fullName => '$firstName $lastName';

Elixir

def add(a, b), do: a + b
def get_version, do: 1

Javascript/Typescript (named closures)

const add = (a, b) => a + b;
const getVersion = () => 1;

Limitations

  1. Single expression only - multi-statement functions require traditional syntax
  2. No variable assignments - cannot declare variables within the expression
  3. No control structures - if/else must use ternary operator

Future Scope

This feature could potentially be extended to:

  • Interface method declarations with default implementations
  • Omitting the function keyword to declare a function – class member

References

rfc/single-expression-functions.txt · Last modified: 2025/05/22 09:10 by xepozz