Na wiki haskell znajduje się następujący przykład warunkowego użycia monady IO (patrz tutaj) .
when :: Bool -> IO () -> IO ()
when condition action world =
if condition
then action world
else ((), world)
Zauważ, że w tym przykładzie IO a
przyjęto definicję, RealWorld -> (a, RealWorld)
aby wszystko było bardziej zrozumiałe.
Ten fragment kodu warunkowo wykonuje akcję w monadzie we / wy. Zakładając, że tak condition
jest False
, akcja action
nigdy nie powinna zostać wykonana. Przy użyciu leniwej semantyki tak właśnie byłoby. Należy jednak zauważyć , że Haskell z technicznego punktu widzenia nie jest ścisły. Oznacza to, że kompilator może na przykład zapobiegawczo uruchamiać action world
się w innym wątku, a później wyrzucić to obliczenie, gdy odkryje, że go nie potrzebuje. Jednak do tego momentu działania niepożądane już się wydarzyły.
Teraz można zaimplementować monadę we / wy w taki sposób, że skutki uboczne są propagowane dopiero po zakończeniu całego programu i wiemy dokładnie, jakie skutki uboczne należy wykonać. Nie dzieje się tak jednak, ponieważ w Haskell można pisać nieskończone programy, które wyraźnie mają pośrednie skutki uboczne.
Czy to oznacza, że monada IO jest technicznie niepoprawna, czy może jest coś innego, co mogłoby temu zapobiec?
when
jest typowalna, ale nie ma deklarowanego typu i nie wiem, co czyni ten kod interesującym.IO a
jest zdefiniowane jakoRealWorld -> (a, RealWorld)
, aby uczynić elementy wewnętrzne IO bardziej czytelnymi.Odpowiedzi:
Jest to sugerowana „interpretacja”
IO
monady. Jeśli chcesz poważnie potraktować tę „interpretację”, musisz poważnie potraktować „RealWorld”. Nie ma znaczenia, czyaction world
zostanie spekulacyjnie oceniony, czy nie,action
nie ma żadnych skutków ubocznych, jego ewentualne skutki są obsługiwane przez zwrócenie nowego stanu wszechświata, w którym wystąpiły te efekty, np. Wysłano pakiet sieciowy. Jednak wynikiem tej funkcji jest((),world)
nowy stan wszechświataworld
. Nie używamy nowego wszechświata, który mogliśmy spekulować na boku. Stan wszechświata jestworld
.Prawdopodobnie trudno ci brać to na poważnie. Jest na wiele sposobów, w najlepszym wypadku, powierzchownie paradoksalny i bezsensowny. Z tej perspektywy współbieżność jest albo nieoczywista, albo szalona.
„Czekaj, czekaj” - mówisz. „
RealWorld
to tylko„ token ”. To nie jest stan całego wszechświata.” Okej, to ta „interpretacja” nic nie wyjaśnia. Niemniej jednak, jako szczegół implementacji , tak właśnie modele GHCIO
. 1 Oznacza to jednak, że mamy magiczne „funkcje”, które faktycznie wywołują skutki uboczne i ten model nie zawiera wskazówek co do ich znaczenia. A ponieważ te funkcje faktycznie wywołują skutki uboczne, twoja obawa jest całkowicie na miejscu. GHC nie trzeba wychodzić z jego sposób, aby upewnić sięRealWorld
, a te specjalne funkcje nie są zoptymalizowane w taki sposób, że zmiany zamierzonego zachowania programu.Osobiście (jak to zapewne jest obecnie widoczne), uważam, że ten „przemijający świat” model
IO
jest po prostu bezużyteczny i mylący jako narzędzie pedagogiczne. (Nie wiem, czy jest to przydatne do implementacji. W przypadku GHC uważam, że jest to raczej historyczny artefakt).Jednym z alternatywnych podejść jest wyświetlanie
IO
żądań opisujących za pomocą procedur obsługi odpowiedzi. Można to zrobić na kilka sposobów. Prawdopodobnie najbardziej dostępna jest darmowa konstrukcja monady, w szczególności możemy użyć:Istnieje wiele sposobów, aby uczynić to bardziej wyrafinowanym i mieć nieco lepsze właściwości, ale jest to już poprawa. Nie wymaga głębokich filozoficznych założeń dotyczących natury rzeczywistości. Stwierdza tylko, że
IO
jest to albo trywialny programReturn
, który nic nie robi, ale zwraca wartość, albo jest to żądanie do systemu operacyjnego z funkcją obsługi odpowiedzi.OSRequest
może być coś takiego:Podobnie
OSResponse
może być coś takiego:(Jednym z ulepszeń, które mogą być wykonane jest, aby rzeczy bardziej wpisz bezpieczny tak, że wiesz, że nie będzie się
OpenSucceeded
zPutStr
wniosku). To modeleIO
jak opisujący wnioski, które uzyskać interpretowane przez jakiegoś układu (za „prawdziwe”IO
monada jest sam środowisko uruchomieniowe Haskell), a następnie być może ten system wywoła moduł obsługi, którego udzieliliśmy odpowiedzi. To oczywiście nie daje żadnych wskazówek na temat tego, jakPutStr "hello world"
należy obsługiwać takie żądanie , ale też nie udaje. Wyjaśnia, że jest to delegowane do innego systemu. Ten model jest również dość dokładny. Wszystkie programy użytkownika we współczesnych systemach operacyjnych muszą wysyłać żądania do systemu operacyjnego, aby cokolwiek zrobić.Ten model zapewnia właściwe intuicje. Na przykład wielu początkujących uważa rzeczy takie jak
<-
operator za „rozpakowywanie”IO
lub mają (niestety wzmocnione) widoki, któreIO String
, powiedzmy, są „pojemnikami”, które „zawierają”String
(a następnie<-
wyciągają je). Ten widok żądanie-odpowiedź sprawia, że ta perspektywa jest wyraźnie błędna. Wewnątrz nie ma uchwytu plikuOpenFile "foo" (\r -> ...)
. Popularną analogią do podkreślenia tego jest to, że w przepisie na ciasto nie ma ciasta (a może w tym przypadku lepiej byłoby „wystawić fakturę”).Ten model działa również z współbieżnością. Możemy łatwo mieć konstruktor dla
OSRequest
like,Fork :: (OSResponse -> IO ()) -> OSRequest
a następnie środowisko wykonawcze może przeplatać żądania wygenerowane przez ten dodatkowy moduł obsługi z normalnym modułem obsługi, jak mu się podoba. Przy odrobinie sprytu możesz użyć tej (lub pokrewnych technik) do modelowania rzeczy takich jak współbieżność bardziej bezpośrednio, niż po prostu mówiąc „wysyłamy zapytanie do systemu operacyjnego i rzeczy się dzieją”. Tak działaIOSpec
biblioteka .1 Uściski zastosowały kontynuację,
IO
która jest mniej więcej podobna do tego, co opisuję, aczkolwiek z nieprzezroczystymi funkcjami zamiast jawnego typu danych. HBC wykorzystał również implementację opartą na kontynuacji warstwową na starym IO opartym na strumieniu żądanie-odpowiedź. NHC (a tym samym YHC) użył thunksów, tzn. Z grubsza,IO a = () -> a
jak()
to się nazywałoWorld
, ale nie robi stanu. JHC i UHC zastosowały w zasadzie to samo podejście co GHC.źródło
OpenFile "foo" (\r -> ...)
tak powinno byćRequest (OpenFile "foo") (\r -> ...)
?Request
. Aby odpowiedzieć na pierwsze pytanie,IO
jest to wyraźnie niewrażliwe na kolejność oceny (dna modulo), ponieważ jest to wartość obojętna. Wszystkie skutki uboczne (jeśli występują) byłyby spowodowane przez rzecz, która interpretuje tę wartość. W tymwhen
przykładzie nie miałoby znaczenia, gdybyaction
zostało ocenione, ponieważ byłaby to po prostu wartość,Request (PutStr "foo") (...)
której i tak nie damy rzeczowi interpretującemu te żądania. To jest jak kod źródłowy; nie ma znaczenia, czy zmniejszysz go chętnie czy leniwie, nic się nie dzieje, dopóki nie zostanie przekazane tłumaczowi.Request
, aby zacząć widzieć efekty uboczne. Oceniając kontynuację, można utworzyć kolejne skutki uboczne. Sprytny!