Dziś jest piątek, 12 marca 2010 roku (z kalendarza...)

Jak mogłoby wyglądać PHP?

Icon

27.01.2010, 17:45

PHP

Komentarze (36)

Powrót

Ostatnio w wolnych chwilach trochę zastanawiałem się, jak mógłby wyglądać następca PHP. Nie chodzi mi o jakieś mityczne PHP 7, ale o zwyczajne wzięcie się i zaprojektowanie tego języka od zera, na wstępie wyrzucając wszystkie irytujące niedociągnięcia. W tym wpisie chciałbym podzielić się wynikami tego eksperymentu myślowego.

PHP kontra reszta świata

Na PHP wieszają psy prawie wszyscy, a w szczególności ludzie, którzy bardzo mało w nim programowali lub dawno zarzucili swe więzy z tym językiem. Dlatego przed rozpoczęciem zabawy postanowiłem zebrać do kupy wszystko, co mnie w tym języku wkurza, a co jest plusem, zwłaszcza w kontekście moich ostatnich zabaw z Rubym.

Minusy:

  1. Mega-bałagan w bibliotece standardowej.
  2. Niewykorzystywanie możliwości języka przez niego samego. Dlaczego 95% rozszerzeń, nawet jeśli powstały już za czasów PHP 5, generuje błędy przy pomocy Warning, kiedy od sześciu lat są wyjątki, albo wynajduje koło od zera w tej dziedzinie?
  3. Naleciałości historyczne w semantyce samego języka, które trochę utrudniają pisanie.
  4. Jak wyżej, ale w składni.
  5. Wiele bardzo potrzebnych dodatków zostało dodanych niedawno, więc nie do końca działają poprawnie. Przykładem jest nowy odśmiecacz pamięci, który byłby fajny, gdyby nie wycieki pamięci oraz wykrzaczanie interpretera na tak popularnych aplikacjach, jak PHPUnit.
  6. Brak wielowątkowości.
  7. Niespójny system typów.

Plusy:

  1. Elegancka składnia wywodząca się z rodziny C. Przynajmniej ja osobiście uważam ją za dużo lepszą i bardziej precyzyjną, niż wynalazki w takim Rubym.
  2. Świetny model obiektowy - jest tylko kilka drobnych braków, które można doszlifować, lecz poza tym korzysta się z niego bardzo przyjemnie.
  3. Pojedyncze, dobrze zaprojektowane rozszerzenia: PHAR, PDO
  4. Skalowalność - dobrze sobie radzi z prostymi skrypcikami, by coś sprawdzić lub policzyć na szybko, jak i z większymi aplikacjami.

Teraz możemy przejść do zabawy w poprawianie PHP.

Zmienne...

Ze zmiennymi w PHP mamy trochę shizofreniczną sytuację. Teoretycznie można je używać od razu bez potrzeby deklarowania czy inicjacji, ale w praktyce trzeba to robić, aby nie zostać zawalonym setkami komunikatów Notice. To, czy mają się one wyświetlać czy nie, zależy od ustawień konfiguracji i widzimisię administratora i naprawdę jest ciężko, gdy trzeba skorzystać ze skryptu, którego twórca sobie sprawę olał. Dlatego podstawowa zmiana to wymuszenie zainicjowania zmiennej przed użyciem pod groźbą rzucenia wyjątku. Nie jest ważne, gdzie to robimy, jak w Javie. Po prostu w momencie, gdy odwołujemy się do zmiennej, interpreter musi mieć w pamięci informację, że jest do niej jawnie przypisana wartość.

System typów

System typów można znacząco uprościć zauważając, że nie ma żadnego technicznego uzasadnienia, dla którego tablica z haszowaniem jest "tablicą", a wszystko inne, wliczając w to pozostałe struktury danych - obiektem. W języku potrzebne są właściwie dwa typy:

  1. Scalar - typ prymitywny: string, integer, float, boolean
  2. Object - typ złożony, czyli obiekt

