Stanka za samostan

Monads može učiniti mnogo nevjerojatnih, ludih stvari. Mogu stvoriti varijable koje sadrže superpoziciju vrijednosti. Mogu vam omogućiti pristup podacima iz budućnosti prije nego što ih izračunate. Mogu vam dopustiti pisanje destruktivnih ažuriranja, ali ne sasvim. A onda vam nastavak monade omogućuje lomljenje ljudi! Usushno - vlastito; -)

Ali ovdje je poziv: Možete li napraviti monadu koja se može obustaviti?

 pauziranje podataka sx primjer monada (pauza s) mutate :: (s -> s) -> Pauziraj s () prinos :: Pauza s () step :: s -> Pauziraj s () -> (s, možda (pauziraj s ()))

Pause Monad je svojevrsna državna monada (stoga, mutate , s očiglednom semantikom). Obično takva monada ima neku vrstu "run" funkcije koja pokreće izračun i vraća vam konačno stanje. Ali Pause je drugačiji: on pruža step funkciju, koja započinje izračun dok ne pozove yield magične funkcije. Ovdje je izračun obustavljen, a pozivatelju se vraća dovoljno informacija da bi kasnije mogao nastaviti izračun.

Za dodatnu awesomness: omogućuje pozivatelju da promijeni stanje između poziva poziva. (Na primjer, gore navedene oznake oznaka trebale bi to dopustiti.)


Koristi slučaj: često je lako napisati kod koji čini nešto složeno, ali kompletan PITA, kako bi ga pretvorio, da bi također izlazio međudržavo u svoj rad. Ako želite da korisnik nešto promijeni usred izvršavanja, sve postaje vrlo brzo.

Ideje za implementaciju:

  • Očito, to se može učiniti s niti, bravama i IO . Ali možemo li bolje? -)

  • Ima li nešto u redu s nastavkom monade?

  • Možda neki pisac monada, gdje yield jednostavno bilježi trenutno stanje, a onda možemo "pretvarati" se step , ponavljajući stanja u časopisu. (Očito, to isključuje promjenu stanja između koraka, budući da trenutno ništa ne "suspendiramo".)

59
20 апр. postavio MathematicalOrchid 20. tra 2012-04-20 00:20 '12 u 0:20 2012-04-20 00:20
@ 6 odgovora

