Haskell Language
Gratis monader
Sök…
Gratis monader delar upp monadiska beräkningar i datastrukturer och tolkar
Till exempel en beräkning som involverar kommandon att läsa och skriva från prompten:
Först beskriver vi "kommandona" för vår beräkning som en Functor-datatyp
{-# LANGUAGE DeriveFunctor #-}
data TeletypeF next
= PrintLine String next
| ReadLine (String -> next)
deriving Functor
Sedan använder vi Free att skapa "Free Monad over TeletypeF " och bygga några grundläggande operationer.
import Control.Monad.Free (Free, liftF, iterM)
type Teletype = Free TeletypeF
printLine :: String -> Teletype ()
printLine str = liftF (PrintLine str ())
readLine :: Teletype String
readLine = liftF (ReadLine id)
Eftersom Free f är en Monad när f är en Functor , kan vi använda de vanliga Monad kombinatorer (inklusive do notation) för att bygga Teletype beräkningar.
import Control.Monad -- we can use the standard combinators
echo :: Teletype ()
echo = readLine >>= printLine
mockingbird :: Teletype a
mockingbird = forever echo
Slutligen skriver vi en "tolk" som förvandlar Teletype a värden till något vi vet hur vi arbetar med som IO a
interpretTeletype :: Teletype a -> IO a
interpretTeletype = foldFree run where
run :: TeletypeF a -> IO a
run (PrintLine str x) = putStrLn *> return x
run (ReadLine f) = fmap f getLine
Som vi kan använda för att "köra" Teletype a beräkning i IO
> interpretTeletype mockingbird
hello
hello
goodbye
goodbye
this will go on forever
this will go on forever
Gratis monader är som fasta punkter
Jämför definitionen av Free med Fix :
data Free f a = Return a
| Free (f (Free f a))
newtype Fix f = Fix { unFix :: f (Fix f) }
Jämför i synnerhet typen av Free konstruktör med typen av Fix . Free lager upp en funktor precis som Fix , förutom att Free har en extra Return a case.
Hur fungerar foldFree och iterM?
Det finns några funktioner som hjälper till att riva ned Free beräkningar genom att tolka dem till en annan monad m : iterM :: (Functor f, Monad m) => (f (ma) -> ma) -> (Free fa -> ma) och foldFree :: Monad m => (forall x. fx -> mx) -> (Free fa -> ma) . Vad håller de på med?
Låt oss först se vad som krävs för att riva ner en tolkning av en Teletype a funktion till IO manuellt. Vi kan se Free fa definieras
data Free f a
= Pure a
| Free (f (Free f a))
Pure fallet är enkelt:
interpretTeletype :: Teletype a -> IO a
interpretTeletype (Pure x) = return x
interpretTeletype (Free teletypeF) = _
Hur tolkar Teletype en Teletype beräkning som byggdes med Free constructor? Vi vill komma fram till ett värde av typ IO a genom att undersöka teletypeF :: TeletypeF (Teletype a) . Till att börja med skriver vi en funktion runIO :: TeletypeF a -> IO a som kartlägger ett enda lager av den fria monaden till en IO åtgärd:
runIO :: TeletypeF a -> IO a
runIO (PrintLine msg x) = putStrLn msg *> return x
runIO (ReadLine k) = fmap k getLine
Nu kan vi använda runIO att fylla i resten av interpretTeletype . Minns att teletypeF :: TeletypeF (Teletype a) är ett skikt av den TeletypeF funktor som innehåller resten av Free beräkning. Vi använder runIO att tolka det yttersta lagret (så vi har runIO teletypeF :: IO (Teletype a) ) och använder sedan IO monaden >>= combinator för att tolka den returnerade Teletype a .
interpretTeletype :: Teletype a -> IO a
interpretTeletype (Pure x) = return x
interpretTeletype (Free teletypeF) = runIO teletypeF >>= interpretTeletype
Definitionen av foldFree är bara den för interpretTeletype , förutom att runIO funktionen har tagits bort. Som ett resultat fungerar foldFree oberoende av någon speciell basfunktor och vilken målmonad som helst.
foldFree :: Monad m => (forall x. f x -> m x) -> Free f a -> m a
foldFree eta (Pure x) = return x
foldFree eta (Free fa) = eta fa >>= foldFree eta
foldFree har en rang-2-typ: eta är en naturlig transformation. Vi kunde ha gett foldFree en typ av Monad m => (f (Free fa) -> m (Free fa)) -> Free fa -> ma , men det ger eta friheten att inspektera den Free beräkningen inuti f lagret. Ge foldFree denna mer restriktiva typ säkerställer att eta bara kan behandla ett enda lager åt gången.
iterM ger vikningsfunktionen förmågan att undersöka underkomputationen. (Monadiskt) resultat från den föregående iterationen är tillgängligt för nästa, inre f 's parameter. iterM är analog med en paramorfism medan foldFree är som en katamorfism .
iterM :: (Monad m, Functor f) => (f (m a) -> m a) -> Free f a -> m a
iterM phi (Pure x) = return x
iterM phi (Free fa) = phi (fmap (iterM phi) fa)
Den friare monaden
Det finns en alternativ formulering av den fria monaden som kallas Freer (eller Prompt eller Operational) monad. Freer-monaden kräver inte en Functor-instans för den underliggande instruktionsuppsättningen, och den har en mer kännbar listliknande struktur än den standardfria monaden.
Den Freer monad representerar program som en sekvens av atom instruktioner som hör till instruktionsuppsättning i :: * -> * . Varje instruktion använder sin parameter för att deklarera sin returtyp. Till exempel uppsättningen bas instruktioner för State mona är följande:
data StateI s a where
Get :: StateI s s -- the Get instruction returns a value of type 's'
Put :: s -> StateI s () -- the Put instruction contains an 's' as an argument and returns ()
Sekvensering av dessa instruktioner sker med :>>= konstruktören. :>>= tar en enda instruktion som returnerar ett a och beror på det till resten av programmet och rör dess returvärde till fortsättningen. Med andra ord, med tanke på en instruktion som returnerar en a , och en funktion för att förvandla a till ett program som returnerar a b , kommer :>>= att producera ett program som returnerar a b .
data Freer i a where
Return :: a -> Freer i a
(:>>=) :: i a -> (a -> Freer i b) -> Freer i b
Observera att a är existentiellt kvantifierat i :>>= konstruktören. Det enda sättet för en tolk att lära sig vad a är är genom att matcha mönster på GADT i .
Vid sidan : Sam-Yoneda-lemmet berättar att
Freerär isomorf förFree. Kom ihåg definitionen avCoYonedafunktorn:data CoYoneda i b where CoYoneda :: i a -> (a -> b) -> CoYoneda i b
Freer imotsvararFree (CoYoneda i). Om du tarFree's konstruktörer och ställer inf ~ CoYoneda i, får du:Pure :: a -> Free (CoYoneda i) a Free :: CoYoneda i (Free (CoYoneda i) b) -> Free (CoYonda i) b ~ i a -> (a -> Free (CoYoneda i) b) -> Free (CoYoneda i) bfrån vilken vi kan återfå
Freer i's konstruktörer genom att bara ställa inFreer i ~ Free (CoYoneda i).
Eftersom CoYoneda i är en Functor för alla i , är Freer en Monad för alla i , även om i inte är en Functor .
instance Monad (Freer i) where
return = Return
Return x >>= f = f x
(i :>>= g) >>= f = i :>>= fmap (>>= f) g -- using `(->) r`'s instance of Functor, so fmap = (.)
Tolkar kan byggas för Freer genom att kartlägga instruktioner till någon hanteringsmonad.
foldFreer :: Monad m => (forall x. i x -> m x) -> Freer i a -> m a
foldFreer eta (Return x) = return x
foldFreer eta (i :>>= f) = eta i >>= (foldFreer eta . f)
Vi kan till exempel tolka Freer (StateI s) monaden med den vanliga State s monaden som en hanterare:
runFreerState :: Freer (StateI s) a -> s -> (a, s)
runFreerState = State.runState . foldFreer toState
where toState :: StateI s a -> State s a
toState Get = State.get
toState (Put x) = State.put x