Funkcyjne programowanie (dalej FP) to paradygmat, który pomimo coraz większej popularności i coraz lepszego wsparcia w językach programowania, wciąż wydaje się nieoczywisty w codziennym programowaniu.
Na tę kwestię warto spojrzeć szerzej, ponieważ stosowanie dowolnego paradygmatu wiąże się z przyjęciem pewnych odgórnych zasad, które z jednej strony torują myślenie o problemach, a z drugiej strony ograniczają pole manewru. Innymi słowy, oddajemy część kontroli/władzy w zamian za większą przewidywalność.
W przypadku FP dąży się do uzyskania kodu, który nie zarządza stanem ani również nie powoduje efektów ubocznych w postaci dodatkowych działań takich jak np. zapisanie pliku czy wysłanie wiadomości. To co zaskakuje w tym podejściu, to fakt, że z punktu widzenia użytkownika, taki kod tak naprawdę nie robi nic użytecznego. W takim razie, jaki jest powód dla którego programiści wybierają to podejście?
Motywacją do stosowania FP są korzyści wynikające z oddzielenia obliczeń od pozostałych akcji jakie mają miejsce w aplikacji.
Jeśli funkcja tylko oblicza wynik, bez wywoływania dodatkowych efektów, wtedy jest łatwiejsza w czytaniu oraz testowaniu. Dzieje się tak, ponieważ funkcja zawsze obliczy ten sam wynik dla tych samych danych wejściowych .
Spójrzmy na poniższy kod, który rysuje siatkę na ekranie.
Możemy wtedy:
- bezpośrednio wywołać funkcje odpowiedzialne za rysowanie,
- albo zgodnie z FP utworzyć funkcję, która będzie wolna od efektów.
Przedstawiony kod oblicza w pierwszej kolejności linie:
def grid_lines(pos, width, height, rows, cols):
x, y = pos
cell_width = width // cols
cell_height = height // rows
for row_y in range(rows + 1):
line_start_pos = (x, y + cell_height * row_y)
line_end_pos = (x + width, y + cell_height * row_y)
yield line_start_pos, line_end_pos
for col_x in range(cols + 1):
line_start_pos = (x + cell_width * col_x, y)
line_end_pos = (x + cell_width * col_x, y + height)
yield line_start_pos, line_end_posby następnie użyć tę funkcję w następujący sposób:
for start_pos, end_pos in grid_lines(
pos=(x, y),
width=self.CELL_SIZE * self.CELL_COLS,
height=self.CELL_SIZE * self.CELL_ROWS,
rows=self.CELL_ROWS,
cols=self.CELL_COLS
):
pygame.draw.line(self._screen, color, start_pos, end_pos)
Docelowe użycie funkcji wygląda teraz trywialnie, ponieważ grid_lines odpowiada za przeprowadzenie obliczeń.
Niestety stosowanie FP nie zawsze ma sens.
Gdyby funkcja grid_lines w języku Python, przy każdym wywołaniu zamiast generatora, zwracała listę krotek, wówczas takie podejście byłoby niepraktyczne z punktu widzenia wydajności.