Nie jestem zwolennikiem wciskania obiektów w każdy najmniejszy zakamarek języka. Ma to duży sens przy rozważaniach akademickich, ale w praktyce rodzi wiele trudnych pytań, na które nie ma dobrej odpowiedzi. Przypuśćmy, że integer byłby typem obiektowym? Jak powinienem rozumieć zapis 13? Czy jest to obiekt liczbowy z wartością "13", a może obiekt "13"? Czy dwa obiekty liczbowe o wartości 13 są tym samym czy czymś innym? Jeśli obiekt liczbowy, to czym jest pojęcie "wartości", skoro w języku wszystko jest obiektem? Obiekt powinien reprezentować byt, więc dlaczego "13" nie jest obiektem reprezentującym liczbę 13 poprzedzonym przez obiekt "12", a następującym po "14"? Jak to się ma do efektów ubocznych funkcji, kiedy obiekty kopiowane są przez referencję, a nie przez wartość? I najważniejsze: na co tyle obiektów w pamięci?

System typów nie musi być ścisły, jak w Javie, stąd nie byłoby problemów z umieszczaniem wartości prymitywnych w strukturach danych ani przekazywaniem ich poprzez argumenty:

function foo($foo, scalar $bar, ClassName $joe)
{
   // ...
} // end foo();

W obrębie typu 'scalar' interpreter zachowywałby się tak samo, jak w PHP, automatycznie rzutując wartości tam, gdzie to konieczne. Jednak przejście z obiektu na typ skalarny lub na odwrót bez jawnej konwersji możliwe by nie było. Praktyka pokazuje, że jest to źródłem wielu błędów, a przy okazji złych praktyk programistycznych, jak np. funkcja("$zmienna").

Skoro już wspomnieliśmy o praktykach programistycznych, warto zastanowić się nad problemem niechlujnego pisania. Twórca Pythona postanowił leczyć nierobienie wcięć, czyniąc je integralnym elementem języka. W poprawionym PHP wyrzuciłbym interpolację ciągów tekstowych: "tekst $zmienna tekst". Nie jest ona do niczego potrzebna - dokładnie ten sam efekt można osiągnąć kropką i zwyczajnym scalaniem, a jest to nawet wydajniejsze i przynajmniej da się uniknąć takiego potworka, jak powyżej.

W obrębie typów prymitywnych wprowadzone mogłyby być następujące zastrzeżenia:

  1. Długość typu liczbowego zależy od platformy, jednak przy próbie jego przekroczenia liczba jest w tle konwertowana na typ "big integer", jak w Rubym. Użycie systemu 64-bitowego powinno po prostu znacząco przyspieszyć obliczenia na dużych liczbach.
  2. Ciągi tekstowe zakodowane są wewnętrznie w UTF-16; cały język wykorzystuje to kodowanie natywnie. Dlaczego UTF-16? Jest to kompromis między zwięzłością UTF-8 dla języków anglosaskich, a przydatnością do zapisu alfabetów nieeuropejskich. Podobne założenie przyjęte jest w Javie.

Rozszerzenia składni

Pierwsza przydatna rzecz to blok 'finally':

try
{
   // blok krytyczny
}
catch(Exception $wyjatek)
{
   // obsługa wyjątku
}
finally
{
   // sprzątanie, obowiązkowe
   // bez względu na to czy rzucamy wyjątek czy nie
}

Problem sprzątania po wyjątkach jest dość poważny. W szczególności wyskoczenie ze środka dość ważnego procesu mogłoby zostawić go w stanie niespójnym i uczynić dany kawałek kodu niezdatnym do użycia.

Wprowadzenie cech do języka, znanych pod angielską nazwą "traits", co nawiasem mówiąc już jest zaproponowane do PHP. Cecha to przydatny sposób na ominięcie braku wielodziedziczenia. Luki po nim nie łatają interfejsy. Cecha to zwyczajny zbiór metod i pól, które na etapie kompilacji są "w tle" doklejane do danej klasy, dzięki czemu możliwe jest wstrzyknięcie także implementacji. Ważną własnością cech jest ich prostota - rodzą one dużo mniej problemów niż domieszki, zwłaszcza w przypadku konfliktów nazw.

trait Foo
{
   public function metoda()
   {
      echo 'Hello!';
   } // end metoda();
}
 
class Bar
{
   use Foo;
 
   // kod...
}

Język oczywiście posiadałby przestrzenie nazw, ale skoro nie jesteśmy ograniczeni wsteczną kompatybilnością, wykonujemy roszadę:

  • do łączenia ciągów tworzymy operator ~,
  • zwolnioną kropkę wykorzystujemy jako separator przestrzeni nazw.

