PHP não precisa de um PHP mais rápido

Eu aprendi a programar em C. Quando você vem de C você tipa tudo por reflexo e estranha quando a linguagem deixa passar qualquer coisa. Foi por isso que nunca me acostumei com PHP sem tipos. Eu passo tempo demais escrevendo guardrail, checando o que entrou, o que saiu, tentando adivinhar o que vai quebrar em produção porque veio um tipo que eu não esperava. Nos últimos meses eu mantive bibliotecas onde o PHPStan faz o trabalho que eu queria que o engine fizesse. Funciona, mas é um imposto.

O imposto do comentário

Se você usa PHPStan ou Psalm hoje, você já usa generics. Só que dentro de comentário. A docblock mente que é documentação e o analisador finge que é código.

/**
 * @template T
 * @param Collection<T> $items
 * @return T
 */
function first(Collection $items) { /* ... */ }

São mais de 202 mil arquivos no GitHub usando esse truque. É o que o Brent Roose chamou de comment tax. A RFC de Bound-Erased Generic Types do Seifeddine Gmati propunha só tirar isso do comentário e colocar na sintaxe. Box<int> e Box<string> seriam a mesma classe em runtime, o tipo viraria o bound declarado, sem custo em prod, com segurança em dev. O motor veria mixed ou o bound, o Reflection guardaria a forma original para a ferramenta ler. É o modelo apagado, igual Java faz.

Eu preferia que tivesse passado. Era o que fazia mais sentido para mim, e vai na linha do que o PHP Annotated já defendeu, checar tudo em análise e rodar sem checagem extra em produção. O próprio Brent defendeu isso em 2021 no texto We don’t need runtime type checks e de novo na série de quatro posts sobre por que não temos generics no PHP. A PHP Foundation também mapeou as alternativas em State of Generics and Collections e admitiu que reificado é muito mais caro em um interpretado.

Erased generics as proposed are a direct continuation on this path, which is already proved viable and useful. Thanks to IDEs, static analysis in PHP has a much wider user-base than just the active users of phpstan, psalm, et al. — Nicolas Grekas, citado em A generic tragedy

Só que eu entendo por que não passou. A RFC foi declinada em maio e a Fundação já tinha sinalizado a preferência por outro caminho em Compile time generics yay or nay. O PHP escolheu faz tempo que tipo é checado em runtime. Quando você escreve function add(int $a) o engine converte ou joga TypeError na hora. É assim que a linguagem pensa. Colocar generics que somem seria criar duas regras na mesma casa. Um tipo que checa em runtime e outro que some. Parece detalhe, mas linguagem incoerente cobra juros por anos. Quem mantém algo que roda boa parte da web não pode experimentar rápido. Como o Nuno bem colocou, o internals move devagar por um motivo.

PHP Annotated sobre generics no PHP e a proposta de bound-erased generics. Veja a RFC completa.

TypePHP, o binário que não é PHP

É dessa frustração que nasce o TypePHP, o antigo Swoole AOT rebatizado. Ele pega PHP 8.4 ou 8.5, baixa para C++17 e gera binário nativo. O site mostra fib(40) 135 vezes mais rápido que ZendVM e PI 69 vezes. Esses números são reais e contam metade da história.

Quando você roda bench.php e micro_bench.php do próprio php-src o ganho cai para oito vezes e seis vezes e meia. Ainda é bom, mas é computação pura. Aplicação web típica não vive de Fibonacci. O projeto impressiona por outro motivo. Ele é escrito em PHP e se compila, é self-hosting de verdade, e tem três modos, binário, extensão e lib compartilhada. Só que ele não é drop-in.

A lista de incompatibilidades tem quase duzentas linhas. Escopo global só pode declarar, código tem que estar em função com main(), $$ não existe, closure não pode ser rebindada, union ainda vira mixed no C++ gerado, propriedade com tipo nativo não pode mudar de tipo depois. O próprio README avisa que é um subconjunto definido e testável. O marketing diz compile seu PHP e seja feliz. A doc diz traga só o pedaço que compila.

