Przemyślenia o type hinting
Dzisiaj chciałem podzielić się z Wami na temat kilku przemyśleń dot. type hinting i alternatywnego sposobu implementacji tego ficzera.
Po co w ogóle type hinting ? Jak dla mnie type hinting spełnia trzy role:
- dokumentacja
- walidacja wejścia
- lukier składniowy
Dzięki roli dokumentacji wiemy jaki typ argumentu należy przekazać do funkcji by działała ona w sposób przewidziany przez jej autora. Dzięki walidacji wejścia autor funkcji upewnia się, że dostaje właściwe parametry, których użyje aby wyprodukować wynik. Natomiast dzięki lukrowi składniowemu jaki dostarcza typ hinting, zamiast pisać przydługawe if-y takie jak:
function foo($bar, $baz, ...){ if(is_int($bar)){ //... } if(is_string($baz)){ //... } }
piszemy po prostu function bar (int $bar, string $baz){//...}
co znacznie skraca zapis.
Generalnie w obecnych wersjach języka, jedyną rzeczą jakiej brakuje jest owy cukier składniowy.
Dokumentowanie istnieje dzięki tagom phpDoc, sprawdzić argumenty możemy dzięki wyżej wspomnianemu if-owi. Czyli tak naprawdę type hinting wiele nie wnosi.
Mogłby jednak wnosić bardzo wiele, o ile umożliwiłby nie tylko sprawdzanie typów ale „grubszą” walidacje np. na wyrażenia regularne, czy obecność pewnych kluczy w tablicy. Szalone ? Nie możliwe ? Oczywiście, że nie!
Poniżej przykładowa implementacja w Pythonie który umożliwia takie rzeczy a poniżej wytłumaczenie:
from ctypes import ArgumentError from re import match class param: def __init__(self, type, name): self.__type = type self.__name = name def __call__(self, function): def decorated(*args, **kwargs): if self.__type.__class__.__name__ == 'str': test = match(self.__type, kwargs[self.__name]) if test == None: raise ArgumentError('Argument: >>' + self.__name + '<< must match pattern >>"'+self.__type + '"<<') else: arg_type = kwargs[self.__name].__class__.__name__ proper_type = self.__type.__name__ if arg_type != proper_type: raise ArgumentError('Expected object of type >>'+proper_type + '<< for argument >>' + self.__name + '<<') function(*args,**kwargs) return decorated @param(int, 'bar') @param('ala.','baz') def foo(bar, baz): print bar, baz foo(bar = '1', baz='ala ma')
Co ten kod robi ? Kod ten używa ficzera Pythona zwanego dekoratorami funkcji. Pokrótce mówiąc, jest to cukier składniowy dzięki któremu można w łatwy sposób przekształcać funkcje.
@param(int, 'bar') @param('ala.','baz') def foo(bar, baz): print bar, baz
Zapis jest podobny do phpDoca więc powinniście go rozszyfrować – @param(int,'bar')
oznacza, że parametr bar funkcji foo powinien być inte-em, natomiast @param('ala.','baz')
oznacza, że parametr baz powinien spełniać wyrażenie regularne ‚ala.’
Działa to w ten sposób, że pisząc @param(parametr1, parametr2 ...)
przed deklaracją funkcji jest ona przekazywana do wcześniej zdeklarowanej klasy ‚param’, która w konstruktorze bierze argumenty dekoratora(parametr1, paramtr2) a w funkcji __call__ która jest odpowiednikiem php-oweg __invoke robi coś z nimi oraz naszą funkcją i zwraca przekształconą funkcję. W naszym przypadku przeprowadza walidacje parametrów i jeżeli jej nie pasują to wyrzuca wyjątek a jeżeli jest wszystko ok to wywołuje naszą oryginalną funkcje.
I co o tym wszystkim sądzicie ?
Czy mechanizm dekorowania funkcji przydałby się w PHP ?
Czy wolicie ‚tradycyjny’ type hinting ?
Adnotacje http://ix.livedata.pl/adnotacje-troche-praktyki/
Oczywiście znam mechanizm adnotacji, jednak same adnotacje są tylko informacją i aby jakoś „ożyły” potrzebna jakaś funkcja która je odczyta i wykorzysta. Czyli w php musielibyśmy wywołać funkcję
foo()
za pośrednictwem innej funkcji, która odczyta adnotacje i coś z nimi zrobi, zdaje się, że podobnie jest w Javie. Tutaj adnotacja/dekorator zmienia obiekt który adnotuje w czasie deklaracji…Myślę, że można poczekać na natywne wsparcie dla adnotacji, które zgodnie z RFC niedługo powinno skoczyć na internals – jeśli już się tam nie pojawiło. Zobaczymy jak samo to będzie wyglądało… Dopisanie patch’a czy kolejnego liba chyba nie będzie już problemem ( np. mamy runkita ).
No niby mamy runkita ale nie ma go w standardowych dystrybucjach (więc i na hostingach), przez co defakto jest całkowicie bezużyteczny…
Jedno jest pewne, kod pythona jest dla mnie nieczytelny – brak klamr przypomina pascala, a * przypominają ANSI C – zgroza. A ficzer, choć ciekawy, moim zdaniem jest nadmiarowy, regexy to nie jest sposób validacji który jakoś szalenie często używam.
Klamry – no cóż, kto co lubi, mnie w PHP irytują nawiasy ‚()’, trzeba je w każdym if-ie i pętli stosować i w zasadzie zupełnie niepotrzebnie (goście od googla dali rade w języku Go).
*args
to sposób na funkcje ze zmienną liczbą parametrów.Może regexp nie jest czymś czego często używasz, ale pewnie stosujesz różne walidatory Zenda typu Date czy Between, czy chociażby PostalCode, dzięki temu, można łatwo takie rzeczy dorzucać, razem z filtrowaniem itp.