Co więcej, dodajemy polecenie import do importowania klas. Byłaby to część zupełnie przeprojektowanego mechanizmu odnajdowania plików z kodem. Prymitywne require oraz include_path po prostu nie sprawdza się w sytuacjach, gdy różne klasy mogą mieć różne przeznaczenie. We frameworkach chcemy, aby biblioteki były elegancko dołączane z określonego katalogu, ale klasy kontrolerów i modeli rządzą się własnymi prawami. Tutaj zastosowałbym pomysł rodem z Open Power Libs. Interpreter dostaje nazwę klasy oraz przestrzeni nazw do załadowania. Bada sobie więc pierwszy element tej przestrzeni i sprawdza w katalogu, jaka ścieżka jest dla niego przewidziana oraz ew. jaka zewnętrzna ładowarka:

Loader::addPath('framework', '../lib/');
Loader::addPath('controller', '../app/frontend/controllers');
Loader::register('controller', myCustomLoader);
 
// Ręczny import
import framework.logs.Logger;
 
// Autoładowanie
$foo = new controller.Index;
 
// Dołączanie pliku z luźno wstawionym kodem
// wyłącznie ścieżki relatywne lub bezwzględne
require('../plik.php');

Zaletą tego podejścia jest wydajność. include_path przy skanowaniu po kolei sprawdza wszystkie katalogi, a operacje dyskowe kosztują, zwłaszcza gdy wszystko musi przejść jeszcze przez naszą dodatkową ładowarkę. Dlaczego interpreter ma skanować wszystko, kiedy może po prostu przeczytać, o jaką przestrzeń nazw nam chodzi i od razu skoczyć do wymaganej lokalizacji?

Niedociągnięciem PHP jest brak kontroli nad elementami statycznymi klas. Nie ma ani statycznego konstruktora, ani możliwości ewentualnego wyczyszczenia stanu, co bardzo utrudnia debugowanie. Dlatego powitałbym takie rozwiązanie:

class Foo
{
   static private $foo;
 
   static function __construct()
   {
      echo 'Konstruktor statyczny';
   } // end __construct();
 
   static function __destruct()
   {
      echo 'Destruktor statyczny';
   } // end __destruct();
} // end Foo;

Do tego pojawiłby się specjalny blok 'static' kontrolujący zakres dostępności elementów statycznych. Rozpatrzmy przykład:

static
{
  echo Klasa::$foo++;
}
 
static
{
  echo Klasa::$foo++;
}

Proces tworzenia i niszczenia elementów statycznych nie byłby już związany z początkiem i końcem działania skryptu, ale z blokiem static. W momencie pierwszego odwołania do elementu statycznego klasy wywoływany byłby statyczny konstruktor, a przy jego opuszczaniu - destruktor. Gdy interpreter wejdzie do drugiego bloku, zainicjuje statyczną część klasy od nowa. Domyślnym blokiem static byłby cały skrypt, przez co bez korzystania z tej funkcjonalności wszystko działałoby po staremu. Za to pakiety do testowania jednostkowego mogłyby bardzo łatwo zapewnić izolację, po prostu wykonując każdy test we własnym bloku static. Byłby to też sposób na kontrolowanie życia singletonów. Jako rozszerzenie można by podawać w nawiasie, jakich klas ma dotyczyć statyczny blok: static(Klasa1, Klasa2, Klasa3){ ... }.

W obiektach doszłoby do małego uporządkowania paru drobiazgów. Najważniejszą zmianą byłoby ustalenie późnego wiązania statycznego jako domyślnego, gdyż obecnie jest tu pewna niekonsekwencja - w obiektach mamy pełen polimorfizm, a w elementach statycznych już nie. Uściślone zostałyby także niewłaściwe praktyki programistyczne.

Ostatnia ciekawa zmiana to wprowadzenie programowania kontraktowego. Chodzi o dodanie możliwości definiowania kontraktów mówiących o tym, jaki jest spodziewany stan wejściowy i jaki wyjściowy:

public function foo($argument)
in
{
   assert($argument instanceof Foo || $argument !== null);
}
out($result)
{
   assert($result != null);
}
body
{
   return $foo->processSomething();
}

