Either + Side Effecty

0

Cześć mam taki problem: założmy że robimy sobie walidację i wychodzi nam:
Either<ExchangeRatesRequestError, ExchangeRatesRequest>
Założenie jest takie -> jeśli request jest poprawny to pobieramy dane np. z restowego api i otrzymujemy
Either<ExchangeRatesRequestError, Traversable<HistoricalExchangeRateItem>>
Teraz pytanie brzmi - jak zrobić to "mapowanie". Normalnie od tego jest np. metoda map, ale map nie powinna zdaje się mieć side effectów.
Podsumowując:

  1. mam na wejściu Either<ExchangeRatesRequestError, ExchangeRatesRequest>

  2. dla Either.right to wołam jakąś funkcję getHistoricalExchangeRates która przyjmuje ExchangeRatesRequest i zwraca Traversable<HistoricalExchangeRateItem>, tak więc z
    <ExchangeRatesRequestError, ExchangeRatesRequest> robię Either<ExchangeRatesRequestError, Traversable<HistoricalExchangeRateItem>>

  3. jeśli nie to Either.left jest zwracany

@danek @jarekr000000

0

Jakieś Future? Czyli ten request mapowany na Future podczas wywołania api i potem obsługiwany dalej

0

Nie widze tu sensu używania Future, tu wszystko leci w tym samym wątku. Zresztą Vavrowe Futury nie współdziałają ładnie z Kotlin Courotines :P
EDIT:
w sumie nie wiem czy pattern matching nie byłby dobry do tego (zwłaszcza że korzystam z Kotlina)

0

Nie rozumiem za bardzo gdzie tu jest jakis side effect. Przecież wołasz funkcje która mapuje request na tą twoją listę. W czym problem?

1

W idealnym świecie byś miał IO monad, w praktyce niewiele osób tak piszę bo użycie IO wymaga jednak bardzo dużej zmiany myślenia (wykonie kodu vs zbudowanie drzewa operacji i przekazanie do interpretera - bo na tym polega IO monad). Na twoim miejscu ja bym po prostu dał call'a do serwisu w map. Zastanowił bym się też czy ty tam potrzebujesz Either czy może Try byłby lepszym wyborem. Dla mnie zwracanie Either z walidatora trochę śmierdzi, walidator nie powinien zwracać i modyfikować przekazywanego do niego obiektu. Spodziewał bym się na wyjściu z walidatora czegoś w stylu ValidationResult z ewentualną listą błędów. Lub w wersji mega prostej tylko List[Violation]...

Takie przemyślenia podczas czekania aż mi się makaron ugotuje...

0

A czy wywowałanie callu do API zewnętrznego czy wczytanie danych z bazy danych to nie jest już side effect?
Może czytanie z zewnętrz to jeszcze nie jest taki problem, ale co jeśli miałbym jakiś np. Either<ItemError, Item> i jeśli Item jest poprawny to byśmy zapisywali do bazy danych i uzyskiwali <ItemError, ItemId>?

0

A czy wywowałanie callu do API zewnętrznego czy wczytanie danych z bazy danych to nie jest już side effect?

Z czyjego punktu widzenia? :) Z punktu widzenia twojej aplikacji niekoniecznie, bo generalnie side effect jest związany ze zmianem stanu aplikacji. Dla mnie jeśli ten zewnętrzny call jest odpowiednio opakowany i nigdzie nic nie wycieka, to jest ok.

0

Ludzie wybierają FP nie z powodu monad, ale z powodu łatwej kompozycji danych oraz funkcji. To jest hasło, ktorym powinieneś się kierować gdy masz jakieś wątpliwości.

Monada to nie jest sposób ochrony przed efektami ubocznymi. Monada w dużym stopniu tylko dokumentuje przepływ danych w Twoim kodzie i ukrywa przed Tobą nulle - ona nie eliminuje efektów ubocznych.

Praktyka o której słyszałeś wynika z tego, że w FP dobrze jest pomyśleć o izolowaniu efektów, bo one są na ogół problematyczne, zależne od kontekstu i mniej przewidywalne, dlatego lepiej jest tak programować, aby jak najmniejsza część kodu doprowadzała do efektów.

