2015-07-25 11 views
6

Aby zrozumieć, jak używać transformatorów Monada, napisałem poniższy kod bez niego. Odczytuje standardową linię wejściową po linii i wyświetla każdą linię odwróconą, aż napotkana zostanie pusta linia. Zlicza również linie przy użyciu State, a na końcu wyświetla całkowitą liczbę.Użyj dwóch monad bez transformatora.

import Control.Monad.State 

main = print =<< fmap (`evalState` 0) go where 
    go :: IO (State Int Int) 
    go = do 
     l <- getLine 
     if null l 
     then return get 
     else do 
      putStrLn (reverse l) 
      -- another possibility: fmap (modify (+1) >>) go 
      rest <- go 
      return $ do 
       modify (+1) 
       rest 

Chciałem dodać bieżący numer linii przed każdym wierszem. Udało mi się to zrobić z StateT:

import Control.Monad.State 

main = print =<< evalStateT go 0 where 
    go :: StateT Int IO Int 
    go = do 
     l <- lift getLine 
     if null l 
     then get 
     else do 
      n <- get 
      lift (putStrLn (show n ++ ' ' : reverse l)) 
      modify (+1) 
      go 

Moje pytanie brzmi: w jaki sposób zrobić to samo w wersji bez transformatorów monady?

Odpowiedz

2

Po prostu trzeba uruchomić obliczenia skumulowanego stanu dla każdej linii. To jest czas O (n²), ale ponieważ twój pierwszy program już korzysta z przestrzeni O (n), to nie jest takie straszne. Oczywiście podejście StateT jest lepsze pod każdym względem! Jeśli naprawdę chcesz robić to "ręcznie" i nie płacić ceny za wydajność, po prostu zarządzaj stanem ręcznie, zamiast budować w ogóle transformator. Naprawdę nie daje żadnych korzyści, używając State zamiast Int w pierwszym programie.

+2

Zdaję sobie z tego sprawę. Nie szukam wersji bez transformatora monadowego ze względu na wydajność, chcę tylko zobaczyć, jak to będzie wyglądać i nauczyć się czegoś, porównując te dwa, mając nadzieję, że lepiej zrozumie potrzebę transformatorów monadowych. – ByteEater

+0

Ponadto, uwzględnienie zakumulowanych obliczeń stanu dla każdej linii jest czymś, co uważałem i odrzucałem z dokładnie tego powodu: nie wydaje się to właściwym sposobem na użycie monad, proste "Int" byłoby lepszym wyborem. Co więcej, z inkrementacją nie robi to różnicy, ale byłoby błędnie koncepcyjnie, ponieważ obliczenia 'State' są budowane poprzez poprzedzanie akcji' modify (+1) ', więc jeślibym miał np. 'modify (+ length l)', które nie działałoby tak, jak powinno. – ByteEater

+0

@ByteEater, sposób, aby to zrobić bez transformatora Monada, to po prostu przekazać 'Int' ręcznie (denerwujące) lub użyć' IORef' (ograniczone do rzeczy 'IO' i potencjalnie nieefektywne, ale w porządku, jeśli pole jest nieuniknione lub aktualizacje są rzadkie). Nie wiem, czego jeszcze szukasz. – dfeuer

1

Może tego właśnie szukasz?

main = print =<< fmap (`evalState` 0) (go get) where 
    go :: State Int Int -> IO (State Int Int) 
    go st = do 
    l <- getLine 
    if null l 
    then return (st >>= \_ -> get) 
    else do 
      let ln = evalState st 0 
      putStrLn(show ln ++ ' ' : reverse l) 
      go (st >>= \_ -> modify (+1) >>= \_ -> get) 

Chodzi o to, aby go ogon rekurencyjnej, budując swoje obliczenia stanu, który można następnie ocenić na każdym kroku.

EDIT

Ta wersja będzie związany rozmiar obliczania państwowego do stałej wielkości, choć w trakcie oceny leniwy, gdy poprzednie obliczenia stan jest zmuszony, powinniśmy być w stanie używać go bez ponownej oceny to, więc zgaduję, że są w zasadzie takie same ...

main = print =<< fmap (`evalState` 0) (go get) where 
    go :: State Int Int -> IO (State Int Int) 
    go st = do 
    l <- getLine 
    if null l 
    then return st 
    else do 
      let ln = evalState st 0 
      putStrLn(show ln ++ ' ' : reverse l) 
      go (modify (\s -> s+ln+1) >>= \_ -> get) 
+0

W rzeczywistości pozwala to na użycie bieżącej wartości obliczonej w 'State Int', kosztem ponownego przeliczenia jej liniową liczbę razy (chociaż z językiem referencyjnym przejrzystym i inteligentnym kompilatorem można tego kosztu uniknąć). Rozważałem to, ale nie byłem przekonany, że jest to kanoniczny odpowiednik monady "State" i "IO" z mojego drugiego programu, który używa transformatora. – ByteEater

+0

Wątpię, czy jest to coś bardziej "kanonicznego" niż moja pierwsza próba, ale moja druga edycja może ograniczyć obliczenia stanu do stałej wielkości, odrzucając poprzednie obliczenia stanu i natychmiast ustawiając bieżący stan na ostatni wynik plus 1. Czy jest coś lepszego? – Matt

+0

Lepiej, zgodnie z opisem. Ale nadal nie można go łatwo porównać z wersją przy użyciu StateT, aby wyraźnie zobaczyć, jakie ulepszenia kodu (refaktoryzacja do formy wyższej) można faktycznie osiągnąć za pomocą transformatora monad. – ByteEater

10

problem masz jest, że ręka jest rozwijanie wśród StateT s IO as -> IO (s, a) nie IO (s -> (s, a))! Po uzyskaniu tego wglądu łatwo jest zobaczyć, jak to zrobić:

go :: Int -> IO (Int, Int) 
go s = do 
    l <- getLine 
    if null l 
    then return (s, s) 
    else do 
     putStrLn (show s ++ ' ' : reverse l) 
     go (s+1) 
Powiązane problemy