Identyczny patent zastosowany jest w języku D, jednak tam kod kontraktów kompilowany jest na życzenie. W języku interpretowanym byłby to dodatkowy (spory) narzut wydajnościowy, dlatego należałoby najpierw pomyśleć, jak sobie z tym poradzić.

Ostatnie dodatki służyłyby do szybkiego tworzenia struktur danych:

// tablica z haszowaniem
$foo = new ['foo' => 'bar', 'joe' => 'goo'];
 
// słownik
$bar = new {'foo', 'bar', 'joe'};
 
// tablica o stałym rozmiarze
$joe = new 15:['foo', 'bar', 12 => 'joe'];

Biblioteka standardowa

Kluczem w bibliotece standardowej jest porządek i niemal pełna obiektowość. Funkcjami pozostałyby podstawowe operacje na typach prymitywnych, ale zostałyby one umieszczone w odpowiednich przestrzeniach nazw, np. math.sin() i logicznie ponazywane. Poza tym cała reszta opierałaby się na obiektach: bazy danych, XML, przetwarzanie obrazków, wyrażenia regularne i co tam jeszcze sobie wymyślimy. Wszystkie błędy raportowane jako wyjątki, co wspólne, jest konfigurowalne przez wspólne API i nie ma czegoś takiego, że każde rozszerzenie sobie wymyśla np. własne funkcje obsługi błędów.

Współbieżność

Jedną z największych bolączek PHP jest brak wsparcia dla programowania współbieżnego, co uniemożliwia praktycznie tworzenie porządnych serwerów aplikacji. Do wyboru są dwa podejścia. Pierwsze to zaimplementowanie klasycznych prymitywów takich, jak klasa Thread, semafory, monitory, zmienne warunkowe i zamki. Są one powszechne, ale na dłuższą metę ciężkie w użyciu, szczególnie w większych aplikacjach. Rozwiązaniem mogłoby być wprowadzenie alternatywnego modelu współbieżności tak, jak to zrobił Erlang, gdzie mamy procesy, które przesyłają sobie komunikaty. Myślałem trochę nad erlangowym modelem, ale jest tu pewien problem: Erlang jest językiem funkcyjnym. Nie ma w nim prawdziwych zmiennych, a tym samym i efektów ubocznych, w związku z czym przesłanie obiektu w komunikacie innemu procesowi absolutnie niczym nie grozi. W językach imperatywnych na przeszkodzie stoi właśnie możliwość zmiany wartości obiektów niespodziewanej dla drugiego wątku/procesu i uszkodzenie aplikacji.

Środowisko

Aplikacje napisane w PHP mogą pracować w różnych trybach: konsola, odpowiedź na żądanie HTTP czy aplikacja okienkowa, jednak dostanie się do informacji o aktualnym środowisku uruchomieniowym jest straszliwie pomieszane. Nikt chyba nie ma wątpliwości, że obecność zmiennej $_POST, gdy aplikacja uruchomiona jest na konsoli, nie ma najmniejszego sensu. Pomimo tego taka zmienna istnieje i ma się dobrze.

Rozwiązaniem tego byłoby wprowadzenie nowego prymitywu o nazwie Environment, po którym dziedziczyłyby szczególne rodzaje środowisk. Każdy skrypt w momencie odpalenia dostawałby obiekt aktualnego środowiska, poprzez który mógłby pobrać odpowiednie dane. Oto przykładowy pseudokod dla skryptu odpalanego na dzisiejszą modłę, gdy proces obsługuje pojedyncze żądanie HTTP:

$environment = lang.getEnvironment();
if($environment instanceof HttpRequestEnv)
{
   echo 'Jestem uruchomiony jako generator odpowiedzi na żądanie HTTP. Dane POST: ';
   foreach($environment.getPost() as $name => $value)
   {
      echo $name~' => '~$value.EOL;
   }
}
if($environment instanceof ConsoleEnv)
{
   echo 'Jestem uruchomiony jako aplikacja konsolowa.';
}