Jak izolować? Piszesz kod, który zamiast dopuszczać się efektu produkuje dane, które opisują co należy dalej zrobić. Osobna partia kodu niech odbiera te dane i wykonuje na nich efekty. Tutaj dodatkowo promowane jest podejście, które redukuje liczbę zadań IO poprzez grupowanie. Wtedy przeływ sterowania w kodzie jest mniej poszatkowany.

4
pan_krewetek napisał(a):

Monada w dużym stopniu tylko dokumentuje przepływ danych w Twoim kodzie i ukrywa przed Tobą nulle - ona nie eliminuje efektów ubocznych.

Monada nie ukrywa nulli, one ogólnie nie mają nic wspólnego z nullami. Fakt, że jedna z nich (Option) opisuje efekt "nie posiadania wartości" nie powoduje, że wszystkie inne to robią, a tym bardziej że mają cokolwiek wspólnego z nullem.

Shalom napisał(a):

Z czyjego punktu widzenia? :) Z punktu widzenia twojej aplikacji niekoniecznie, bo generalnie side effect jest związany ze zmianem stanu aplikacji. Dla mnie jeśli ten zewnętrzny call jest odpowiednio opakowany i nigdzie nic nie wycieka, to jest ok.

Side effect może być związany ze zmianą stanu aplikacji ale nie tylko. Akurat w przypadku API czy bazy danych można mówić o side effectach bo łamane jest referential transparency. Nie mniej, z całą resztą się zgadzam - korzystając z Javy ważniejsze jest odpowiednio zamodelować to obiektowo niż próbować zrobić wydmuszkę IO (która i tak nie będzie miała sensu).

scibi92 napisał(a):

Może czytanie z zewnętrz to jeszcze nie jest taki problem, ale co jeśli miałbym jakiś np. Either<ItemError, Item> i jeśli Item jest poprawny to byśmy zapisywali do bazy danych i uzyskiwali <ItemError, ItemId>?

Jak dla mnie rozwiązanie jest dość proste - korzystając z języków, które nie wspierają w 100% FP trzeba iść na kompromisy korzystając z FP. Nie ma sensu w Javie, czy jakimkolwiek innym stricte obiektowym języku opisywać side effectów za pomocą IO.
W przykładzie który podałeś, funkcja Either<ItemError, Item> -> Either<ItemError, ItemId> brzmi po prostu ok. Gdybyś chciał to zamodelować "funkcyjnie" to pewnie uzyskałbyś funkcję, która zwracałaby IO zamiast Either (ale tak jak wspominałem, w Javie to nie ma większego sensu).

0

Jak dla mnie rozwiązanie jest dość proste - korzystając z języków, które nie wspierają w 100% FP trzeba iść na kompromisy korzystając z FP. Nie ma sensu w Javie, czy jakimkolwiek innym stricte obiektowym języku opisywać side effectów za pomocą IO.

Implementację robię w Kotlinie, i w Kotlinie jest arrow-kt ale jakoś ciężko mi się z tym pracowało. Może jeszcze biblioteka nie jest w pełni gotowa.
Poza tym jeszcze jestem dosyć początkujący w temacie FP, i mój poziom to niemutowalność, proste monady (Option/Either), algebraiczne struktury danych/pattern matchin (Sealed Class i when w Kotlinie). I tez kładę nacisk na silne typowanie (takie prawdziwie, nie tylko String, String, String i jakiś BigDecimal ;) )

2

Zgadzam się z przedmówcami.
Oczywiście robienie call http w map to jest side effect.
Ale raczej w niczym Ci to nie przeszkadza.

Jeśli byś używał jakiś reactive clientów (reactor http client) to mógłbyś całość faktycznie zrobić bez side effektów, i byłoby to dość naturalne. Takie rozwiązanie ma to sens jeśli reactive streams pasują do twojej aplikacji. (Mono jest trochę odpowiednikiem monady IO).
Jakkolwiek, jeśli tam jest pojedyncze wołanie http - to raczej nie ma potrzeby reactive streams przywoływać.

1 użytkowników online, w tym zalogowanych: 0, gości: 1