Przepraszam za jeszcze jedno pytanie o efekty uboczne FP +, ale nie mogłem znaleźć już istniejącego, który całkiem mi na to odpowiedział.
Moje (ograniczone) rozumienie programowania funkcjonalnego jest takie, że efekty stanu / uboczne powinny być zminimalizowane i oddzielone od logiki bezstanowej.
Zbieram również podejście Haskella do tego, monady IO, osiąga to poprzez zawijanie stanowych akcji w kontenerze, do późniejszego wykonania, rozważanego poza zakresem samego programu.
Próbuję zrozumieć ten wzorzec, ale tak naprawdę ustalić, czy użyć go w projekcie Python, więc jeśli to możliwe, unikaj specyfiki Haskell.
Nadchodzi surowy przykład.
Jeśli mój program konwertuje plik XML na plik JSON:
def main():
xml_data = read_file('input.xml') # impure
json_data = convert(xml_data) # pure
write_file('output.json', json_data) # impure
Czy podejście Monady IO nie jest skuteczne, aby to zrobić:
steps = list(
read_file,
convert,
write_file,
)
następnie zwolnij się z odpowiedzialności, nie dzwoniąc tych kroków, ale pozwalając tłumaczowi to zrobić?
Lub inaczej: pisanie:
def main(): # pure
def inner(): # impure
xml_data = read_file('input.xml')
json_data = convert(xml_data)
write_file('output.json', json_data)
return inner
następnie oczekuję, że ktoś zadzwoni inner()
i powie, że twoja praca jest skończona, ponieważmain()
jest czysta.
Zasadniczo cały program zostanie zawarty w monadzie IO.
Kiedy kod jest faktycznie wykonywany , wszystko po odczytaniu pliku zależy od stanu tego pliku, więc nadal będą cierpieć z powodu tych samych błędów związanych ze stanem, co implementacja imperatywna, więc czy rzeczywiście zyskałeś coś, jako programista, który to utrzyma?
Całkowicie doceniam korzyści płynące z ograniczenia i izolowania zachowań stanowych, dlatego właśnie stworzyłem taką wersję imperatywną: zbieraj dane wejściowe, rób czyste rzeczy, wypluwaj dane wyjściowe. Mamy nadzieję, że convert()
może być całkowicie czysty i czerpać korzyści z możliwości buforowania, bezpieczeństwa wątków itp.
Doceniam również to, że typy monadyczne mogą być użyteczne, szczególnie w potokach działających na porównywalnych typach, ale nie rozumiem, dlaczego IO powinno używać monad, chyba że jest już w takim potoku.
Czy jest jakaś dodatkowa korzyść z radzenia sobie z efektami ubocznymi, które przynosi wzorzec monady IO, którego mi brakuje?
main
programu Haskell jest IO ()
- działanie IO. To wcale nie jest funkcja; to jest wartość . Cały twój program jest czystą wartością zawierającą instrukcje, które informują środowisko wykonawcze języka, co powinien zrobić. Wszystkie nieczyste rzeczy (faktycznie wykonujące operacje IO) są poza zakresem twojego programu.
read_file
) i użyjesz go jako argumentu do następnego ( write_file
). Gdybyś miał tylko sekwencję niezależnych działań, nie potrzebowałbyś Monady.