Jeszcze ciekawszy przykład zastosowania to serwer aplikacji, który można odpalić jako aplikację FastCGI i podłączyć do serwera WWW. Odpalone z niego procesy mogłyby komunikować się z procesem-matką, który zarządzałby konfiguracją, połączeniami z bazą itd. dzięki czemu nie trzeba by się tym zajmować w kółko za każdym razem:

$env = lang.getEnvironment();
if(!$env instanceof FastCGIEnv)
{
   throw new EnvironmentException('Nieprawidłowe środowisko');
}
 
import appserver.Request;
 
while($message = $env->dispatchMessage())
{
   switch($message)
   {
      instanceof fastcgi.Init:
         appserver.initialize();
         break;
      instanceof fastcgi.Request:
         thread.spawn(appserver.Request.run, appserver.prepareProcessData(), $message->getRequest());
         break;
      instanceof fastcgi.Done:
         appserver.done();
   }
}

Przy okazji widzimy tu ciekawe rozszerzenie możliwości instrukcji switch, nad którym także można by się zastanowić.

Interpreter

Ostatnie propozycje to przede wszystkim wbudowany od początku odśmiecacz pamięci oraz wbudowane automatyczne kompilowanie do bajtkodu, które zrzuca obraz do pamięci lub na dysk, by nie robić tego po sto razy.

Zakończenie

Mam nadzieję, że wpis był inspirujący i zachęcił do pomyślenia nad kształtem PHP teraz i w przyszłości, a zaprezentowane tutaj kierunki rozwoju się Wam spodobały.

Powrót

Komentarze

Napisał Zyx w poniedziałek, 1 lutego 2010 o 17:34

WebCM -> udostępnia, nazywa się PHP_EOL i zawiera znak nowej linii charakterystyczny dla systemu operacyjnego, na którym uruchomiony jest interpreter.

bigZbig -> problem z polami statycznymi polega na tym, że jeśli są one prywatne, to o ile twórca klasy tego nie przewidział, za Chiny ich nie zresetujesz. Dopiero w PHP 5.3 można przy pomocy długiego kombinowania i zabaw z Reflection API dostać się z zewnątrz do prywatnego pola obiektu, ale bynajmniej nie jest to ani wygodne, ani przyjemne.

A o pomysłach wprowadzenia cech do PHP wiem - zamieściłem info o nich dlatego, że jak najbardziej je popieram.

Napisał Chlebik w poniedziałek, 1 lutego 2010 o 23:53

Po prostu w momencie, gdy odwołujemy się do zmiennej, interpreter musi mieć w pamięci informację, że jest do niej jawnie przypisana wartość.

No w Javie nie musi - wystarczy, ze zmienne instancji zostana zadeklarowane - automatycznie maja NULL, ale nie wiem czy to inicjalizacja i jawne podanie wartosci.


Twórca Pythona postanowił leczyć nierobienie wcięć, czyniąc je integralnym elementem języka.

Bodajże Spolsky napisal swego czasu artykul na ten temat i ja sie z nim zgodze - biale znaki w formie elementu jezyka (poza rzecz jasna spacja sluzaca do rozdzielania instrukcji) to porazka. Jego pomysl by pewien "coding standard" byl wymuszany przez kompilator (podawal przyklad stylu programowania dla C z ksiazki Richiego).


Nie ma ani statycznego konstruktora,

No to juz smierdzi Java na kilometr (jesli chodzi o statyczne bloki kodu). Czy naprawde jest cos zlego w obecnych statycznych metodach faktorujacych? Generalnie uzywanie statykow w jakiejs wiekszej liczbie nie jest zalecane - pisze zreszta o tym w przypadku Javy Joshua Bloch w swojej ostatniej ksiazce.


// tablica o stałym rozmiarze
$joe = new 15:'foo', 'bar', 12 => 'joe';


Java jesli chodzi o staly rozmiar, Ruby jesli chodzi o zakrecona skladnie.


Co do wspolbieznosci - poki co narzut, ktory zrobily serwery WWW na ogarnianie tematu procesow PHP jest tak duzy, ze proby wbudowywania tematu w jezyk sa lipne.


jednak dostanie się do informacji o aktualnym środowisku uruchomieniowym jest straszliwie pomieszane

