Próbka paradygmatu funkcjonalnego
12 grudnia 2009, dyskusja: 21 komentarzy
Najbardziej rozpowszechnionym paradygmatem programowania jest paradygmat imperatywny. W jego ujęciu program jest ciągiem instrukcji modyfikujących stan. Istnieją jednak inne podejścia, a jednym z nich jest paradygmat funkcjonalny, który traktuje program jak obliczenie wartości pewnej funkcji. Wybór paradygmatu ma ogromny wpływ zarówno na języki programowania jak i same programy, co postaramy się zademonstrować na prostym przykładzie.
Naszym zadaniem będzie rozwiązanie klasycznego problemu sumy podzbioru. Jest on zadany następująco: mając dany niepusty i skończony zbiór liczb należy znaleźć wszystkie jego podzbiory, których suma elementów jest zerowa (dla uproszczenia do rozwiązania dopuścimy zbiór pusty). Rozwiążemy go przy pomocy PHP i Haskella starając się, aby kod w PHP był jak najbliższy duchowi funkcjonalności.
Zaimplementujemy rozwiązanie, które podpowiada nam intuicja. Najpierw wybierzemy wszystkie podzbiory rozważanego zbioru, a następnie zostawimy tylko te, których suma jest zerowa. Opis ten jest truistyczny i bardzo ogólnikowy, jednak jest w zupełności wystarczający.
Zaczniemy od rozwiązania w PHP. Zbiór będziemy reprezentować przy pomocy tablicy. Do dyspozycji mamy funkcję sumującą elementy (array_sum) oraz filtrującą (array_filter), jednak nie ma funkcji zwracającej wszystkie podtablice. Musimy napisać ją samodzielnie.
function subarrays($array){if(!$array){return array(array());}$last = array_pop($array);$subarrays = subarrays($array);foreach($subarrays as $subarray){$newsubarray = $subarray;$newsubarray[] = $last;$subarrays[] = $newsubarray;}return $subarrays;}function zero_sum($array){return !array_sum($array);}$input = array(-2, -1, 0, 1, 2);$output = array_filter(subarrays($input), 'zero_sum');print_r($output);
Rozwiązanie uzyskujemy w jednej linii, wykonując na tablicy wejściowej $input kilka elementarnych operacji. Dodatkowo od wersji 5.3 można użyć anonimowej funkcji jako filtru (pomijamy prowizoryczną funkcję create_function).
Spójrzmy jak sprawa wygląda w Haskellu. Tutaj do dyspozycji mamy cały arsenał funkcji i nie musimy niczego pisać samodzielnie.
import Data.Listmain = doputStrLn $ show outputwhereinput = [-2..2]output = filter ((== 0) . sum) $ subsequences input
input jest wejściową listą. output jest konstruowany w banalny sposób: wybieramy wszystkie podlisty (funkcja subsequences), a następnie wybieramy te z zerową sumą (funkcja filter i filtr (== 0) . sum). Piękne!
Posługując się tym trywialnym przykładem pokazaliśmy jak stosowany paradygmat wpływa na składnię języka, sposób myślenia o problemach i wygląd programów. Funkcjonalność daje nam zwięzłość i łatwość zrozumienia, a także niebywałą estetykę. Dlatego warto wyjść poza nurt imperatywny, chociażby na próbę, aby zyskać nowe spojrzenie na programowanie. Niech do popularnych języków takich jak PHP, Python, Ruby czy Java dołączą Erlang, Haskell i Scala!