Naravno; jednostavno dopuštate da bilo koji izračun dovrši rezultat ili se obustavite, dajući radnju za nastavak, kao i stanje u to vrijeme:

 data Pause sa = Pause { runPause :: s -> (PauseResult sa, s) } data PauseResult sa = Done a | Suspend (Pause sa) instance Monad (Pause s) where return a = Pause (\s -> (Done a, s)) m >>= k = Pause $ \s -> case runPause ms of (Done a, s') -> runPause (ka) s' (Suspend m', s') -> (Suspend (m' >>= k), s') get :: Pause ss get = Pause (\s -> (Done s, s)) put :: s -> Pause s () put s = Pause (\_ -> (Done (), s)) yield :: Pause s () yield = Pause (\s -> (Suspend (return ()), s)) step :: Pause s () -> s -> (Maybe (Pause s ()), s) step ms = case runPause ms of (Done _, s') -> (Nothing, s') (Suspend m', s') -> (Just m', s') 

Primjer Monad jednostavno uređuje stvari na uobičajeni način, prenoseći konačni rezultat na nastavak k ili dodajući ostatak izračuna koji se mora obaviti tijekom suspenzije.

53
20 апр. odgovor ehird 20 apr. 2012-04-20 00:46 '12 u 12:46 2012-04-20 00:46

Napomena: Niste dali sebi izravan pristup trenutnom stanju s u ovoj monadi.

Pause s je jednostavno slobodna monada o mutate i yield operacijama. Izvršeno izravno:

 data Pause sa = Return a | Mutate (s -> s) (Pause sa) | Yield (Pause sa) instance Monad (Pause s) where return = Return Return a >>= k = ka Mutate fp >>= k = Mutate f (p >>= k) Yield p >>= k = Yield (p >>= k) 

s nekoliko inteligentnih dizajnera koji će vam pružiti željeni API:

 mutate :: (s -> s) -> Pause s () mutate f = Mutate f (return ()) yield :: Pause s () yield = Yield (return ()) 

i korak funkcije za pomicanje

 step :: s -> Pause s () -> (s, Maybe (Pause s ())) step s (Mutate fk) = step (fs) k step s (Return ()) = (s, Nothing) step s (Yield k) = (s, Just k) 

To možete odrediti i izravno pomoću

 data Free fa = Pure a | Free (f (Free fa)) 

(iz mog "besplatnog" paketa) s

 data Op sa = Mutate (s -> s) a | Yield a 
border=0

onda već imamo monadu za pauzu

 type Pause s = Free (Op s) 

i samo trebate identificirati inteligentne dizajnere i stepere.

Brže.

Sada su ove implementacije lako argumentirati, ali nemaju najbrži operativni model. Konkretno, lijevo pridružene aplikacije (→ =) daju asimptotički sporiji kod.

Da biste to zaobišli, možete primijeniti Codensity monadu na postojeću slobodnu monadu, ili jednostavno koristiti monadu 'besplatno u Crkvi' , koju oboje detaljno opisujem na svom blogu.

http://comonad.com/reader/2011/free-monads-for-less/

http://comonad.com/reader/2011/free-monads-for-less-2/

http://comonad.com/reader/2011/free-monads-for-less-3/

Rezultat primjene verzije Free monade, kodirane u crkvi, je da lako možete govoriti o modelu za vrstu podataka, a još uvijek možete dobiti model brze procjene.

60
20 апр. odgovor je dao Edward KMETT 20. travnja 2012-04-20 00:51 '12 u 0:51 2012-04-20 00:51

Tako ću to učiniti pomoću besplatnih monada. Uh, što su oni? To su stabla s djelovanjem na čvorovima i vrijednostima na lišću, dok >>= djeluje kao stablo cijepljenja.

 data f :^* x = Ret x | Do (f (f :^* x)) 

Nije neobično pisati F * X za takvu stvar u matematici, otuda i ime moje mrzovoljne infix. Da biste stvorili instancu, samo trebate napraviti ono što možete prikazati: svaki Functor će učiniti.

 instance Functor f => Monad ((:^*) f) where return = Ret Ret x >>= k = kx Do ffx >>= k = Do (fmap (>>= k) ffx) 

To je samo "nanesite k na sve listove i kalem u stablima koja su nastala." Ova stabla mogu biti strategije za interaktivno računanje: cijelo stablo pokriva sve moguće interakcije s okolinom, a okolina odabire koju će stazu slijediti u stablu. Zašto su slobodni? To su samo stabla, bez kojih nema Equanove teorije, govoreći koje su strategije ekvivalentne drugim strategijama.

Sada dajte skupu da kreira Funktore, koji odgovara skupu naredbi koje možemo htjeti. Ova stvar

 data (:>>:) stx = s :? (t -> x) instance Functor (s :>>: t) where fmap k (s :? f) = s :? (k . f) 

snima ideju dobivanja vrijednosti u x nakon jedne naredbe s tipom ulaza s i tipom izlaza t . Da biste to učinili, morate odabrati ulaz za s i objasniti kako nastaviti vrijednost u x , uzimajući u obzir izlaz naredbe za t . Da biste prikazali funkciju kroz takvu stvar, vežite je u nastavak. Do sada, standardna oprema. Za naš problem sada možemo definirati dva funktora:

 type Modify s = (s -> s) :>>: () type Yield = () :>>: () 

Volim činjenicu da sam samo zapisao tipove vrijednosti za naredbe koje želimo napraviti!

Sada budimo sigurni da možemo ponuditi izbor između tih timova. Možemo pokazati da izbor između funktora daje funktor. Više standardne opreme.

 data (:+:) fgx = L (fx) | R (gx) instance (Functor f, Functor g) => Functor (f :+: g) where fmap k (L fx) = L (fmap k fx) fmap k (R gx) = R (fmap k gx) 

Dakle, Modify s :+: Yield je izbor između izmjene i koncesije. Svaki potpis jednostavnih naredbi (povezivanje sa svijetom u smislu vrijednosti, a ne manipulacija kalkulacijama) može se na taj način pretvoriti u functor. Smeta mi što to radim ručno!

To mi daje vašu monadu: slobodnu monadu nad potpisom promjene i štete.

 type Pause s = (:^*) (Modify s :+: Yield) 

Mogu definirati promjene i izlazne naredbe kao jednokratne, a zatim vratiti. Osim dogovora o izmišljenom unosu za yield , to je samo mehanički.

 mutate :: (s -> s) -> Pause s () mutate f = Do (L (f :? Ret)) yield :: Pause s () yield = Do (R (() :? Ret)) 

Zatim step funkcija daje vrijednost za stabla strategije. To je kontrolni operator koji je izgradio jedan izračun (po mogućnosti) iz drugog.

 step :: s -> Pause s () -> (s, Maybe (Pause s ())) step s (Ret ()) = (s, Nothing) step s (Do (L (f :? k))) = step (fs) (k ()) step s (Do (R (() :? k))) = (s, Just (k ())) 

Funkcija step pokreće strategiju dok se ne dovrši s Ret , ili će mutirati stanje kako se pojavljuje.

Opća metoda je sljedeća: Odvojene naredbe (interakcije u smislu vrijednosti) od upravljačkih operatera (manipuliranje izračunima); izgraditi slobodnu monadu "strateških stabala" iznad potpisa naredbe (odmotavanje deskriptora); implementirati upravljačke operatore rekurzije preko stabala strategije.

30
20 апр. Odgovor koji je dao svinjac 20 Apr. 2012-04-20 01:15 '12 u 1:15 2012-04-20 01:15

Ne odgovara točno vašoj vrsti potpisa, ali je definitivno jednostavna:

 {-# > 
10
20 апр. Odgovor dao je Daniel Wagner 20. travnja 2012-04-20 05:13 '12 u 5:13 2012-04-20 05:13
 {-# > 

Način na koji sam ga napisao. Dao sam step nešto općenitije definicije, također se može nazvati runPause . Zapravo, razmišljanje o vrsti step vodi me do definicije Pause .

U paketu monad-coroutine naći ćete zajednički monodalni transformator. Monada Pause s ista je kao Coroutine (State s) Id . Koroutine možete kombinirati s drugim monadama.

Povezano: Pozivanje monade na http://themonadreader.files.wordpress.com/2010/01/issue15.pdf

8
20 апр. odgovor je dan sdcvvc 20. travnja 2012-04-20 00:50 '12 u 0:50

Napomena. Taj je odgovor dostupan kao kompetentna Haskell datoteka u Gist.

Doista sam uživao u ovoj vježbi. Pokušao sam to učiniti bez gledanja odgovora, i bilo je vrijedno toga. Trebalo mi je dosta vremena, ali rezultat je iznenađujuće blizu ostalih dvaju odgovora, kao i knjižnice monad-coroutine . Stoga predlažem da je ovo prilično prirodno rješenje ovog problema. Bez ove vježbe ne bih razumio kako djeluje monad-koroutin.

Da bih dodao neku dodatnu vrijednost, objasnit ću korake koji su me na kraju doveli do odluke.

Priznanje državne monade

Budući da se bavimo državama, tražimo obrasce koje država može učinkovito opisati. Konkretno, s - s je izomorfan s -> (s, ()) , tako da ga može zamijeniti State s () . Funkcija tipa s -> x -> (s, y) može biti okrenuta na x -> (s -> (s, y)) , što je zapravo x -> State sy . To nas dovodi do ažuriranih potpisa.

 mutate :: State s () - Pause s () step :: Pause s () - State s (Maybe (Pause s ())) 

generalizacija

Naša Pause monada trenutno je parametrizirana od strane države. Međutim, sada vidimo da nam zapravo ništa ne treba za državu i ne koristimo nikakve značajke državne monade. Stoga bismo mogli pokušati napraviti općenitije rješenje, parametrirano od strane bilo koje monade:

 mutate :: (Monad m) = m () -> Pause m () yield :: (Monad m) = Pause m () step :: (Monad m) = Pause m () -> m (Maybe (Pause m ())) 

Osim toga, mogli bismo pokušati učiniti mutate i step općenitijim, dopuštajući bilo koju vrijednost, a ne samo () . I, shvaćajući da je Maybe a izomorfan Either a () , konačno možemo sažeti naše potpise na

 mutate :: (Monad m) = ma -> Pause ma yield :: (Monad m) = Pause m () step :: (Monad m) = Pause ma -> m (Either (Pause ma) a) 

tako step vraća srednju vrijednost izračuna.

Monad Transformer

Sada vidimo da zapravo pokušavamo napraviti monadu monade - dodajte neke dodatne funkcije. To je ono što se obično naziva monad transformator. Štoviše, mutate potpis je potpuno isti kao i lift iz MonadTrans . Najvjerojatnije smo na pravom putu.

Konačna monada

step , očito, najvažniji je dio naše monade, definira samo ono što nam je potrebno. Možda bi to mogla biti nova struktura podataka? pokušajte:

 import Control.Monad import Control.Monad.Cont import Control.Monad.State import Control.Monad.Trans data Pause ma = Pause { step :: m (Either (Pause ma) a) } 

Ako je dio Either Right , to je samo monadska vrijednost, bez ikakve suspenzije. To nas dovodi do toga kako implementirati pojednostavljenu stvar - funkciju lift iz MonadTrans :

 instance MonadTrans Pause where lift k = Pause (liftM Right k) 

i mutate je samo specijalizacija:

 mutate :: (Monad m) => m () -> Pause m () mutate = lift 

Ako je dio Left , to je tekući izračun nakon pauze. Stoga stvorimo funkciju za ovo:

 suspend :: (Monad m) => Pause ma -> Pause ma suspend = Pause . return . Left 

Sada je izračun yield jednostavan, pauziramo s praznim izračunom:

 yield :: (Monad m) => Pause m () yield = suspend (return ()) 

Međutim, nedostaje nam najvažniji dio. Monad primjer. Neka to popravi. Provedba return je jednostavna, mi samo podižemo unutarnju monadu. Implementacija >>= malo složenija. Ako je izvorna vrijednost Pause bila samo jednostavna vrijednost ( Right y ), onda jednostavno fy kao rezultat. Ako je riječ o obustavljenom izračunu koji se može nastaviti ( Left p ), mi se rekurzivno spuštamo u njega.

 instance (Monad m) => Monad (Pause m) where return x = lift (return x) -- Pause (return (Right x)) (Pause s) >>= f = Pause $ s >>= \x -> case x of Right y -> step (fy) Left p -> return (Left (p >>= f)) 

testiranje

Pokušajte napraviti neku funkciju modela koja koristi i ažurira stanje, donoseći unutar izračuna:

 test1 :: Int -> Pause (State Int) Int test1 y = do x <- lift get lift $ put (x * 2) yield return (y + x) 

A pomoćna funkcija koja otklanja greške u monadi - ispisuje svoje međukorake za konzolu:

 debug :: Show s => s -> Pause (State s) a -> IO (s, a) debug sp = case runState (step p) s of (Left next, s') -> print s' >> debug s' next (Right r, s') -> return (s', r) main :: IO () main = do debug 1000 (test1 1 >>= test1 >>= test1) >>= print 

Rezultat

 2000 4000 8000 (8000,7001) 

kako je i očekivano.

Coroutines i monad-coroutine

Implementirali smo prilično generičko monadično rješenje koje implementira Coroutines . Možda i ne čudi da je netko prije imao ideju :-) i stvorio paket monad-coroutine . Manje iznenađujuće, ovo je vrlo slično onome što smo stvorili.

Paket još više generalizira ovu ideju. Kontinuirano računanje je pohranjeno unutar proizvoljnog funkcije. To vam omogućuje da obustavite mnoge opcije za rad s obustavljenim računanjima. Na primjer, proslijedite vrijednost nastavku pozivatelja (koji smo nazvali step ) ili pričekajte da se vrijednost nastavi itd.

8
31 авг. odgovor od Petr Pudlák 31 kol. 2012-08-31 16:06 '12 u 16:06 2012-08-31 16:06