99,9% kodu PHP to rzeczy uruchamiane przez WWW. 0,99% to skrypty CLI, ktore czesto po prostu odpowiadaja za administracyjna czesc aplikacji i sa uruchamiane z crona by np. poczyscic baze danych. Aplikacje standalone w PHP to 0,01% kodu czyli summa summarum zabawy zapalencow i geekow.



Ogólnie widzę, że chciałbyś mieć taką Javę, ale w ubranku PHP. Natomiast trzeba zwrócić uwagę na jedną rzecz - w przypadku języków obiektowych i kompilowanych sam język wymusza pewne zachowania i dlatego niby kod w nich powstajacy uwaza sie za "lepszy". A tak naprawde nic nie stoi na przeszkodzie pisania rownie dobrego kodu w PHP - to tylko kwestia "chcenia". Moim zdaniem wypadaloby po prostu kazdemu koderowi PHP zafundowac poznanie Javy na poziomie SCJP i potem ich kod juz nigdy nie bedzie taki sam :)


Napisał Zyx w wtorek, 2 lutego 2010 o 13:44

Serwery WWW nie dają żadnego "ekstra narzutu" nikomu, ani specjalnie nie uwalają PHP, ani to nie ma żadnego związku z oferowaniem wielowątkowości przez język. Już teraz, jak podłączysz PHP przez FastCGI, cały interpreter działa wielowątkowo (kilka wątków stale oczekuje na nadchodzące zgłoszenia), tyle że pojedynczy skrypt nie może się rozmnożyć na dwa i wykonywać współbieżnie. I brak tego skutecznie uniemożliwia tworzenie w PHP bardziej skalowalnych aplikacji. W moim zamyśle programista może, korzystając z prostego API dostarczonego w postaci wyżej w postaci environments napisać własny serwer FastCGI (serwer aplikacji), który wstępnie wszystko inicjuje, dzięki czemu nie trzeba w kółko konfigurować każdej głupoty przy każdym możliwym żądaniu HTTP.

PS. Tak w ogóle to wynalazki w stylu mod_php więcej zepsuły w świecie WWW niż przyniosły dobrego...

Natomiast czy to coś złego, że po prostu chcę mieć język o podobnej filozofii pracy, jak PHP, tyle że pozbawiony wynikających z przeszłości niedoróbek i braków? Java po prostu jest sensownym punktem odniesienia w wielu kwestiach, tyle że w innych z kolei sama jest nieco wybrakowana.

Napisał Chlebik w wtorek, 2 lutego 2010 o 17:02

Nie mowie, ze to zle jesli chodzi o wzorowanie sie na Javie :)

PS. Mysle, ze kiedy jest wiecej niz 1 strona komentarzy i na glownej klika sie od razu w np. cyferke 3 to ekran powinien przeskrolowac sie na dol do jakiejs kotwicy komentarzowej, a na razie po prostu wyswietla caly wpis z nowymi komentarzami i zostawia usera na gorze strony.

Napisał cypherq w wtorek, 2 lutego 2010 o 22:58

http://developers.facebook.com/news.php?blog=1&story=358

Może skrobniesz coś o tym? Tzn. jak Ty się zapatrujesz na takie kombinacje z kompilowaniem do PHP Extensions.

Napisał Sin w środę, 3 lutego 2010 o 17:42

Skoro tak odwołujesz się do javy zawsze możesz quercus używać ;) Przyjemne z pożytecznym.

Strona 3 z 3 :: 1 2 [3]

Pamiętaj, dbaj o kulturę wypowiedzi oraz dyskusji w sieci.

Skomentuj

NickInformacja
E-mailNa wypadek potrzeby kontaktu z autorem (niepublikowany)
BlogNie zapomnij o http://
LayoutNapisz tu, czy widzisz dzienny czy nocny layout.
WpisFormatowanie wiki

Na Zyxist.com panuje swoboda wyrażania opinii oraz krytyki pod dowolnym adresem. Jedyny warunek: musi być ona kulturalna i rzeczowa. Na chamstwo, prostactwo lub jawne obrażanie kogokolwiek nie ma tu miejsca i takie komentarze są bardzo szybko usuwane. Jeśli zamierzasz polemizować z treścią wpisu, wpierw uważnie ją przeczytaj.

© Tomasz "Zyx" Jędrzejewski 2005 - 2010 | Wykonanych zapytań: 2 | Serwer wirtualny zapewnia