Pure methods

Nie wiem, czy istnieje jakieś bardziej oficjalne tłumaczenie pure methods,więc aby uniknąć śmiesznych nazw przez resztę postu będę posługiwał się nazwą angielską. Nie chodzi mi o metody czysto wirtualne, które są powszechnie znane.  W .NET istnieje atrybut PureAttribute, którym możemy oznaczać metody pure. Warto zacząć jednak od czystej definicji, czym jest metoda pure?

W skrócie metoda pure nie zmienia stanu obiektu a jedynie operuje na parametrach wejściowych. Aby spełniać wymogi metod pure należy:

  1. Parametry wejściowe nie mogą być zmieniane. Muszą stanowić one wyłącznie dane na których się operuje w celu wygenerowania wyjścia.
  2. Jeśli parametry wejściowe to typy referencyjne, nie można zmieniać ich stanu (bezpośrednio przez zmianę właściwości lub pośrednio przez wywołanie metod).
  3. Nie można zmieniać stanów innych klas np. po przez ustawienie pola statycznego.

Co daje takie podejście? Przede wszystkim metody zaimplementowane w ten sposób są bezpieczniejsze i nie przynoszą efektów ubocznych. Z punktu widzenia wielowątkowości łatwiej je wdrożyć ponieważ każda operuje tylko na lokalnym stosie a nie na współdzielonym stanie obiektu. Z łatwością można je wywoływać w jakiejkolwiek kolejności i nie przyniesie to nieoczekiwanego rezultatu. Testy jednostkowe ograniczają się do do przetestowania różnych kombinacji parametrów wejściowych oraz wyjścia.

Wszelkie zapytania np. LINQ są doskonałym przykładem na użycie metod pure. Implementując własne rozszerzenia (extension methods) niedopuszczalne jest aby metoda modyfikowała parametry wejściowe lub stan obiektu. Modyfikacja stanu jest powodem wielu błędów. Autorzy zbyt często zakładają, że użytkownicy wywołają metody w odpowiedniej kolejności. W .NET istnieje atrybut Pure do identyfikowania takich metod:

class Manager
{
    [Pure]
    public void CalculateSomething(int x,int y)
    {        
    }
}

Należy zaznaczyć, że kompilator nie sprawdza metody pod kątem czystości i zmiana stanu wciąż jest możliwa:

class Manager
{
    private int _x;
    [Pure]
    public void CalculateSomething(int x,int y)
    {
        _x = x;
    }
}

Atrybut ma znaczenie wyłącznie informacyjne.  To na programiście spada odpowiedzialność zagwarantowania poprawności metody. Atrybut Pure jest częścią tzw. kontraktów (code contracts) ale o tym mam nadzieję, uda mi się przygotować osobne wpisy. Kiedyś napisałem już jeden wprowadzający post do kontraktów tutaj.

5 thoughts on “Pure methods”

  1. “Implementując własne rozszerzenia (extension methods) niedopuszczalne jest aby metoda modyfikowała parametry wejściowe lub stan obiektu”

    Tego nie zrozumiałem szczerze mówiąc. Chodzi o jakiś konkretny kontekst, czy piszesz ogólnie? Jeśli ogólnie – to skąd takie stwierdzenie? Extension methods mają wiele zastosowań, część z nich to wg mnie właśnie zamknięcie “ustawiania” danego stanu w obiekcie, zamknięcie pewnej sekwencji w jednej metodzie.

  2. Ciekawy atrybut. Zawsze irytowało mnie nakłanianie przez CodeRush do zamiany takich metod na statyczne. Takie wyjście jest czytelniejsze.

  3. @Maciej:
    Chodzi mi o query pattern czyli np. klauzule Where. Cięzko sobie wyobrazic aby modyfikowała ona stan parametrów wejściowych albo polegała na jakimś zewnętrznym stanie.

  4. Dosyć często, słysząc lub czytając o tego typu metodach, spotykałem się z najprostszym tłumaczeniem ‘metody bez skutków ubocznych’. Przydługie co prawda, ale ja nie o tym…
    Szkoda, że kompilator w żaden sposób nie wspomaga programisty przed zmianą stanu w takiej metodzie, jak robiły to np. kompilatory C++ dla metod const.
    Wspominałeś o łatwości wdrożenia w środowisku wielowątkowym, ale również w przypadku środowiska jednowątkowego zastosowanie tego typu wynalazków może dać pewne zalety, choćby wydajnościowe – można np. uniknąć kopiowania dużej struktury danych i zamiast tego przekazać referencję do niej, jeżeli wiadomo, że podprogram jej nie zmodyfikuje. Ale najważniejsza moim zdaniem jest inna zaleta – determinizm. Tego typu metoda to zwykła funkcja matematyczna, dla takich samych argumentów produkuje identyczne wyniki. To daje możliwości łatwiejszego wnioskowania na temat zachowania programu, ułatwione zrównoleglanie (o czym wspomniałeś) czy choćby, w przypadku złożonych i czasochłonnych funkcji, prostotę implementacji cache’owania.
    Na koniec jedno pytanie: czy atrybut ten jest wykorzystywany w jakikolwiek sposób przez kompilator lub środowisko uruchomieniowe do optymalizacji kodu (pośredniego lub natywnego)?

Leave a Reply

Your email address will not be published.