Pracuję nad napisaniem sobie schematu w 48 godzin (mam do około 85 godzin) i dotarłem do części dotyczącej dodawania zmiennych i przypisań . W tym rozdziale jest duży skok koncepcyjny i żałuję, że nie wykonano go w dwóch krokach z dobrą refaktoryzacją pomiędzy, a nie przeskakiwaniem od razu do ostatecznego rozwiązania. Tak czy inaczej…
Stałam utracone z wielu różnych klas, które wydają się służyć temu samemu celowi: State
, ST
, IORef
, i MVar
. Pierwsze trzy są wymienione w tekście, a ostatnia wydaje się być preferowaną odpowiedzią na wiele pytań StackOverflow dotyczących pierwszych trzech. Wszystkie wydają się nosić stan między kolejnymi inwokacjami.
Co to jest i czym się od siebie różnią?
W szczególności te zdania nie mają sensu:
Zamiast tego używamy funkcji zwanej wątkami stanu , pozwalając Haskellowi zarządzać za nas stanem zagregowanym. To pozwala nam traktować zmienne zmienne tak jak w każdym innym języku programowania, używając funkcji do pobierania lub ustawiania zmiennych.
i
Moduł IORef pozwala na używanie zmiennych stanowych w monadzie IO .
Wszystko to sprawia, że linia jest type ENV = IORef [(String, IORef LispVal)]
zagmatwana - dlaczego druga IORef
? Co się zepsuje, jeśli type ENV = State [(String, LispVal)]
zamiast tego napiszę ?
MVar
powinno to być preferowaneSTRef
?STRef
gwarantuje, że tylko jeden wątek może go mutować (i że nie mogą wystąpić inne typy IO) - na pewno lepiej, jeśli nie potrzebuję jednoczesnego dostępu do stanu mutowalnego?Ok, zacznę od
IORef
.IORef
zapewnia wartość, która jest zmienna w monadzie IO. To tylko odniesienie do niektórych danych i jak każde odniesienie, istnieją funkcje, które pozwalają zmienić dane, do których się odnoszą. W Haskell wszystkie te funkcje działają wIO
. Możesz myśleć o tym jak o bazie danych, pliku lub innym zewnętrznym magazynie danych - możesz pobrać i ustawić w nim dane, ale wymaga to przejścia przez IO. Powodem, dla którego IO jest w ogóle konieczne, jest to, że Haskell jest czysty ; kompilator potrzebuje sposobu, aby wiedzieć, do których danych odwołuje się w danym momencie (przeczytaj wpis na blogu sigfpe „You could have wynaleziono monady” ).MVar
s są w zasadzie tym samym, co IORef, z wyjątkiem dwóch bardzo ważnych różnic.MVar
jest prymitywem współbieżności, więc jest przeznaczony do dostępu z wielu wątków. Druga różnica polega na tym, żeMVar
jest to pudełko, które może być pełne lub puste. Więc jeśliIORef Int
zawsze maInt
(lub jest na dole),MVar Int
może miećInt
lub może być puste. Jeśli wątek spróbuje odczytać wartość z pustegoMVar
, będzie blokować, dopóki nieMVar
zostanie wypełniony (przez inny wątek). Zasadniczo anMVar a
jest odpowiednikiem anIORef (Maybe a)
z dodatkową semantyką, która jest przydatna do współbieżności.State
jest monadą, która zapewnia zmienny stan, niekoniecznie z IO. W rzeczywistości jest to szczególnie przydatne do czystych obliczeń. Jeśli masz algorytm, który używa stanu, ale go nie maIO
,State
monada jest często eleganckim rozwiązaniem.Istnieje również wersja State, z transformatorem monadowym
StateT
. Jest to często używane do przechowywania danych konfiguracyjnych programu lub stanów „stanu świata gry” w aplikacjach.ST
jest czymś nieco innym. Główną strukturą danych w programieST
jestSTRef
, który jest podobny doIORef
monady, ale z inną monadą.ST
Oszustwo system wykorzystuje Monad typ (dalej „nici państwowe” docs wspominając) w celu zapewnienia, że dane Zmienne nie może uciec monady; to znaczy, kiedy wykonujesz obliczenia ST, otrzymujesz czysty wynik. Powodem, dla którego ST jest interesujący, jest to, że jest to prymitywna monada, taka jak IO, pozwalająca obliczeniom na wykonywanie niskopoziomowych manipulacji na tablicach i wskaźnikach. Oznacza to, żeST
może zapewnić czysty interfejs podczas wykonywania operacji niskiego poziomu na danych modyfikowalnych, co oznacza, że jest bardzo szybki. Z perspektywy programu wygląda to tak, jakbyST
obliczenia przebiegały w oddzielnym wątku z magazynem lokalnym.źródło
Inni zrobili podstawowe rzeczy, ale odpowiadając na bezpośrednie pytanie:
Lisp to język funkcjonalny ze zmiennym stanem i zakresem leksykalnym. Wyobraź sobie, że zamknąłeś zmienną zmienną. Teraz masz odniesienie do tej zmiennej, które kręci się wewnątrz innej funkcji - powiedzmy (w pseudokodzie w stylu haskell)
(printIt, setIt) = let x = 5 in (\ () -> print x, \y -> set x y)
. Masz teraz dwie funkcje - jedna wypisuje x, a druga ustawia jego wartość. Podczas ocenyprintIt
, chcesz odnośnika nazwę X w początkowym środowisku, w którymprintIt
została zdefiniowana, ale chcesz odnośnika wartość ta nazwa jest związany w środowisku, w którymprintIt
jest nazywany (posetIt
mogły być nazywane dowolną liczbę razy ).Poza dwoma IORefs są na to sposoby, ale z pewnością potrzebujesz czegoś więcej niż ten drugi typ, który zaproponowałeś, co nie pozwala na zmianę wartości, do których nazwy są przypisane w sposób leksykalny. Wygoogluj "problem funargów" dla wielu interesujących prehistorii.
źródło