Jak zbudować system, który ma wszystkie następujące elementy :
- Używanie czystych funkcji z niezmiennymi obiektami.
- Przekaż tylko dane funkcji, których potrzebuje, nie więcej (tj. Nie ma dużego obiektu stanu aplikacji)
- Unikaj posiadania zbyt wielu argumentów do funkcji.
- Unikaj konstruowania nowych obiektów tylko w celu pakowania i rozpakowywania parametrów do funkcji, po prostu unikaj przekazywania zbyt wielu parametrów do funkcji. Jeśli mam spakować wiele elementów do funkcji jako pojedynczy obiekt, chcę, aby ten obiekt był właścicielem tych danych, a nie czymś tymczasowo skonstruowanym
Wydaje mi się, że monada państwowa łamie zasadę nr 2, chociaż nie jest to oczywiste, ponieważ jest wpleciona w monadę.
Mam wrażenie, że muszę jakoś używać Soczewek, ale bardzo mało jest o tym napisane dla języków niefunkcjonalnych.
tło
W ramach ćwiczenia przekształcam jedną z istniejących aplikacji ze stylu obiektowego na styl funkcjonalny. Pierwszą rzeczą, którą próbuję zrobić, jest jak najwięcej wewnętrznego rdzenia aplikacji.
Jedną rzeczą, jaką słyszałem, jest to, jak zarządzać „stanem” w czysto funkcjonalnym języku, a moim zdaniem to, co robią monady państwowe, polega na tym, że logicznie nazywasz funkcję czystą, „przechodząc w stan world as is ”, a kiedy funkcja powróci, zwróci ci stan świata, który się zmienił.
Aby to zilustrować, sposób, w jaki można zrobić „witaj świat” w czysto funkcjonalny sposób, jest taki, że przekazujesz programowi ten stan ekranu i odbierasz stan ekranu z nadrukowanym „witaj światem”. Więc technicznie, wywołujesz funkcję czystą i nie ma żadnych skutków ubocznych.
Na tej podstawie przejrzałem moją aplikację i: 1. Najpierw umieść cały stan mojej aplikacji w jednym globalnym obiekcie (GameState) 2. Po drugie, uczyniłem GameState niezmiennym. Nie możesz tego zmienić. Jeśli potrzebujesz zmiany, musisz zbudować nową. Zrobiłem to, dodając konstruktor kopii, który opcjonalnie przyjmuje jedno lub więcej pól, które uległy zmianie. 3. Do każdej aplikacji przekazuję GameState jako parametr. W ramach funkcji, po zrobieniu tego, co zrobi, tworzy nowe GameState i zwraca je.
Jak mam czysty funkcjonalny rdzeń i zewnętrzną pętlę, która przesyła GameState do głównej pętli przepływu pracy aplikacji.
Moje pytanie:
Mój problem polega na tym, że GameState ma około 15 różnych niezmiennych obiektów. Wiele funkcji na najniższym poziomie działa tylko na kilku z tych obiektów, takich jak utrzymywanie wyniku. Powiedzmy, że mam funkcję, która oblicza wynik. Dzisiaj GameState jest przekazywane do tej funkcji, która modyfikuje wynik, tworząc nowy GameState z nowym wynikiem.
Coś w tym wydaje się nie tak. Funkcja nie potrzebuje całości GameState. Po prostu potrzebuje obiektu Score. Zaktualizowałem go, aby przekazać Wynik i zwrócić tylko Wynik.
Wydawało się to mieć sens, więc poszedłem dalej z innymi funkcjami. Niektóre funkcje wymagałyby ode mnie wprowadzenia 2, 3 lub 4 parametrów z GameState, ale ponieważ użyłem wzorca przez cały zewnętrzny rdzeń aplikacji, przekazuję coraz więcej stanu aplikacji. Na przykład w górnej części pętli przepływu pracy wywołałbym metodę, która wywołałaby metodę, która wywołałaby metodę itp., Aż do miejsca, w którym obliczany jest wynik. Oznacza to, że bieżący wynik jest przekazywany przez wszystkie te warstwy tylko dlatego, że funkcja na samym dole obliczy wynik.
Więc teraz mam funkcje z czasami dziesiątkami parametrów. Mógłbym umieścić te parametry w obiekcie, aby zmniejszyć liczbę parametrów, ale wtedy chciałbym, aby ta klasa była główną lokalizacją stanu aplikacji stanu, a nie obiektem, który jest po prostu budowany w momencie wywołania, aby uniknąć przekazania w wielu parametrach, a następnie rozpakuj je.
Zastanawiam się teraz, czy mam problem z tym, że moje funkcje są zbyt głęboko zagnieżdżone. Jest to wynik chęci posiadania małych funkcji, więc refaktoryzuję, gdy funkcja staje się duża, i dzielę ją na wiele mniejszych funkcji. Ale zrobienie tego powoduje głębszą hierarchię i wszystko, co przekazane do funkcji wewnętrznych, musi zostać przekazane do funkcji zewnętrznej, nawet jeśli funkcja zewnętrzna nie działa bezpośrednio na tych obiektach.
Wydawało się, że po prostu przekazanie GameState po drodze pozwoliło uniknąć tego problemu. Ale wracam do pierwotnego problemu polegającego na przekazywaniu większej ilości informacji do funkcji, niż jej potrzebuje.