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 ?

  1. Wojciech Soczyński pisze:

    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…

  2. 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 ).

  3. Wojciech Soczyński pisze:

    No niby mamy runkita ale nie ma go w standardowych dystrybucjach (więc i na hostingach), przez co defakto jest całkowicie bezużyteczny…

  4. Konradzik pisze:

    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.

  5. Wojciech Soczyński pisze:

    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.

  1. […] W wyniku dość długiej dyskusji, skrystalizowało się kilka wniosków – po pierwsze adnotacje nie powinny wprowadzać żadnej nowej składni ponad zaznaczenie, że dana część kodu jest adnotacją, czyli coś w rodzaju [kod php] gdzie „[" i "]” oznaczają początek i koniec adnotacji, natomiast jej treść jest poprawnym kodem php. Drugi wniosek jest taki, że grupa nr trzy stwierdziła, że skoro nie można dość do kompromisu w sprawie adnotacji, to może po prostu najlepiej by było wbudować parser phpDoc do mechanizmów refleksji. Trzeci wniosek, jest taki, że pewna część deweloperów nie widzi zastosowania dla adnotacji, co więcej nie wie nawet czym one są i dlatego uważa, że nie należy ich wprowadzać. Z innych ciekawych rzeczy, zauważyłem, że jest grupka deweloperów, którzy mają jakieś doświadczenie z Pythonem i chcieli by zmienić trochę PHP w jego stronę tj. wszystko jest obiektem (klasy, funkcje etc). Wtedy adnotacje mogły by być zastąpione przez Pythonowe dekoratory. […]

  2. […] Sprawa adnotacji wywołała dość duże poruszenie na liście wbudować parser phpDoc do mechanizmów refleksji. Trzeci wniosek, jest taki, że pewna część deweloperów nie widzi zastosowania dla adnotacji, co więcej nie wie nawet czym one są i dlatego uważa, że nie należy ich wprowadzać. Z innych ciekawych rzeczy, zauważyłem, że jest grupka deweloperów, którzy mają jakieś doświadczenie z Pythonem i chcieli by zmienić trochę php w jego stronę tj. wszystko jest obiektem (klasy, funkcje etc). Wtedy adnotacje mogły by być zastąpione przez Pythonowe dekoratory. […]

Leave a Reply

Informuj mnie o odpowiedziach poprzez e-mail. Możesz również subskrybować wpis bez zostawiania komentarza.