2016-09-09 14 views
10

mam Producer który tworzy wartości, które zależą od przypadkowości, wykorzystując własne Random monady:Jak mogę idiomatycznie i wydajnie spożywać fajkę w monadzie innej niż IO z operacją IO?

policies :: Producer (Policy s a) Random x 

Random jest opakowaniem nad mwc-random, które mogą być uruchamiane z ST lub IO:

newtype Random a = 
    Random (forall m. PrimMonad m => Gen (PrimState m) -> m a) 

runIO :: Random a -> IO a 
runIO (Random r) = MWC.withSystemRandom (r @ IO) 

policies producent otrzymuje lepsze i lepsze zasady z prostego algorytmu uczenia zbrojenia.

mogę skutecznie po wykreślić politykę, powiedzmy, 5.000.000 iteracje przez indeksowanie w policies:

Just convergedPolicy <- Random.runIO $ Pipes.index 5000000 policies 
plotPolicy convergedPolicy "policy.svg" 

I teraz chce wykreślić pośrednie polityki na każdym 500000 kroków, aby zobaczyć, jak one zbieżne. Napisałem kilka funkcji, które pobierają producent policies i wyodrębniają listę ([Policy s a]) z, powiedzmy, 10 zasad - po jednej na 500 000 iteracji - a następnie wykreślają wszystkie z nich.

Jednak te funkcje zajmują dużo więcej czasu (10x) i zużywają więcej pamięci (4x) niż tylko kreślenie ostatecznej polityki, jak wyżej, mimo że całkowita liczba iteracji uczenia się powinna być taka sama (np. 5 000 000). Podejrzewam, że to z powodu wydobywania listę hamującą śmieciarza, a to wydaje się być unidiomatic zastosowanie Rury

Idiomatic rury styl zużywa natychmiast elementów, ponieważ są one generowane zamiast ładuje wszystkie elementy w pamięci .

Co to jest prawidłowe podejście do spożywania rurę tak, gdy Producer jest nad jakimś losowym monady (tj Random) i efekt chcę produkować jest w IO?

Innymi słowy, chcę podłączyć Producer (Policy s a) Random x do Consumer (Policy s a) IO x.

+0

Czy 'Random' ma instancję' MonadIO'? Jeśli tak, będziesz mieć 'hoist liftIO :: Consumer (Policy s a) IO x -> Consumer (Policy s a) Random x'. Są na to inne sposoby, niektóre mogą być szybsze, ale może pomóc dowiedzieć się więcej o 'Losowym'. – Michael

+1

@Michael: Nie ma i nie chcę tego. Może jedynie wykonywać losowość i * nie * ogólne IO. Idealnie, chciałbym rozwiązanie, które zależy tylko od tego, że 'Random' ma funkcję' runIO :: Random a -> IO a'. –

+1

I chyba nie chcesz, aby 'hoist runIO' konwertował' Producer (Policy s a) Random x' na 'Producer (Policy s a) IO x' ze względu na sposób generowania losowości. – Michael

Odpowiedz

2

Random to czytnik, który czyta generator

import Control.Monad.Primitive 
import System.Random.MWC 

newtype Random a = Random { 
    runRandom :: forall m. PrimMonad m => Gen (PrimState m) -> m a 
} 

Możemy trywialnie przekonwertować Random a w ReaderT (Gen (PrimState m)) m a. Ta trywialna operacja jest tą, którą chcesz hoist przekształcić Producer ... Random a w Producer ... IO a.

import Control.Monad.Trans.Reader 

toReader :: PrimMonad m => Random a -> ReaderT (Gen (PrimState m)) m a 
toReader = ReaderT . runRandom 

Od toReader jest trywialne nie będzie żadnych losowa generacja napowietrznych od hoist ing go. Ta funkcja jest napisana tylko po to, aby zademonstrować jej typ podpisu.

import Pipes 

hoistToReader :: PrimMonad m => Proxy a a' b b' Random       r -> 
           Proxy a a' b b' (ReaderT (Gen (PrimState m)) m) r 
hoistToReader = hoist toReader 

Istnieją dwa podejścia, które należy tutaj zastosować. Proste podejście to hoist twój Consumer do tej samej monady, skomponowanie rur razem i uruchomienie ich.

type ReadGenIO = ReaderT GenIO IO 

toReadGenIO :: MFunctor t => t Random a -> t ReadGenIO a 
toReadGenIO = hoist toReader 

int :: Random Int 
int = Random uniform 

ints :: Producer Int Random x 
ints = forever $ do 
    i <- lift int 
    yield i 

sample :: Show a => Int -> Consumer a IO() 
sample 0 = return() 
sample n = do 
    x <- await 
    lift $ print x 
    sample (n-1) 

sampleSomeInts :: Effect ReadGenIO() 
sampleSomeInts = hoist toReader ints >-> hoist lift (sample 1000) 

runReadGenE :: Effect ReadGenIO a -> IO a 
runReadGenE = withSystemRandom . runReaderT . runEffect 

example :: IO() 
example = runReadGenE sampleSomeInts 

Jest jeszcze jeden zestaw narzędzi w Pipes.Lift że użytkownicy rur powinien być świadomy. Są to narzędzia do pracy z transformatorami, takimi jak monada Random, poprzez dystrybucję ich przez Proxy. Są tu gotowe narzędzia do uruchamiania znanych transformatorów z biblioteki transformatorów. Wszystkie są zbudowane z distribute. Włącza się Proxy ... (t m) a w t (Proxy ... m) a, który można uruchomić raz z dowolnymi narzędziami, których używasz do uruchomienia t.

import Pipes.Lift 

runRandomP :: PrimMonad m => Proxy a a' b b' Random r -> 
          Gen (PrimState m) -> Proxy a a' b b' m r 
runRandomP = runReaderT . distribute . hoist toReader 

można zakończyć łączenie rur ze sobą i używać runEffect aby pozbyć się Proxy s, ale chcesz być żonglerka argument Generator się jak połączyć Proxy ... IO r y razem.

+0

Właśnie dostałem się do realizacji tego i zdecydowanie podoba mi się to rozwiązanie. Dzięki! –