Zarządzanie zależnościami jest dużym problemem w OOP z dwóch następujących powodów:
- Ścisłe połączenie danych i kodu.
- Wszechobecne stosowanie efektów ubocznych.
Większość programistów OO uważa ścisłe połączenie danych i kodu za całkowicie korzystne, ale wiąże się to z pewnymi kosztami. Zarządzanie przepływem danych przez warstwy jest nieuniknioną częścią programowania w dowolnym paradygmacie. Połączenie danych i kodu dodaje dodatkowy problem, że jeśli chcesz użyć funkcji w pewnym momencie, musisz znaleźć sposób, aby dostać się do tego obiektu.
Stosowanie efektów ubocznych stwarza podobne trudności. Jeśli używasz efektu ubocznego do niektórych funkcji, ale chcesz mieć możliwość zamiany jego implementacji, właściwie nie masz innego wyjścia, jak wstrzyknąć tę zależność.
Rozważmy jako przykład program spamujący, który usuwa strony internetowe w poszukiwaniu adresów e-mail, a następnie wysyła je pocztą elektroniczną. Jeśli masz sposób myślenia DI, teraz myślisz o usługach, które będziesz enkapsulować za interfejsami i które usługi zostaną tam wprowadzone. Zostawię ten projekt jako ćwiczenie dla czytelnika. Jeśli masz nastawienie FP, teraz myślisz o wejściach i wyjściach dla najniższej warstwy funkcji, takich jak:
- Wpisz adres strony internetowej, wyślij tekst tej strony.
- Wpisz tekst strony, wypisz listę linków z tej strony.
- Wpisz tekst strony, wyślij listę adresów e-mail na tej stronie.
- Wprowadź listę adresów e-mail, wyślij listę adresów e-mail z usuniętymi duplikatami.
- Wpisz adres e-mail, wyślij spam dla tego adresu.
- Wpisz spam, wyślij polecenia SMTP, aby wysłać ten email.
Gdy myślisz o wejściach i wyjściach, nie ma zależności funkcji, tylko zależności danych. Dzięki temu są tak łatwe do przetestowania w jednostce. Następna warstwa organizuje przekazywanie danych wyjściowych jednej funkcji do danych wejściowych kolejnej i może w razie potrzeby łatwo wymieniać różne implementacje.
W bardzo realnym sensie programowanie funkcjonalne w naturalny sposób prowadzi do odwrócenia zależności funkcji i dlatego zwykle nie trzeba podejmować żadnych specjalnych działań po tym fakcie. Gdy to zrobisz, narzędzia takie jak funkcje wyższego rzędu, zamknięcia i częściowe zastosowanie ułatwiają to przy mniejszej liczbie płyt kotłowych.
Zauważ, że same zależności nie są problematyczne. Zależności wskazują niewłaściwie. Następna warstwa może mieć funkcję:
processText = spamToSMTP . emailAddressToSpam . removeEmailDups . textToEmailAddresses
Zupełnie dobrze jest, aby ta warstwa miała tak zakodowane zależności, ponieważ jej jedynym celem jest sklejenie funkcji dolnej warstwy razem. Zamiana implementacji jest tak prosta, jak utworzenie innej kompozycji:
processTextFancy = spamToSMTP . emailAddressToFancySpam . removeEmailDups . textToEmailAddresses
Ta łatwa rekompozycja jest możliwa dzięki brakowi efektów ubocznych. Funkcje niższej warstwy są całkowicie od siebie niezależne. Następna warstwa może wybrać, która processText
jest faktycznie używana na podstawie konfiguracji użytkownika:
actuallyUsedProcessText = if (config == "Fancy") then processTextFancy else processText
Znowu nie jest to problem, ponieważ wszystkie zależności wskazują w jedną stronę. Nie musimy odwracać niektórych zależności, aby wszystkie wskazywały w ten sam sposób, ponieważ czyste funkcje już nas do tego zmusiły.
Zauważ, że możesz to uczynić o wiele bardziej sprzężonym, przechodząc config
do najniższej warstwy zamiast sprawdzać ją u góry. FP nie przeszkadza ci to robić, ale sprawia, że staje się o wiele bardziej irytujący, jeśli spróbujesz.