Funkcje jako dane wejściowe

2009-05-29 15:56

Sporo algorytmów jako swoje parametry przyjmuje różnego typu funkcje, które potem są wykorzystywane w trakcie ich działania. Prostym przykładem są tu wszelkiego rodzaju sortowania czy wyszukiwania, umożliwiające często podanie własnego predykatu (funkcji zwracającej wartość logiczną). W bardziej skomplikowanej wersji może chodzić chociażby o algorytm genetyczny lub przeszukujący drzewo gry, który wykorzystuje do działania jakąś funkcję oceniającą (np. osobników w populacji).

Na takie okazje różne języki programowania oferują różne narzędzia, lecz w większości przypadków sprowadzają się one do jednego z poniższych:

  • Wskaźniki lub delegaty. Przekazywanie funkcji przez wskaźnik jest proste i naturalne, ale ma pewne ograniczenia. W C(++) na przykład nie da się zwykłym wskaźnikiem pokazać na metodę konkretnego obiektu; docelowo może to być tylko funkcja globalna lub statyczna. Ten problem nie występuje zwykle w przypadku delegatów, którymi często można pokazać właściwie na wszystko - również na anonimową funkcję zdefiniowaną ad hoc:
    // sortowanie listy w oparciu o własny predykat (C# 2.0 wzwyż)
    objects.Sort (delegate (object a, object b)
        { return a.ToString().Length - b.ToString().Length; });

    Niektóre języki nie mają jednak ani wskaźników, ani delegatów - w nich zwykle stosuje się sposób następny.

  • Interfejsy i metody wirtualne. Ta technika polega na zdefiniowaniu interfejsu (lub klasy abstrakcyjnej) z metodą wirtualną, którą docelowo będzie wywoływać algorytm. Korzystający z niego programista implementuje po prostu wskazany interfejs (lub definiuje klasę pochodną) i nadpisuje wspomnianą metodę własną wersją. Ten sposób jest szczególnie popularny w Javie, gdzie wspomagają go inne mechanizmy językowe - jak choćby niestatyczność klas wewnętrznych lub możliwość ich definiowania inline - np. w wywołaniu funkcji.
    Zauważmy też, że implementacja naszej 'funkcji' w postaci klasy pozwala też na dodatkowe możliwości, jak na przykład posiadanie przez nią stanu (co aczkolwiek w wielu przypadkach, np. predykatów sortowania, jest wysoce niezalecane).
  • Funktory. To rozwiązanie jest specyficzne dla C++ i opiera się na dwóch występujących w tym języku feature'ach: szablonach i przeciążaniu operatorów. Dzięki temu algorytm korzystający z "czegoś co przypomina funkcję" może wyglądać choćby tak:
    template <typename T, typename SearchFunc>
        const T find(const std::vector<T>& v, SearchFunc pred)
        {
            for (int i = 0; i <v.size(); ++i)
                if (pred(v[i])) return v[i]// znaleziono element spełniający predykat
            return T();
        }

    Określenie "coś co przypomina funkcję" jest jak najbardziej na miejscu, gdyż tutaj predykatem może być cokolwiek, co da się jako funkcję potraktować - czyli wywołać operatorem (). Może więc to być zwykły wskaźnik, jakaś ładna zewnętrzna implementacja mechanizmu delegatów (np. FastDelegate) lub jakikolwiek obiekt z przeciążonym operatorem nawiasów (). Właśnie te ostatnie nazywa się zwykle funktorami, a jeśli ktoś nieco bardziej zagłębił się w bibliotekę STL, na pewno nie raz się z nimi spotkał.

Trzeba też powiedzieć, że właściwie istnieje też inny sposób: zamiast samej funkcji przekazywanie jej... nazwy. "I niby jak ją potem wywołać?", można zapytać. Ano to już indywidualna kwestia każdego języka - w tym przypadku zwykle interpretowanego :)

  • Facebook
  • Twitter
  • Wykop
  • Reddit
  • del.icio.us
  • Google Bookmarks
Tagi: , , , ,
Autor: Xion w Programowanie »


Możesz śledzić komentarze do tej notki poprzez kanał RSS 2.0.
Możesz przejść do końca i zostawić komentarz. Śledzenie notek (trackback) jest aktualnie wyłączone.


5 komentarze/y do notki “Funkcje jako dane wejściowe”.
  1. Kos:
    Maj 30th, 2009 o 19:29

    Hehe, w D jest jeszcze jedno fajne rozwiązanie:

    void dotimes(int n, lazy void exp)
    {
    while (n--)
    exp();
    }

    void test()
    { int x;
    dotimes(3, writefln(x++));
    }

  2. Reg:
    Maj 31st, 2009 o 13:13

    W językach skryptowych chodzi nie tyle o przekazanie nazwy jako stringa, ile o to, że często w nich funkcja też jest wartością (specjalnego typu "funkcja"), którą można przekazywać.

  3. revo:
    Maj 31st, 2009 o 20:56

    W C# można użyć jeszcze wyrażeń lambda ;)

  4. Anonim:
    Czerwiec 1st, 2009 o 17:15

    Mam nadzieję, że nie obrazisz się za mały offtop: grasz może dalej w WoWa?

  5. Xion:
    Czerwiec 1st, 2009 o 17:52

    Tak też bywa. Ale w JavaScript mamy też na przykład:

    setTimeout ("alert('5 sekund')", 5000);

    :-)

Dodaj komentarz

Znaki nowej linii dodawane są automatycznie.
Do wstawiania kodu użyj [code][/code], a do wzorów (w LaTeX-u) [tex][/tex].
Dozwolne tagi HTML:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

 



© 2010 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.