Para CLI, jogo, desktop, WASM e workload pesado de CPU, faz todo sentido. O repositório já mostra exemplos compilando para Android, iOS e Godot. Para levar um Laravel para binário amanhã, não.

Nuno Maduro sobre a chegada de um compilador para PHP e por que generics divide tanto o internals.

Whim, o PHP que admite que não é PHP

Whim é outra aposta. É do mesmo Seifeddine Gmati, autor da RFC que não passou, e criador do Mago, o toolchain em Rust que finalmente fez lint e análise em PHP serem rápidos, com 3.3k estrelas e mais de 1.6M installs. Se o core não aceitou generics, ele fez a linguagem que ele já analisa. Arquivo .whim, sintaxe familiar, mas não é PHP.

Whim e Mago - repositórios da Carthage Software no GitHub

Whim tem generics reificados com pattern matching em runtime, aquele exemplo de match com Box<int> diferente de Box<string>, tem classes e enums, tasks cooperativas e stdlib própria para HTTP e Postgres. É interpretada, em Rust, com playground no ar. Tem vinte e poucas estrelas, é experimental. E é aí que ela me interessa mais, mesmo eu preferindo apagado.

Eu concordo com o PHP Annotated, para mim apagado seria mais limpo e mais rápido, checar em dev e rodar sem peso em prod. Mas tudo bem se não for apagado. Whim escolheu reificado e eu entendo a escolha. Ela não finge que é PHP compilado. Ela diz que é outra linguagem com cara de PHP. É mais fácil confiar em algo que admite o que é.

O Nuno também comentou o lançamento recente do Whim como basicamente PHP mas com generics, async e pattern matching sem esperar 2030. Vale ver o tom da comunidade no r/PHP, onde o medo recorrente é repetir Hack, Facebook fez HPHPc, depois HHVM, depois Hack, e o ecossistema rachou.

Nuno Maduro apresentando Whim, a linguagem com cara de PHP feita em Rust.

O Mojo do PHP

Muita gente chama os dois de fragmentação, o velho xkcd de criar a ferramenta que unifica quinhentas ferramentas e terminar com quinhentas e uma. Eu não vejo assim. Fragmentação pressupõe que alguém está tentando substituir o centro. Esses projetos são laboratório.

Whim para mim é o Mojo do Python. Provavelmente não vinga como produto que vai rodar seu SaaS, mas pode vingar como pressão. Ideias boas vazam de volta para o centro. O PHP já fez isso antes. Hack nasceu de frustração parecida e o core acabou absorvendo parte das ideias com seu próprio tempo e suas próprias restrições. Até um experimento como userland-php-generics que monomorfiza classe em runtime com FFI mostra que tem gente tentando medir o custo real de reificado que o internals diz ser caro demais.

Na era de IA isso importa mais do que parece. Eu gero código com modelo todo dia e modelo funciona melhor quanto mais tipo tem. Código bem tipado é mais previsível para máquina e para humano. Se o PHP demora para dar tipos de verdade, a barreira entre linguagens que a IA derrubou joga contra o PHP. Não porque Python ou Rust sejam sempre melhores, mas porque eles deixam o gerador errar menos.

Eu preferia apagado. O internals fez bem em dizer não por coerência, mesmo me frustrando. Entre um compilador que promete que seu PHP vai virar binário nativo e uma linguagem nova que admite que não é PHP, eu fico com a segunda. Não porque ela é mais rápida hoje e nem porque eu ache reificado ruim. Fico porque ela não me vende atalho. Ela mostra como seria se a gente começasse de novo sem carregar quarenta anos de compatibilidade. Se o PHP copiar uma boa ideia dela daqui a dois anos, ela já terá valido a pena, mesmo que ninguém faça deploy de um .whim em produção.


Links para acompanhar: RFC Bound-Erased e discussão no internals, A generic tragedy do Brent, TypePHP no GitHub e doc de performance, Whim no GitHub e Mago.